Plugin Directory

Changeset 2910869


Ignore:
Timestamp:
05/10/2023 07:28:55 PM (3 years ago)
Author:
sadowski
Message:

Tagging version 3.6.0

Location:
action-scheduler
Files:
28 added
38 edited
1 copied

Legend:

Unmodified
Added
Removed
  • action-scheduler/tags/3.6.0/action-scheduler.php

    r2850016 r2910869  
    66 * Author: Automattic
    77 * Author URI: https://automattic.com/
    8  * Version: 3.5.4
     8 * Version: 3.6.0
    99 * License: GPLv3
    1010 *
     
    2727 */
    2828
    29 if ( ! function_exists( 'action_scheduler_register_3_dot_5_dot_4' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION.
     29if ( ! function_exists( 'action_scheduler_register_3_dot_6_dot_0' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION.
    3030
    3131    if ( ! class_exists( 'ActionScheduler_Versions', false ) ) {
     
    3434    }
    3535
    36     add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_5_dot_4', 0, 0 ); // WRCS: DEFINED_VERSION.
     36    add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_6_dot_0', 0, 0 ); // WRCS: DEFINED_VERSION.
    3737
    3838    /**
    3939     * Registers this version of Action Scheduler.
    4040     */
    41     function action_scheduler_register_3_dot_5_dot_4() { // WRCS: DEFINED_VERSION.
     41    function action_scheduler_register_3_dot_6_dot_0() { // WRCS: DEFINED_VERSION.
    4242        $versions = ActionScheduler_Versions::instance();
    43         $versions->register( '3.5.4', 'action_scheduler_initialize_3_dot_5_dot_4' ); // WRCS: DEFINED_VERSION.
     43        $versions->register( '3.6.0', 'action_scheduler_initialize_3_dot_6_dot_0' ); // WRCS: DEFINED_VERSION.
    4444    }
    4545
     
    4747     * Initializes this version of Action Scheduler.
    4848     */
    49     function action_scheduler_initialize_3_dot_5_dot_4() { // WRCS: DEFINED_VERSION.
     49    function action_scheduler_initialize_3_dot_6_dot_0() { // WRCS: DEFINED_VERSION.
    5050        // A final safety check is required even here, because historic versions of Action Scheduler
    5151        // followed a different pattern (in some unusual cases, we could reach this point and the
     
    5959    // Support usage in themes - load this version if no plugin has loaded a version yet.
    6060    if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) {
    61         action_scheduler_initialize_3_dot_5_dot_4(); // WRCS: DEFINED_VERSION.
     61        action_scheduler_initialize_3_dot_6_dot_0(); // WRCS: DEFINED_VERSION.
    6262        do_action( 'action_scheduler_pre_theme_init' );
    6363        ActionScheduler_Versions::initialize_latest_version();
  • action-scheduler/tags/3.6.0/changelog.txt

    r2850016 r2910869  
    11*** Changelog ***
     2
     3= 3.6.0 - 2023-05-10 =
     4* Add $unique parameter to function signatures.
     5* Add a cast-to-int for extra safety before forming new DateTime object.
     6* Add a hook allowing exceptions for consistently failing recurring actions.
     7* Add action priorities.
     8* Add init hook.
     9* Always raise the time limit.
     10* Bump minimatch from 3.0.4 to 3.0.8.
     11* Bump yaml from 2.2.1 to 2.2.2.
     12* Defensive coding relating to gaps in declared schedule types.
     13* Do not process an action if it cannot be set to `in-progress`.
     14* Filter view labels (status names) should be translatable | #919.
     15* Fix WPCLI progress messages.
     16* Improve data-store initialization flow.
     17* Improve error handling across all supported PHP versions.
     18* Improve logic for flushing the runtime cache.
     19* Support exclusion of multiple groups.
     20* Update lint-staged and Node/NPM requirements.
     21* add CLI clean command.
     22* add CLI exclude-group filter.
     23* exclude past-due from list table all filter count.
     24* throwing an exception if as_schedule_recurring_action interval param is not of type integer.
    225
    326= 3.5.4 - 2023-01-17 =
  • action-scheduler/tags/3.6.0/classes/ActionScheduler_ActionFactory.php

    r2850016 r2910869  
    1414     * @param ActionScheduler_Schedule $schedule The action's schedule.
    1515     * @param string                   $group A group to put the action in.
     16     * @param int                      $priority The action priority.
    1617     *
    1718     * @return ActionScheduler_Action An instance of the stored action.
    1819     */
    1920    public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
     21        // The 6th parameter ($priority) is not formally declared in the method signature to maintain compatibility with
     22        // third-party subclasses created before this param was added.
     23        $priority = func_num_args() >= 6 ? (int) func_get_arg( 5 ) : 10;
    2024
    2125        switch ( $status ) {
     
    3741
    3842        $action = new $action_class( $hook, $args, $schedule, $group );
     43        $action->set_priority( $priority );
    3944
    4045        /**
    4146         * Allow 3rd party code to change the instantiated action for a given hook, args, schedule and group.
    4247         *
    43          * @param ActionScheduler_Action $action The instantiated action.
    44          * @param string $hook The instantiated action's hook.
    45          * @param array $args The instantiated action's args.
     48         * @param ActionScheduler_Action   $action The instantiated action.
     49         * @param string                   $hook The instantiated action's hook.
     50         * @param array                    $args The instantiated action's args.
    4651         * @param ActionScheduler_Schedule $schedule The instantiated action's schedule.
    47          * @param string $group The instantiated action's group.
     52         * @param string                   $group The instantiated action's group.
     53         * @param int                      $priority The action priority.
    4854         */
    49         return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group );
     55        return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group, $priority );
    5056    }
    5157
     
    230236        $new_schedule   = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() );
    231237        $new_action     = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() );
     238        $new_action->set_priority( $action->get_priority() );
    232239        return $this->store( $new_action );
     240    }
     241
     242    /**
     243     * Creates a scheduled action.
     244     *
     245     * This general purpose method can be used in place of specific methods such as async(),
     246     * async_unique(), single() or single_unique(), etc.
     247     *
     248     * @internal Not intended for public use, should not be overriden by subclasses.
     249     * @throws   Exception May be thrown if invalid options are passed.
     250     *
     251     * @param array $options {
     252     *     Describes the action we wish to schedule.
     253     *
     254     *     @type string     $type      Must be one of 'async', 'cron', 'recurring', or 'single'.
     255     *     @type string     $hook      The hook to be executed.
     256     *     @type array      $arguments Arguments to be passed to the callback.
     257     *     @type string     $group     The action group.
     258     *     @type bool       $unique    If the action should be unique.
     259     *     @type int        $when      Timestamp. Indicates when the action, or first instance of the action in the case
     260     *                                 of recurring or cron actions, becomes due.
     261     *     @type int|string $pattern   Recurrence pattern. This is either an interval in seconds for recurring actions
     262     *                                 or a cron expression for cron actions.
     263     *     @type int        $priority  Lower values means higher priority. Should be in the range 0-255.
     264     * }
     265     *
     266     * @return int
     267     */
     268    public function create( array $options = array() ) {
     269        $defaults = array(
     270            'type'      => 'single',
     271            'hook'      => '',
     272            'arguments' => array(),
     273            'group'     => '',
     274            'unique'    => false,
     275            'when'      => time(),
     276            'pattern'   => null,
     277            'priority'  => 10,
     278        );
     279
     280        $options = array_merge( $defaults, $options );
     281
     282        // Cron/recurring actions without a pattern are treated as single actions (this gives calling code the ability
     283        // to use functions like as_schedule_recurring_action() to schedule recurring as well as single actions).
     284        if ( ( 'cron' === $options['type'] || 'recurring' === $options['type'] ) && empty( $options['pattern'] ) ) {
     285            $options['type'] = 'single';
     286        }
     287
     288        switch ( $options['type'] ) {
     289            case 'async':
     290                $schedule = new ActionScheduler_NullSchedule();
     291                break;
     292
     293            case 'cron':
     294                $date     = as_get_datetime_object( $options['when'] );
     295                $cron     = CronExpression::factory( $options['pattern'] );
     296                $schedule = new ActionScheduler_CronSchedule( $date, $cron );
     297                break;
     298
     299            case 'recurring':
     300                $date     = as_get_datetime_object( $options['when'] );
     301                $schedule = new ActionScheduler_IntervalSchedule( $date, $options['pattern'] );
     302                break;
     303
     304            case 'single':
     305                $date     = as_get_datetime_object( $options['when'] );
     306                $schedule = new ActionScheduler_SimpleSchedule( $date );
     307                break;
     308
     309            default:
     310                throw new Exception( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." );
     311        }
     312
     313        $action = new ActionScheduler_Action( $options['hook'], $options['arguments'], $schedule, $options['group'] );
     314        $action->set_priority( $options['priority'] );
     315        return $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action );
    233316    }
    234317
  • action-scheduler/tags/3.6.0/classes/ActionScheduler_Compatibility.php

    r2622865 r2910869  
    55 */
    66class ActionScheduler_Compatibility {
    7 
    87    /**
    98     * Converts a shorthand byte value to an integer byte value.
     
    9089        $max_execution_time = (int) ini_get( 'max_execution_time' );
    9190
    92         /*
    93          * If the max execution time is already unlimited (zero), or if it exceeds or is equal to the proposed
    94          * limit, there is no reason for us to make further changes (we never want to lower it).
    95          */
    96         if (
    97             0 === $max_execution_time
    98             || ( $max_execution_time >= $limit && $limit !== 0 )
    99         ) {
     91        // If the max execution time is already set to zero (unlimited), there is no reason to make a further change.
     92        if ( 0 === $max_execution_time ) {
    10093            return;
    10194        }
    10295
     96        // Whichever of $max_execution_time or $limit is higher is the amount by which we raise the time limit.
     97        $raise_by = 0 === $limit || $limit > $max_execution_time ? $limit : $max_execution_time;
     98
    10399        if ( function_exists( 'wc_set_time_limit' ) ) {
    104             wc_set_time_limit( $limit );
     100            wc_set_time_limit( $raise_by );
    105101        } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
    106             @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     102            @set_time_limit( $raise_by ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    107103        }
    108104    }
  • action-scheduler/tags/3.6.0/classes/ActionScheduler_ListTable.php

    r2775803 r2910869  
    253253    protected function get_recurrence( $action ) {
    254254        $schedule = $action->get_schedule();
    255         if ( $schedule->is_recurring() ) {
     255        if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) {
    256256            $recurrence = $schedule->get_recurrence();
    257257
     
    472472        }
    473473
    474         if ( ! $schedule->get_date() ) {
     474        if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) {
    475475            return '0000-00-00 00:00:00';
    476476        }
  • action-scheduler/tags/3.6.0/classes/ActionScheduler_QueueCleaner.php

    r2542151 r2910869  
    2020
    2121    /**
     22     * @var string[] Default list of statuses purged by the cleaner process.
     23     */
     24    private $default_statuses_to_purge = [
     25        ActionScheduler_Store::STATUS_COMPLETE,
     26        ActionScheduler_Store::STATUS_CANCELED,
     27    ];
     28
     29    /**
    2230     * ActionScheduler_QueueCleaner constructor.
    2331     *
     
    3038    }
    3139
     40    /**
     41     * Default queue cleaner process used by queue runner.
     42     *
     43     * @return array
     44     */
    3245    public function delete_old_actions() {
     46        /**
     47         * Filter the minimum scheduled date age for action deletion.
     48         *
     49         * @param int $retention_period Minimum scheduled age in seconds of the actions to be deleted.
     50         */
    3351        $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
    34         $cutoff = as_get_datetime_object($lifespan.' seconds ago');
    35 
    36         $statuses_to_purge = array(
    37             ActionScheduler_Store::STATUS_COMPLETE,
    38             ActionScheduler_Store::STATUS_CANCELED,
    39         );
    40 
     52
     53        try {
     54            $cutoff = as_get_datetime_object( $lifespan . ' seconds ago' );
     55        } catch ( Exception $e ) {
     56            _doing_it_wrong(
     57                __METHOD__,
     58                sprintf(
     59                    /* Translators: %s is the exception message. */
     60                    esc_html__( 'It was not possible to determine a valid cut-off time: %s.', 'action-scheduler' ),
     61                    esc_html( $e->getMessage() )
     62                ),
     63                '3.5.5'
     64            );
     65
     66            return array();
     67        }
     68
     69
     70        /**
     71         * Filter the statuses when cleaning the queue.
     72         *
     73         * @param string[] $default_statuses_to_purge Action statuses to clean.
     74         */
     75        $statuses_to_purge = (array) apply_filters( 'action_scheduler_default_cleaner_statuses', $this->default_statuses_to_purge );
     76
     77        return $this->clean_actions( $statuses_to_purge, $cutoff, $this->get_batch_size() );
     78    }
     79
     80    /**
     81     * Delete selected actions limited by status and date.
     82     *
     83     * @param string[] $statuses_to_purge List of action statuses to purge. Defaults to canceled, complete.
     84     * @param DateTime $cutoff_date Date limit for selecting actions. Defaults to 31 days ago.
     85     * @param int|null $batch_size Maximum number of actions per status to delete. Defaults to 20.
     86     * @param string $context Calling process context. Defaults to `old`.
     87     * @return array Actions deleted.
     88     */
     89    public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) {
     90        $batch_size = $batch_size !== null ? $batch_size : $this->batch_size;
     91        $cutoff     = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' );
     92        $lifespan   = time() - $cutoff->getTimestamp();
     93        if ( empty( $statuses_to_purge ) ) {
     94            $statuses_to_purge = $this->default_statuses_to_purge;
     95        }
     96
     97        $deleted_actions = [];
    4198        foreach ( $statuses_to_purge as $status ) {
    4299            $actions_to_delete = $this->store->query_actions( array(
     
    44101                'modified'         => $cutoff,
    45102                'modified_compare' => '<=',
    46                 'per_page'         => $this->get_batch_size(),
     103                'per_page'         => $batch_size,
    47104                'orderby'          => 'none',
    48105            ) );
    49106
    50             foreach ( $actions_to_delete as $action_id ) {
    51                 try {
    52                     $this->store->delete_action( $action_id );
    53                 } catch ( Exception $e ) {
    54 
    55                     /**
    56                      * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
    57                      *
    58                      * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
    59                      * actions.
    60                      *
    61                      * @since 2.0.0
    62                      *
    63                      * @param int $action_id The scheduled actions ID in the data store
    64                      * @param Exception $e The exception thrown when attempting to delete the action from the data store
    65                      * @param int $lifespan The retention period, in seconds, for old actions
    66                      * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
    67                      */
    68                     do_action( 'action_scheduler_failed_old_action_deletion', $action_id, $e, $lifespan, count( $actions_to_delete ) );
    69                 }
     107            $deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) );
     108        }
     109
     110        return $deleted_actions;
     111    }
     112
     113    /**
     114     * @param int[] $actions_to_delete List of action IDs to delete.
     115     * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted.
     116     * @param string $context Context of the delete request.
     117     * @return array Deleted action IDs.
     118     */
     119    private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) {
     120        $deleted_actions = [];
     121        if ( $lifespan === null ) {
     122            $lifespan = $this->month_in_seconds;
     123        }
     124
     125        foreach ( $actions_to_delete as $action_id ) {
     126            try {
     127                $this->store->delete_action( $action_id );
     128                $deleted_actions[] = $action_id;
     129            } catch ( Exception $e ) {
     130                /**
     131                 * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
     132                 *
     133                 * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
     134                 * actions.
     135                 *
     136                 * @param int $action_id The scheduled actions ID in the data store
     137                 * @param Exception $e The exception thrown when attempting to delete the action from the data store
     138                 * @param int $lifespan The retention period, in seconds, for old actions
     139                 * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
     140                 * @since 2.0.0
     141                 *
     142                 */
     143                do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) );
    70144            }
    71145        }
     146        return $deleted_actions;
    72147    }
    73148
  • action-scheduler/tags/3.6.0/classes/ActionScheduler_QueueRunner.php

    r2850016 r2910869  
    186186        /*
    187187         * Calling wp_cache_flush_runtime() lets us clear the runtime cache without invalidating the external object
    188          * cache, so we will always prefer this when it is available (but it was only introduced in WordPress 6.0).
     188         * cache, so we will always prefer this method (as compared to calling wp_cache_flush()) when it is available.
     189         *
     190         * However, this function was only introduced in WordPress 6.0. Additionally, the preferred way of detecting if
     191         * it is supported changed in WordPress 6.1 so we use two different methods to decide if we should utilize it.
    189192         */
    190         if ( function_exists( 'wp_cache_flush_runtime' ) ) {
     193        $flushing_runtime_cache_explicitly_supported = function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_runtime' );
     194        $flushing_runtime_cache_implicitly_supported = ! function_exists( 'wp_cache_supports' ) && function_exists( 'wp_cache_flush_runtime' );
     195
     196        if ( $flushing_runtime_cache_explicitly_supported || $flushing_runtime_cache_implicitly_supported ) {
    191197            wp_cache_flush_runtime();
    192198        } elseif (
  • action-scheduler/tags/3.6.0/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php

    r2340383 r2910869  
    9191        $this->progress_bar = new ProgressBar(
    9292            /* translators: %d: amount of actions */
    93             sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), number_format_i18n( $count ) ),
     93            sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), $count ),
    9494            $count
    9595        );
  • action-scheduler/tags/3.6.0/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php

    r2729833 r2910869  
    5656     * : Only run actions from the specified group. Omitting this option runs actions from all groups.
    5757     *
     58     * [--exclude-groups=<groups>]
     59     * : Run actions from all groups except the specified group(s). Define multiple groups as a comma separated string (without spaces), e.g. '--group_a,group_b'. This option is ignored when `--group` is used.
     60     *
    5861     * [--free-memory-on=<count>]
    5962     * : The number of actions to process between freeing memory. 0 disables freeing memory. Default 50.
     
    7376    public function run( $args, $assoc_args ) {
    7477        // Handle passed arguments.
    75         $batch   = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) );
    76         $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
    77         $clean   = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) );
    78         $hooks   = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) );
    79         $hooks   = array_filter( array_map( 'trim', $hooks ) );
    80         $group   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' );
    81         $free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 );
    82         $sleep   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
    83         $force   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false );
     78        $batch          = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) );
     79        $batches        = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
     80        $clean          = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) );
     81        $hooks          = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) );
     82        $hooks          = array_filter( array_map( 'trim', $hooks ) );
     83        $group          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' );
     84        $exclude_groups = \WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude-groups', '' );
     85        $free_on        = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 );
     86        $sleep          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
     87        $force          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false );
    8488
    8589        ActionScheduler_DataController::set_free_ticks( $free_on );
     
    8993        $actions_completed = 0;
    9094        $unlimited         = $batches === 0;
     95        if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) {
     96            $exclude_groups = $this->parse_comma_separated_string( $exclude_groups );
     97
     98            if ( ! empty( $exclude_groups ) ) {
     99                ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups );
     100            }
     101        }
    91102
    92103        try {
     
    118129
    119130    /**
     131     * Converts a string of comma-separated values into an array of those same values.
     132     *
     133     * @param string $string The string of one or more comma separated values.
     134     *
     135     * @return array
     136     */
     137    private function parse_comma_separated_string( $string ): array {
     138        return array_filter( str_getcsv( $string ) );
     139    }
     140
     141    /**
    120142     * Print WP CLI message about how many actions are about to be processed.
    121143     *
     
    127149        WP_CLI::log(
    128150            sprintf(
    129                 /* translators: %d refers to how many scheduled taks were found to run */
     151                /* translators: %d refers to how many scheduled tasks were found to run */
    130152                _n( 'Found %d scheduled task', 'Found %d scheduled tasks', $total, 'action-scheduler' ),
    131                 number_format_i18n( $total )
     153                $total
    132154            )
    133155        );
     
    146168                /* translators: %d refers to the total number of batches executed */
    147169                _n( '%d batch executed.', '%d batches executed.', $batches_completed, 'action-scheduler' ),
    148                 number_format_i18n( $batches_completed )
     170                $batches_completed
    149171            )
    150172        );
     
    180202        WP_CLI::success(
    181203            sprintf(
    182                 /* translators: %d refers to the total number of taskes completed */
     204                /* translators: %d refers to the total number of tasks completed */
    183205                _n( '%d scheduled task completed.', '%d scheduled tasks completed.', $actions_completed, 'action-scheduler' ),
    184                 number_format_i18n( $actions_completed )
     206                $actions_completed
    185207            )
    186208        );
  • action-scheduler/tags/3.6.0/classes/abstracts/ActionScheduler.php

    r2729833 r2910869  
    154154            add_action( 'init', array( $logger, 'init' ), 1, 0 );
    155155            add_action( 'init', array( $runner, 'init' ), 1, 0 );
     156
     157            add_action(
     158                'init',
     159                /**
     160                 * Runs after the active store's init() method has been called.
     161                 *
     162                 * It would probably be preferable to have $store->init() (or it's parent method) set this itself,
     163                 * once it has initialized, however that would cause problems in cases where a custom data store is in
     164                 * use and it has not yet been updated to follow that same logic.
     165                 */
     166                function () {
     167                    self::$data_store_initialized = true;
     168
     169                    /**
     170                     * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
     171                     *
     172                     * @since 3.5.5
     173                     */
     174                    do_action( 'action_scheduler_init' );
     175                },
     176                1
     177            );
    156178        } else {
    157179            $admin_view->init();
     
    159181            $logger->init();
    160182            $runner->init();
     183            self::$data_store_initialized = true;
     184
     185            /**
     186             * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
     187             *
     188             * @since 3.5.5
     189             */
     190            do_action( 'action_scheduler_init' );
    161191        }
    162192
     
    167197        if ( defined( 'WP_CLI' ) && WP_CLI ) {
    168198            WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' );
     199            WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' );
    169200            if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) {
    170201                $command = new Migration_Command();
     
    172203            }
    173204        }
    174 
    175         self::$data_store_initialized = true;
    176205
    177206        /**
     
    193222    public static function is_initialized( $function_name = null ) {
    194223        if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
    195             $message = sprintf( __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), esc_attr( $function_name ) );
    196             error_log( $message, E_WARNING );
     224            $message = sprintf(
     225                /* translators: %s function name. */
     226                __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ),
     227                esc_attr( $function_name )
     228            );
     229            error_log( $message );
    197230        }
    198231
  • action-scheduler/tags/3.6.0/classes/abstracts/ActionScheduler_Abstract_ListTable.php

    r2775803 r2910869  
    674674        // Helper to set 'all' filter when not set on status counts passed in.
    675675        if ( ! isset( $this->status_counts['all'] ) ) {
    676             $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
    677         }
    678 
    679         foreach ( $this->status_counts as $status_name => $count ) {
     676            $all_count = array_sum( $this->status_counts );
     677            if ( isset( $this->status_counts['past-due'] ) ) {
     678                $all_count -= $this->status_counts['past-due'];
     679            }
     680            $this->status_counts = array( 'all' => $all_count ) + $this->status_counts;
     681        }
     682
     683        // Translated status labels.
     684        $status_labels             = ActionScheduler_Store::instance()->get_status_labels();
     685        $status_labels['all']      = _x( 'All', 'status labels', 'action-scheduler' );
     686        $status_labels['past-due'] = _x( 'Past-due', 'status labels', 'action-scheduler' );
     687
     688        foreach ( $this->status_counts as $status_slug => $count ) {
    680689
    681690            if ( 0 === $count ) {
     
    683692            }
    684693
    685             if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
     694            if ( $status_slug === $request_status || ( empty( $request_status ) && 'all' === $status_slug ) ) {
    686695                $status_list_item = '<li class="%1$s"><a href="%2$s" class="current">%3$s</a> (%4$d)</li>';
    687696            } else {
     
    689698            }
    690699
    691             $status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
     700            $status_name         = isset( $status_labels[ $status_slug ] ) ? $status_labels[ $status_slug ] : ucfirst( $status_slug );
     701            $status_filter_url   = ( 'all' === $status_slug ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_slug );
    692702            $status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
    693             $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
     703            $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_slug ), esc_url( $status_filter_url ), esc_html( $status_name ), absint( $count ) );
    694704        }
    695705
  • action-scheduler/tags/3.6.0/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

    r2850016 r2910869  
    4949     */
    5050    public function process_action( $action_id, $context = '' ) {
     51        // Temporarily override the error handler while we process the current action.
     52        set_error_handler(
     53            /**
     54             * Temporary error handler which can catch errors and convert them into exceptions. This faciliates more
     55             * robust error handling across all supported PHP versions.
     56             *
     57             * @throws Exception
     58             *
     59             * @param int    $type    Error level expressed as an integer.
     60             * @param string $message Error message.
     61             */
     62            function ( $type, $message ) {
     63                throw new Exception( $message );
     64            },
     65            E_USER_ERROR | E_RECOVERABLE_ERROR
     66        );
     67
     68        /*
     69         * The nested try/catch structure is required because we potentially need to convert thrown errors into
     70         * exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same*
     71         * structure).
     72         */
    5173        try {
    52             $valid_action = false;
    53             do_action( 'action_scheduler_before_execute', $action_id, $context );
    54 
    55             if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
    56                 do_action( 'action_scheduler_execution_ignored', $action_id, $context );
    57                 return;
     74            try {
     75                $valid_action = false;
     76                do_action( 'action_scheduler_before_execute', $action_id, $context );
     77
     78                if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
     79                    do_action( 'action_scheduler_execution_ignored', $action_id, $context );
     80                    return;
     81                }
     82
     83                $valid_action = true;
     84                do_action( 'action_scheduler_begin_execute', $action_id, $context );
     85
     86                $action = $this->store->fetch_action( $action_id );
     87                $this->store->log_execution( $action_id );
     88                $action->execute();
     89                do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
     90                $this->store->mark_complete( $action_id );
     91            } catch ( Throwable $e ) {
     92                // Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for
     93                // compatibility with ActionScheduler_Logger.
     94                throw new Exception( $e->getMessage(), $e->getCode(), $e->getPrevious() );
    5895            }
    59 
    60             $valid_action = true;
    61             do_action( 'action_scheduler_begin_execute', $action_id, $context );
    62 
    63             $action = $this->store->fetch_action( $action_id );
    64             $this->store->log_execution( $action_id );
    65             $action->execute();
    66             do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
    67             $this->store->mark_complete( $action_id );
    6896        } catch ( Exception $e ) {
    69             if ( $valid_action ) {
    70                 $this->store->mark_failure( $action_id );
    71                 do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
    72             } else {
    73                 do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
    74             }
     97            // This catch block exists for compatibility with PHP 5.6.
     98            $this->handle_action_error( $action_id, $e, $context, $valid_action );
     99        } finally {
     100            restore_error_handler();
    75101        }
    76102
    77103        if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
    78104            $this->schedule_next_instance( $action, $action_id );
     105        }
     106    }
     107
     108    /**
     109     * Marks actions as either having failed execution or failed validation, as appropriate.
     110     *
     111     * @param int       $action_id    Action ID.
     112     * @param Exception $e            Exception instance.
     113     * @param string    $context      Execution context.
     114     * @param bool      $valid_action If the action is valid.
     115     *
     116     * @return void
     117     */
     118    private function handle_action_error( $action_id, $e, $context, $valid_action ) {
     119        if ( $valid_action ) {
     120            $this->store->mark_failure( $action_id );
     121            /**
     122             * Runs when action execution fails.
     123             *
     124             * @param int       $action_id Action ID.
     125             * @param Exception $e         Exception instance.
     126             * @param string    $context   Execution context.
     127             */
     128            do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
     129        } else {
     130            /**
     131             * Runs when action validation fails.
     132             *
     133             * @param int       $action_id Action ID.
     134             * @param Exception $e         Exception instance.
     135             * @param string    $context   Execution context.
     136             */
     137            do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
    79138        }
    80139    }
     
    144203        }
    145204
    146         // Now let's fetch the first action (having the same hook) of *any status*ithin the same window.
     205        // Now let's fetch the first action (having the same hook) of *any status* within the same window.
    147206        unset( $query_args['status'] );
    148207        $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args );
    149208
    150         // If the IDs match, then actions for this hook must be consistently failing.
    151         return $first_action_id_with_the_same_hook === $first_failing_action_id;
     209        /**
     210         * If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a
     211         * way to observe and optionally override that assessment.
     212         *
     213         * @param bool                   $is_consistently_failing If the action is considered to be consistently failing.
     214         * @param ActionScheduler_Action $action                  The action being assessed.
     215         */
     216        return (bool) apply_filters(
     217            'action_scheduler_recurring_action_is_consistently_failing',
     218            $first_action_id_with_the_same_hook === $first_failing_action_id,
     219            $action
     220        );
    152221    }
    153222
  • action-scheduler/tags/3.6.0/classes/actions/ActionScheduler_Action.php

    r2775803 r2910869  
    1010    protected $schedule = NULL;
    1111    protected $group = '';
     12
     13    /**
     14     * Priorities are conceptually similar to those used for regular WordPress actions.
     15     * Like those, a lower priority takes precedence over a higher priority and the default
     16     * is 10.
     17     *
     18     * Unlike regular WordPress actions, the priority of a scheduled action is strictly an
     19     * integer and should be kept within the bounds 0-255 (anything outside the bounds will
     20     * be brought back into the acceptable range).
     21     *
     22     * @var int
     23     */
     24    protected $priority = 10;
    1225
    1326    public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) {
     
    94107        return FALSE;
    95108    }
     109
     110    /**
     111     * Sets the priority of the action.
     112     *
     113     * @param int $priority Priority level (lower is higher priority). Should be in the range 0-255.
     114     *
     115     * @return void
     116     */
     117    public function set_priority( $priority ) {
     118        if ( $priority < 0 ) {
     119            $priority = 0;
     120        } elseif ( $priority > 255 ) {
     121            $priority = 255;
     122        }
     123
     124        $this->priority = (int) $priority;
     125    }
     126
     127    /**
     128     * Gets the action priority.
     129     *
     130     * @return int
     131     */
     132    public function get_priority() {
     133        return $this->priority;
     134    }
    96135}
  • action-scheduler/tags/3.6.0/classes/data-stores/ActionScheduler_DBStore.php

    r2850016 r2910869  
    2626    protected static $max_index_length = 191;
    2727
     28    /** @var array List of claim filters. */
     29    protected $claim_filters = [
     30        'group'          => '',
     31        'hooks'          => '',
     32        'exclude-groups' => '',
     33    ];
     34
    2835    /**
    2936     * Initialize the data store
     
    8592                'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
    8693                'schedule'             => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
    87                 'group_id'             => $this->get_group_id( $action->get_group() ),
     94                'group_id'             => current( $this->get_group_ids( $action->get_group() ) ),
     95                'priority'             => $action->get_priority(),
    8896            );
    8997
     
    173181        );
    174182        $pending_status_placeholders = implode( ', ', array_fill( 0, count( $pending_statuses ), '%s' ) );
     183
    175184        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $pending_status_placeholders is hardcoded.
    176185        $where_clause = $wpdb->prepare(
     
    243252     * Get a group's ID based on its name/slug.
    244253     *
    245      * @param string $slug The string name of a group.
    246      * @param bool   $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
    247      *
    248      * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created.
    249      */
    250     protected function get_group_id( $slug, $create_if_not_exists = true ) {
    251         if ( empty( $slug ) ) {
    252             return 0;
    253         }
    254         /** @var \wpdb $wpdb */
    255         global $wpdb;
    256         $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
    257         if ( empty( $group_id ) && $create_if_not_exists ) {
    258             $group_id = $this->create_group( $slug );
    259         }
    260 
    261         return $group_id;
     254     * @param string|array $slugs                The string name of a group, or names for several groups.
     255     * @param bool         $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
     256     *
     257     * @return array The group IDs, if they exist or were successfully created. May be empty.
     258     */
     259    protected function get_group_ids( $slugs, $create_if_not_exists = true ) {
     260        $slugs     = (array) $slugs;
     261        $group_ids = array();
     262
     263        if ( empty( $slugs ) ) {
     264            return array();
     265        }
     266
     267        /** @var \wpdb $wpdb */
     268        global $wpdb;
     269
     270        foreach ( $slugs as $slug ) {
     271            $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
     272
     273            if ( empty( $group_id ) && $create_if_not_exists ) {
     274                $group_id = $this->create_group( $slug );
     275            }
     276
     277            if ( $group_id ) {
     278                $group_ids[] = $group_id;
     279            }
     280        }
     281
     282        return $group_ids;
    262283    }
    263284
     
    356377        $group = $data->group ? $data->group : '';
    357378
    358         return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
     379        return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group, $data->priority );
    359380    }
    360381
     
    798819
    799820    /**
     821     * Set a claim filter.
     822     *
     823     * @param string $filter_name Claim filter name.
     824     * @param mixed $filter_values Values to filter.
     825     * @return void
     826     */
     827    public function set_claim_filter( $filter_name, $filter_values ) {
     828        if ( isset( $this->claim_filters[ $filter_name ] ) ) {
     829            $this->claim_filters[ $filter_name ] = $filter_values;
     830        }
     831    }
     832
     833    /**
     834     * Get the claim filter value.
     835     *
     836     * @param string $filter_name Claim filter name.
     837     * @return mixed
     838     */
     839    public function get_claim_filter( $filter_name ) {
     840        if ( isset( $this->claim_filters[ $filter_name ] ) ) {
     841            return $this->claim_filters[ $filter_name ];
     842        }
     843
     844        return '';
     845    }
     846
     847    /**
    800848     * Mark actions claimed.
    801849     *
     
    814862        global $wpdb;
    815863
    816         $now  = as_get_datetime_object();
    817         $date = is_null( $before_date ) ? $now : clone $before_date;
    818 
     864        $now    = as_get_datetime_object();
     865        $date   = is_null( $before_date ) ? $now : clone $before_date;
    819866        // can't use $wpdb->update() because of the <= condition.
    820867        $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
     
    825872        );
    826873
     874        // Set claim filters.
     875        if ( ! empty( $hooks ) ) {
     876            $this->set_claim_filter( 'hooks', $hooks );
     877        } else {
     878            $hooks = $this->get_claim_filter( 'hooks' );
     879        }
     880        if ( ! empty( $group ) ) {
     881            $this->set_claim_filter( 'group', $group );
     882        } else {
     883            $group = $this->get_claim_filter( 'group' );
     884        }
     885
    827886        $where    = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s';
    828887        $params[] = $date->format( 'Y-m-d H:i:s' );
     
    835894        }
    836895
     896        $group_operator = 'IN';
     897        if ( empty( $group ) ) {
     898            $group = $this->get_claim_filter( 'exclude-groups' );
     899            $group_operator = 'NOT IN';
     900        }
     901
    837902        if ( ! empty( $group ) ) {
    838 
    839             $group_id = $this->get_group_id( $group, false );
    840 
    841             // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour.
    842             if ( empty( $group_id ) ) {
    843                 /* translators: %s: group name */
    844                 throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
    845             }
    846 
    847             $where    .= ' AND group_id = %d';
    848             $params[] = $group_id;
     903            $group_ids = $this->get_group_ids( $group, false );
     904
     905            // throw exception if no matching group(s) found, this matches ActionScheduler_wpPostStore's behaviour.
     906            if ( empty( $group_ids ) ) {
     907                throw new InvalidArgumentException(
     908                    sprintf(
     909                        /* translators: %s: group name(s) */
     910                        _n(
     911                            'The group "%s" does not exist.',
     912                            'The groups "%s" do not exist.',
     913                            is_array( $group ) ? count( $group ) : 1,
     914                            'action-scheduler'
     915                        ),
     916                        $group
     917                    )
     918                );
     919            }
     920
     921            $id_list = implode( ',', array_map( 'intval', $group_ids ) );
     922            $where   .= " AND group_id {$group_operator} ( $id_list )";
    849923        }
    850924
     
    856930         * @param string $order_by_sql
    857931         */
    858         $order    = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
     932        $order    = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
    859933        $params[] = $limit;
    860934
     
    913987
    914988        $sql = $wpdb->prepare(
    915             "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d",
     989            "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d ORDER BY priority ASC",
    916990            $claim_id
    917991        );
     
    10061080     * Add execution message to action log.
    10071081     *
     1082     * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress').
     1083     *
    10081084     * @param int $action_id Action ID.
    10091085     *
     
    10161092        $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
    10171093        $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    1018         $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     1094
     1095        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     1096        $status_updated = $wpdb->query( $sql );
     1097
     1098        if ( ! $status_updated ) {
     1099            throw new Exception(
     1100                sprintf(
     1101                    /* translators: 1: action ID. 2: status slug. */
     1102                    __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
     1103                    $action_id,
     1104                    self::STATUS_RUNNING
     1105                )
     1106            );
     1107        }
    10191108    }
    10201109
  • action-scheduler/tags/3.6.0/classes/data-stores/ActionScheduler_wpPostStore.php

    r2775803 r2910869  
    937937     * Log Execution.
    938938     *
     939     * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress').
     940     *
    939941     * @param string $action_id Action ID.
    940942     */
     
    948950
    949951        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    950         $wpdb->query(
     952        $status_updated = $wpdb->query(
    951953            $wpdb->prepare(
    952954                "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s",
     
    958960            )
    959961        );
     962
     963        if ( ! $status_updated ) {
     964            throw new Exception(
     965                sprintf(
     966                    /* translators: 1: action ID. 2: status slug. */
     967                    __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
     968                    $action_id,
     969                    self::STATUS_RUNNING
     970                )
     971            );
     972        }
    960973    }
    961974
  • action-scheduler/tags/3.6.0/classes/migration/Runner.php

    r2340383 r2910869  
    8080        if ( $this->progress_bar ) {
    8181            /* translators: %d: amount of actions */
    82             $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), number_format_i18n( $batch_size ) ) );
     82            $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), $batch_size ) );
    8383            $this->progress_bar->set_count( $batch_size );
    8484        }
  • action-scheduler/tags/3.6.0/classes/schema/ActionScheduler_StoreSchema.php

    r2850016 r2910869  
    1717     * @var int Increment this value to trigger a schema update.
    1818     */
    19     protected $schema_version = 6;
     19    protected $schema_version = 7;
    2020
    2121    public function __construct() {
     
    5050                        scheduled_date_gmt datetime NULL default '{$default_date}',
    5151                        scheduled_date_local datetime NULL default '{$default_date}',
     52                        priority tinyint unsigned NOT NULL default '10',
    5253                        args varchar($max_index_length),
    5354                        schedule longtext,
  • action-scheduler/tags/3.6.0/functions.php

    r2850016 r2910869  
    1313 * @param string $group The group to assign this job to.
    1414 * @param bool   $unique Whether the action should be unique.
     15 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    1516 *
    1617 * @return int The action ID.
    1718 */
    18 function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false ) {
     19function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    1920    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    2021        return 0;
     
    3435     * @param array    $args       Action arguments.
    3536     * @param string   $group      Action group.
     37     * @param int      $priority   Action priority.
    3638     */
    37     $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group );
     39    $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority );
    3840    if ( null !== $pre ) {
    3941        return is_int( $pre ) ? $pre : 0;
    4042    }
    4143
    42     return ActionScheduler::factory()->async_unique( $hook, $args, $group, $unique );
     44    return ActionScheduler::factory()->create(
     45        array(
     46            'type'      => 'async',
     47            'hook'      => $hook,
     48            'arguments' => $args,
     49            'group'     => $group,
     50            'unique'    => $unique,
     51            'priority'  => $priority,
     52        )
     53    );
    4354}
    4455
     
    5162 * @param string $group The group to assign this job to.
    5263 * @param bool   $unique Whether the action should be unique.
     64 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    5365 *
    5466 * @return int The action ID.
    5567 */
    56 function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false ) {
     68function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    5769    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    5870        return 0;
     
    7385     * @param array    $args       Action arguments.
    7486     * @param string   $group      Action group.
     87     * @param int      $priorities Action priority.
    7588     */
    76     $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group );
     89    $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority );
    7790    if ( null !== $pre ) {
    7891        return is_int( $pre ) ? $pre : 0;
    7992    }
    8093
    81     return ActionScheduler::factory()->single_unique( $hook, $args, $timestamp, $group, $unique );
     94    return ActionScheduler::factory()->create(
     95        array(
     96            'type'      => 'single',
     97            'hook'      => $hook,
     98            'arguments' => $args,
     99            'when'      => $timestamp,
     100            'group'     => $group,
     101            'unique'    => $unique,
     102            'priority'  => $priority,
     103        )
     104    );
    82105}
    83106
     
    91114 * @param string $group The group to assign this job to.
    92115 * @param bool   $unique Whether the action should be unique.
     116 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    93117 *
    94118 * @return int The action ID.
    95119 */
    96 function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false ) {
    97     if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
     120function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
     121    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
     122        return 0;
     123    }
     124
     125    $interval = (int) $interval_in_seconds;
     126
     127    // We expect an integer and allow it to be passed using float and string types, but otherwise
     128    // should reject unexpected values.
     129    if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) {
     130        _doing_it_wrong(
     131            __METHOD__,
     132            sprintf(
     133                /* translators: 1: provided value 2: provided type. */
     134                esc_html__( 'An integer was expected but "%1$s" (%2$s) was received.', 'action-scheduler' ),
     135                esc_html( $interval_in_seconds ),
     136                esc_html( gettype( $interval_in_seconds ) )
     137            ),
     138            '3.6.0'
     139        );
     140
    98141        return 0;
    99142    }
     
    114157     * @param array    $args                Action arguments.
    115158     * @param string   $group               Action group.
     159     * @param int      $priority            Action priority.
    116160     */
    117     $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group );
     161    $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority );
    118162    if ( null !== $pre ) {
    119163        return is_int( $pre ) ? $pre : 0;
    120164    }
    121165
    122     return ActionScheduler::factory()->recurring_unique( $hook, $args, $timestamp, $interval_in_seconds, $group, $unique );
     166    return ActionScheduler::factory()->create(
     167        array(
     168            'type'      => 'recurring',
     169            'hook'      => $hook,
     170            'arguments' => $args,
     171            'when'      => $timestamp,
     172            'pattern'   => $interval_in_seconds,
     173            'group'     => $group,
     174            'unique'    => $unique,
     175            'priority'  => $priority,
     176        )
     177    );
    123178}
    124179
     
    144199 * @param string $group The group to assign this job to.
    145200 * @param bool   $unique Whether the action should be unique.
     201 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    146202 *
    147203 * @return int The action ID.
    148204 */
    149 function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false ) {
     205function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    150206    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    151207        return 0;
     
    167223     * @param array    $args       Action arguments.
    168224     * @param string   $group      Action group.
     225     * @param int      $priority   Action priority.
    169226     */
    170     $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group );
     227    $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority );
    171228    if ( null !== $pre ) {
    172229        return is_int( $pre ) ? $pre : 0;
    173230    }
    174231
    175     return ActionScheduler::factory()->cron_unique( $hook, $args, $timestamp, $schedule, $group, $unique );
     232    return ActionScheduler::factory()->create(
     233        array(
     234            'type'      => 'cron',
     235            'hook'      => $hook,
     236            'arguments' => $args,
     237            'when'      => $timestamp,
     238            'pattern'   => $schedule,
     239            'group'     => $group,
     240            'unique'    => $unique,
     241            'priority'  => $priority,
     242        )
     243    );
    176244}
    177245
  • action-scheduler/tags/3.6.0/readme.txt

    r2850016 r2910869  
    44Requires at least: 5.2
    55Tested up to: 6.0
    6 Stable tag: 3.5.4
     6Stable tag: 3.6.0
    77License: GPLv3
    88Requires PHP: 5.6
     
    4747
    4848== Changelog ==
     49
     50= 3.6.0 - 2023-05-10 =
     51* Add $unique parameter to function signatures.
     52* Add a cast-to-int for extra safety before forming new DateTime object.
     53* Add a hook allowing exceptions for consistently failing recurring actions.
     54* Add action priorities.
     55* Add init hook.
     56* Always raise the time limit.
     57* Bump minimatch from 3.0.4 to 3.0.8.
     58* Bump yaml from 2.2.1 to 2.2.2.
     59* Defensive coding relating to gaps in declared schedule types.
     60* Do not process an action if it cannot be set to `in-progress`.
     61* Filter view labels (status names) should be translatable | #919.
     62* Fix WPCLI progress messages.
     63* Improve data-store initialization flow.
     64* Improve error handling across all supported PHP versions.
     65* Improve logic for flushing the runtime cache.
     66* Support exclusion of multiple groups.
     67* Update lint-staged and Node/NPM requirements.
     68* add CLI clean command.
     69* add CLI exclude-group filter.
     70* exclude past-due from list table all filter count.
     71* throwing an exception if as_schedule_recurring_action interval param is not of type integer.
    4972
    5073= 3.5.4 - 2023-01-17 =
  • action-scheduler/trunk/action-scheduler.php

    r2850016 r2910869  
    66 * Author: Automattic
    77 * Author URI: https://automattic.com/
    8  * Version: 3.5.4
     8 * Version: 3.6.0
    99 * License: GPLv3
    1010 *
     
    2727 */
    2828
    29 if ( ! function_exists( 'action_scheduler_register_3_dot_5_dot_4' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION.
     29if ( ! function_exists( 'action_scheduler_register_3_dot_6_dot_0' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION.
    3030
    3131    if ( ! class_exists( 'ActionScheduler_Versions', false ) ) {
     
    3434    }
    3535
    36     add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_5_dot_4', 0, 0 ); // WRCS: DEFINED_VERSION.
     36    add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_6_dot_0', 0, 0 ); // WRCS: DEFINED_VERSION.
    3737
    3838    /**
    3939     * Registers this version of Action Scheduler.
    4040     */
    41     function action_scheduler_register_3_dot_5_dot_4() { // WRCS: DEFINED_VERSION.
     41    function action_scheduler_register_3_dot_6_dot_0() { // WRCS: DEFINED_VERSION.
    4242        $versions = ActionScheduler_Versions::instance();
    43         $versions->register( '3.5.4', 'action_scheduler_initialize_3_dot_5_dot_4' ); // WRCS: DEFINED_VERSION.
     43        $versions->register( '3.6.0', 'action_scheduler_initialize_3_dot_6_dot_0' ); // WRCS: DEFINED_VERSION.
    4444    }
    4545
     
    4747     * Initializes this version of Action Scheduler.
    4848     */
    49     function action_scheduler_initialize_3_dot_5_dot_4() { // WRCS: DEFINED_VERSION.
     49    function action_scheduler_initialize_3_dot_6_dot_0() { // WRCS: DEFINED_VERSION.
    5050        // A final safety check is required even here, because historic versions of Action Scheduler
    5151        // followed a different pattern (in some unusual cases, we could reach this point and the
     
    5959    // Support usage in themes - load this version if no plugin has loaded a version yet.
    6060    if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) {
    61         action_scheduler_initialize_3_dot_5_dot_4(); // WRCS: DEFINED_VERSION.
     61        action_scheduler_initialize_3_dot_6_dot_0(); // WRCS: DEFINED_VERSION.
    6262        do_action( 'action_scheduler_pre_theme_init' );
    6363        ActionScheduler_Versions::initialize_latest_version();
  • action-scheduler/trunk/changelog.txt

    r2850016 r2910869  
    11*** Changelog ***
     2
     3= 3.6.0 - 2023-05-10 =
     4* Add $unique parameter to function signatures.
     5* Add a cast-to-int for extra safety before forming new DateTime object.
     6* Add a hook allowing exceptions for consistently failing recurring actions.
     7* Add action priorities.
     8* Add init hook.
     9* Always raise the time limit.
     10* Bump minimatch from 3.0.4 to 3.0.8.
     11* Bump yaml from 2.2.1 to 2.2.2.
     12* Defensive coding relating to gaps in declared schedule types.
     13* Do not process an action if it cannot be set to `in-progress`.
     14* Filter view labels (status names) should be translatable | #919.
     15* Fix WPCLI progress messages.
     16* Improve data-store initialization flow.
     17* Improve error handling across all supported PHP versions.
     18* Improve logic for flushing the runtime cache.
     19* Support exclusion of multiple groups.
     20* Update lint-staged and Node/NPM requirements.
     21* add CLI clean command.
     22* add CLI exclude-group filter.
     23* exclude past-due from list table all filter count.
     24* throwing an exception if as_schedule_recurring_action interval param is not of type integer.
    225
    326= 3.5.4 - 2023-01-17 =
  • action-scheduler/trunk/classes/ActionScheduler_ActionFactory.php

    r2850016 r2910869  
    1414     * @param ActionScheduler_Schedule $schedule The action's schedule.
    1515     * @param string                   $group A group to put the action in.
     16     * @param int                      $priority The action priority.
    1617     *
    1718     * @return ActionScheduler_Action An instance of the stored action.
    1819     */
    1920    public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
     21        // The 6th parameter ($priority) is not formally declared in the method signature to maintain compatibility with
     22        // third-party subclasses created before this param was added.
     23        $priority = func_num_args() >= 6 ? (int) func_get_arg( 5 ) : 10;
    2024
    2125        switch ( $status ) {
     
    3741
    3842        $action = new $action_class( $hook, $args, $schedule, $group );
     43        $action->set_priority( $priority );
    3944
    4045        /**
    4146         * Allow 3rd party code to change the instantiated action for a given hook, args, schedule and group.
    4247         *
    43          * @param ActionScheduler_Action $action The instantiated action.
    44          * @param string $hook The instantiated action's hook.
    45          * @param array $args The instantiated action's args.
     48         * @param ActionScheduler_Action   $action The instantiated action.
     49         * @param string                   $hook The instantiated action's hook.
     50         * @param array                    $args The instantiated action's args.
    4651         * @param ActionScheduler_Schedule $schedule The instantiated action's schedule.
    47          * @param string $group The instantiated action's group.
     52         * @param string                   $group The instantiated action's group.
     53         * @param int                      $priority The action priority.
    4854         */
    49         return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group );
     55        return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group, $priority );
    5056    }
    5157
     
    230236        $new_schedule   = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() );
    231237        $new_action     = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() );
     238        $new_action->set_priority( $action->get_priority() );
    232239        return $this->store( $new_action );
     240    }
     241
     242    /**
     243     * Creates a scheduled action.
     244     *
     245     * This general purpose method can be used in place of specific methods such as async(),
     246     * async_unique(), single() or single_unique(), etc.
     247     *
     248     * @internal Not intended for public use, should not be overriden by subclasses.
     249     * @throws   Exception May be thrown if invalid options are passed.
     250     *
     251     * @param array $options {
     252     *     Describes the action we wish to schedule.
     253     *
     254     *     @type string     $type      Must be one of 'async', 'cron', 'recurring', or 'single'.
     255     *     @type string     $hook      The hook to be executed.
     256     *     @type array      $arguments Arguments to be passed to the callback.
     257     *     @type string     $group     The action group.
     258     *     @type bool       $unique    If the action should be unique.
     259     *     @type int        $when      Timestamp. Indicates when the action, or first instance of the action in the case
     260     *                                 of recurring or cron actions, becomes due.
     261     *     @type int|string $pattern   Recurrence pattern. This is either an interval in seconds for recurring actions
     262     *                                 or a cron expression for cron actions.
     263     *     @type int        $priority  Lower values means higher priority. Should be in the range 0-255.
     264     * }
     265     *
     266     * @return int
     267     */
     268    public function create( array $options = array() ) {
     269        $defaults = array(
     270            'type'      => 'single',
     271            'hook'      => '',
     272            'arguments' => array(),
     273            'group'     => '',
     274            'unique'    => false,
     275            'when'      => time(),
     276            'pattern'   => null,
     277            'priority'  => 10,
     278        );
     279
     280        $options = array_merge( $defaults, $options );
     281
     282        // Cron/recurring actions without a pattern are treated as single actions (this gives calling code the ability
     283        // to use functions like as_schedule_recurring_action() to schedule recurring as well as single actions).
     284        if ( ( 'cron' === $options['type'] || 'recurring' === $options['type'] ) && empty( $options['pattern'] ) ) {
     285            $options['type'] = 'single';
     286        }
     287
     288        switch ( $options['type'] ) {
     289            case 'async':
     290                $schedule = new ActionScheduler_NullSchedule();
     291                break;
     292
     293            case 'cron':
     294                $date     = as_get_datetime_object( $options['when'] );
     295                $cron     = CronExpression::factory( $options['pattern'] );
     296                $schedule = new ActionScheduler_CronSchedule( $date, $cron );
     297                break;
     298
     299            case 'recurring':
     300                $date     = as_get_datetime_object( $options['when'] );
     301                $schedule = new ActionScheduler_IntervalSchedule( $date, $options['pattern'] );
     302                break;
     303
     304            case 'single':
     305                $date     = as_get_datetime_object( $options['when'] );
     306                $schedule = new ActionScheduler_SimpleSchedule( $date );
     307                break;
     308
     309            default:
     310                throw new Exception( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." );
     311        }
     312
     313        $action = new ActionScheduler_Action( $options['hook'], $options['arguments'], $schedule, $options['group'] );
     314        $action->set_priority( $options['priority'] );
     315        return $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action );
    233316    }
    234317
  • action-scheduler/trunk/classes/ActionScheduler_Compatibility.php

    r2622865 r2910869  
    55 */
    66class ActionScheduler_Compatibility {
    7 
    87    /**
    98     * Converts a shorthand byte value to an integer byte value.
     
    9089        $max_execution_time = (int) ini_get( 'max_execution_time' );
    9190
    92         /*
    93          * If the max execution time is already unlimited (zero), or if it exceeds or is equal to the proposed
    94          * limit, there is no reason for us to make further changes (we never want to lower it).
    95          */
    96         if (
    97             0 === $max_execution_time
    98             || ( $max_execution_time >= $limit && $limit !== 0 )
    99         ) {
     91        // If the max execution time is already set to zero (unlimited), there is no reason to make a further change.
     92        if ( 0 === $max_execution_time ) {
    10093            return;
    10194        }
    10295
     96        // Whichever of $max_execution_time or $limit is higher is the amount by which we raise the time limit.
     97        $raise_by = 0 === $limit || $limit > $max_execution_time ? $limit : $max_execution_time;
     98
    10399        if ( function_exists( 'wc_set_time_limit' ) ) {
    104             wc_set_time_limit( $limit );
     100            wc_set_time_limit( $raise_by );
    105101        } elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
    106             @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     102            @set_time_limit( $raise_by ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    107103        }
    108104    }
  • action-scheduler/trunk/classes/ActionScheduler_ListTable.php

    r2775803 r2910869  
    253253    protected function get_recurrence( $action ) {
    254254        $schedule = $action->get_schedule();
    255         if ( $schedule->is_recurring() ) {
     255        if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) {
    256256            $recurrence = $schedule->get_recurrence();
    257257
     
    472472        }
    473473
    474         if ( ! $schedule->get_date() ) {
     474        if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) {
    475475            return '0000-00-00 00:00:00';
    476476        }
  • action-scheduler/trunk/classes/ActionScheduler_QueueCleaner.php

    r2542151 r2910869  
    2020
    2121    /**
     22     * @var string[] Default list of statuses purged by the cleaner process.
     23     */
     24    private $default_statuses_to_purge = [
     25        ActionScheduler_Store::STATUS_COMPLETE,
     26        ActionScheduler_Store::STATUS_CANCELED,
     27    ];
     28
     29    /**
    2230     * ActionScheduler_QueueCleaner constructor.
    2331     *
     
    3038    }
    3139
     40    /**
     41     * Default queue cleaner process used by queue runner.
     42     *
     43     * @return array
     44     */
    3245    public function delete_old_actions() {
     46        /**
     47         * Filter the minimum scheduled date age for action deletion.
     48         *
     49         * @param int $retention_period Minimum scheduled age in seconds of the actions to be deleted.
     50         */
    3351        $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
    34         $cutoff = as_get_datetime_object($lifespan.' seconds ago');
    35 
    36         $statuses_to_purge = array(
    37             ActionScheduler_Store::STATUS_COMPLETE,
    38             ActionScheduler_Store::STATUS_CANCELED,
    39         );
    40 
     52
     53        try {
     54            $cutoff = as_get_datetime_object( $lifespan . ' seconds ago' );
     55        } catch ( Exception $e ) {
     56            _doing_it_wrong(
     57                __METHOD__,
     58                sprintf(
     59                    /* Translators: %s is the exception message. */
     60                    esc_html__( 'It was not possible to determine a valid cut-off time: %s.', 'action-scheduler' ),
     61                    esc_html( $e->getMessage() )
     62                ),
     63                '3.5.5'
     64            );
     65
     66            return array();
     67        }
     68
     69
     70        /**
     71         * Filter the statuses when cleaning the queue.
     72         *
     73         * @param string[] $default_statuses_to_purge Action statuses to clean.
     74         */
     75        $statuses_to_purge = (array) apply_filters( 'action_scheduler_default_cleaner_statuses', $this->default_statuses_to_purge );
     76
     77        return $this->clean_actions( $statuses_to_purge, $cutoff, $this->get_batch_size() );
     78    }
     79
     80    /**
     81     * Delete selected actions limited by status and date.
     82     *
     83     * @param string[] $statuses_to_purge List of action statuses to purge. Defaults to canceled, complete.
     84     * @param DateTime $cutoff_date Date limit for selecting actions. Defaults to 31 days ago.
     85     * @param int|null $batch_size Maximum number of actions per status to delete. Defaults to 20.
     86     * @param string $context Calling process context. Defaults to `old`.
     87     * @return array Actions deleted.
     88     */
     89    public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) {
     90        $batch_size = $batch_size !== null ? $batch_size : $this->batch_size;
     91        $cutoff     = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' );
     92        $lifespan   = time() - $cutoff->getTimestamp();
     93        if ( empty( $statuses_to_purge ) ) {
     94            $statuses_to_purge = $this->default_statuses_to_purge;
     95        }
     96
     97        $deleted_actions = [];
    4198        foreach ( $statuses_to_purge as $status ) {
    4299            $actions_to_delete = $this->store->query_actions( array(
     
    44101                'modified'         => $cutoff,
    45102                'modified_compare' => '<=',
    46                 'per_page'         => $this->get_batch_size(),
     103                'per_page'         => $batch_size,
    47104                'orderby'          => 'none',
    48105            ) );
    49106
    50             foreach ( $actions_to_delete as $action_id ) {
    51                 try {
    52                     $this->store->delete_action( $action_id );
    53                 } catch ( Exception $e ) {
    54 
    55                     /**
    56                      * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
    57                      *
    58                      * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
    59                      * actions.
    60                      *
    61                      * @since 2.0.0
    62                      *
    63                      * @param int $action_id The scheduled actions ID in the data store
    64                      * @param Exception $e The exception thrown when attempting to delete the action from the data store
    65                      * @param int $lifespan The retention period, in seconds, for old actions
    66                      * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
    67                      */
    68                     do_action( 'action_scheduler_failed_old_action_deletion', $action_id, $e, $lifespan, count( $actions_to_delete ) );
    69                 }
     107            $deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) );
     108        }
     109
     110        return $deleted_actions;
     111    }
     112
     113    /**
     114     * @param int[] $actions_to_delete List of action IDs to delete.
     115     * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted.
     116     * @param string $context Context of the delete request.
     117     * @return array Deleted action IDs.
     118     */
     119    private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) {
     120        $deleted_actions = [];
     121        if ( $lifespan === null ) {
     122            $lifespan = $this->month_in_seconds;
     123        }
     124
     125        foreach ( $actions_to_delete as $action_id ) {
     126            try {
     127                $this->store->delete_action( $action_id );
     128                $deleted_actions[] = $action_id;
     129            } catch ( Exception $e ) {
     130                /**
     131                 * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
     132                 *
     133                 * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
     134                 * actions.
     135                 *
     136                 * @param int $action_id The scheduled actions ID in the data store
     137                 * @param Exception $e The exception thrown when attempting to delete the action from the data store
     138                 * @param int $lifespan The retention period, in seconds, for old actions
     139                 * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
     140                 * @since 2.0.0
     141                 *
     142                 */
     143                do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) );
    70144            }
    71145        }
     146        return $deleted_actions;
    72147    }
    73148
  • action-scheduler/trunk/classes/ActionScheduler_QueueRunner.php

    r2850016 r2910869  
    186186        /*
    187187         * Calling wp_cache_flush_runtime() lets us clear the runtime cache without invalidating the external object
    188          * cache, so we will always prefer this when it is available (but it was only introduced in WordPress 6.0).
     188         * cache, so we will always prefer this method (as compared to calling wp_cache_flush()) when it is available.
     189         *
     190         * However, this function was only introduced in WordPress 6.0. Additionally, the preferred way of detecting if
     191         * it is supported changed in WordPress 6.1 so we use two different methods to decide if we should utilize it.
    189192         */
    190         if ( function_exists( 'wp_cache_flush_runtime' ) ) {
     193        $flushing_runtime_cache_explicitly_supported = function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_runtime' );
     194        $flushing_runtime_cache_implicitly_supported = ! function_exists( 'wp_cache_supports' ) && function_exists( 'wp_cache_flush_runtime' );
     195
     196        if ( $flushing_runtime_cache_explicitly_supported || $flushing_runtime_cache_implicitly_supported ) {
    191197            wp_cache_flush_runtime();
    192198        } elseif (
  • action-scheduler/trunk/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php

    r2340383 r2910869  
    9191        $this->progress_bar = new ProgressBar(
    9292            /* translators: %d: amount of actions */
    93             sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), number_format_i18n( $count ) ),
     93            sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), $count ),
    9494            $count
    9595        );
  • action-scheduler/trunk/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php

    r2729833 r2910869  
    5656     * : Only run actions from the specified group. Omitting this option runs actions from all groups.
    5757     *
     58     * [--exclude-groups=<groups>]
     59     * : Run actions from all groups except the specified group(s). Define multiple groups as a comma separated string (without spaces), e.g. '--group_a,group_b'. This option is ignored when `--group` is used.
     60     *
    5861     * [--free-memory-on=<count>]
    5962     * : The number of actions to process between freeing memory. 0 disables freeing memory. Default 50.
     
    7376    public function run( $args, $assoc_args ) {
    7477        // Handle passed arguments.
    75         $batch   = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) );
    76         $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
    77         $clean   = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) );
    78         $hooks   = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) );
    79         $hooks   = array_filter( array_map( 'trim', $hooks ) );
    80         $group   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' );
    81         $free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 );
    82         $sleep   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
    83         $force   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false );
     78        $batch          = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) );
     79        $batches        = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
     80        $clean          = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) );
     81        $hooks          = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) );
     82        $hooks          = array_filter( array_map( 'trim', $hooks ) );
     83        $group          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' );
     84        $exclude_groups = \WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude-groups', '' );
     85        $free_on        = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 );
     86        $sleep          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
     87        $force          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false );
    8488
    8589        ActionScheduler_DataController::set_free_ticks( $free_on );
     
    8993        $actions_completed = 0;
    9094        $unlimited         = $batches === 0;
     95        if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) {
     96            $exclude_groups = $this->parse_comma_separated_string( $exclude_groups );
     97
     98            if ( ! empty( $exclude_groups ) ) {
     99                ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups );
     100            }
     101        }
    91102
    92103        try {
     
    118129
    119130    /**
     131     * Converts a string of comma-separated values into an array of those same values.
     132     *
     133     * @param string $string The string of one or more comma separated values.
     134     *
     135     * @return array
     136     */
     137    private function parse_comma_separated_string( $string ): array {
     138        return array_filter( str_getcsv( $string ) );
     139    }
     140
     141    /**
    120142     * Print WP CLI message about how many actions are about to be processed.
    121143     *
     
    127149        WP_CLI::log(
    128150            sprintf(
    129                 /* translators: %d refers to how many scheduled taks were found to run */
     151                /* translators: %d refers to how many scheduled tasks were found to run */
    130152                _n( 'Found %d scheduled task', 'Found %d scheduled tasks', $total, 'action-scheduler' ),
    131                 number_format_i18n( $total )
     153                $total
    132154            )
    133155        );
     
    146168                /* translators: %d refers to the total number of batches executed */
    147169                _n( '%d batch executed.', '%d batches executed.', $batches_completed, 'action-scheduler' ),
    148                 number_format_i18n( $batches_completed )
     170                $batches_completed
    149171            )
    150172        );
     
    180202        WP_CLI::success(
    181203            sprintf(
    182                 /* translators: %d refers to the total number of taskes completed */
     204                /* translators: %d refers to the total number of tasks completed */
    183205                _n( '%d scheduled task completed.', '%d scheduled tasks completed.', $actions_completed, 'action-scheduler' ),
    184                 number_format_i18n( $actions_completed )
     206                $actions_completed
    185207            )
    186208        );
  • action-scheduler/trunk/classes/abstracts/ActionScheduler.php

    r2729833 r2910869  
    154154            add_action( 'init', array( $logger, 'init' ), 1, 0 );
    155155            add_action( 'init', array( $runner, 'init' ), 1, 0 );
     156
     157            add_action(
     158                'init',
     159                /**
     160                 * Runs after the active store's init() method has been called.
     161                 *
     162                 * It would probably be preferable to have $store->init() (or it's parent method) set this itself,
     163                 * once it has initialized, however that would cause problems in cases where a custom data store is in
     164                 * use and it has not yet been updated to follow that same logic.
     165                 */
     166                function () {
     167                    self::$data_store_initialized = true;
     168
     169                    /**
     170                     * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
     171                     *
     172                     * @since 3.5.5
     173                     */
     174                    do_action( 'action_scheduler_init' );
     175                },
     176                1
     177            );
    156178        } else {
    157179            $admin_view->init();
     
    159181            $logger->init();
    160182            $runner->init();
     183            self::$data_store_initialized = true;
     184
     185            /**
     186             * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
     187             *
     188             * @since 3.5.5
     189             */
     190            do_action( 'action_scheduler_init' );
    161191        }
    162192
     
    167197        if ( defined( 'WP_CLI' ) && WP_CLI ) {
    168198            WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' );
     199            WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' );
    169200            if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) {
    170201                $command = new Migration_Command();
     
    172203            }
    173204        }
    174 
    175         self::$data_store_initialized = true;
    176205
    177206        /**
     
    193222    public static function is_initialized( $function_name = null ) {
    194223        if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
    195             $message = sprintf( __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), esc_attr( $function_name ) );
    196             error_log( $message, E_WARNING );
     224            $message = sprintf(
     225                /* translators: %s function name. */
     226                __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ),
     227                esc_attr( $function_name )
     228            );
     229            error_log( $message );
    197230        }
    198231
  • action-scheduler/trunk/classes/abstracts/ActionScheduler_Abstract_ListTable.php

    r2775803 r2910869  
    674674        // Helper to set 'all' filter when not set on status counts passed in.
    675675        if ( ! isset( $this->status_counts['all'] ) ) {
    676             $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts;
    677         }
    678 
    679         foreach ( $this->status_counts as $status_name => $count ) {
     676            $all_count = array_sum( $this->status_counts );
     677            if ( isset( $this->status_counts['past-due'] ) ) {
     678                $all_count -= $this->status_counts['past-due'];
     679            }
     680            $this->status_counts = array( 'all' => $all_count ) + $this->status_counts;
     681        }
     682
     683        // Translated status labels.
     684        $status_labels             = ActionScheduler_Store::instance()->get_status_labels();
     685        $status_labels['all']      = _x( 'All', 'status labels', 'action-scheduler' );
     686        $status_labels['past-due'] = _x( 'Past-due', 'status labels', 'action-scheduler' );
     687
     688        foreach ( $this->status_counts as $status_slug => $count ) {
    680689
    681690            if ( 0 === $count ) {
     
    683692            }
    684693
    685             if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) {
     694            if ( $status_slug === $request_status || ( empty( $request_status ) && 'all' === $status_slug ) ) {
    686695                $status_list_item = '<li class="%1$s"><a href="%2$s" class="current">%3$s</a> (%4$d)</li>';
    687696            } else {
     
    689698            }
    690699
    691             $status_filter_url   = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name );
     700            $status_name         = isset( $status_labels[ $status_slug ] ) ? $status_labels[ $status_slug ] : ucfirst( $status_slug );
     701            $status_filter_url   = ( 'all' === $status_slug ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_slug );
    692702            $status_filter_url   = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
    693             $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) );
     703            $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_slug ), esc_url( $status_filter_url ), esc_html( $status_name ), absint( $count ) );
    694704        }
    695705
  • action-scheduler/trunk/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php

    r2850016 r2910869  
    4949     */
    5050    public function process_action( $action_id, $context = '' ) {
     51        // Temporarily override the error handler while we process the current action.
     52        set_error_handler(
     53            /**
     54             * Temporary error handler which can catch errors and convert them into exceptions. This faciliates more
     55             * robust error handling across all supported PHP versions.
     56             *
     57             * @throws Exception
     58             *
     59             * @param int    $type    Error level expressed as an integer.
     60             * @param string $message Error message.
     61             */
     62            function ( $type, $message ) {
     63                throw new Exception( $message );
     64            },
     65            E_USER_ERROR | E_RECOVERABLE_ERROR
     66        );
     67
     68        /*
     69         * The nested try/catch structure is required because we potentially need to convert thrown errors into
     70         * exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same*
     71         * structure).
     72         */
    5173        try {
    52             $valid_action = false;
    53             do_action( 'action_scheduler_before_execute', $action_id, $context );
    54 
    55             if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
    56                 do_action( 'action_scheduler_execution_ignored', $action_id, $context );
    57                 return;
     74            try {
     75                $valid_action = false;
     76                do_action( 'action_scheduler_before_execute', $action_id, $context );
     77
     78                if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
     79                    do_action( 'action_scheduler_execution_ignored', $action_id, $context );
     80                    return;
     81                }
     82
     83                $valid_action = true;
     84                do_action( 'action_scheduler_begin_execute', $action_id, $context );
     85
     86                $action = $this->store->fetch_action( $action_id );
     87                $this->store->log_execution( $action_id );
     88                $action->execute();
     89                do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
     90                $this->store->mark_complete( $action_id );
     91            } catch ( Throwable $e ) {
     92                // Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for
     93                // compatibility with ActionScheduler_Logger.
     94                throw new Exception( $e->getMessage(), $e->getCode(), $e->getPrevious() );
    5895            }
    59 
    60             $valid_action = true;
    61             do_action( 'action_scheduler_begin_execute', $action_id, $context );
    62 
    63             $action = $this->store->fetch_action( $action_id );
    64             $this->store->log_execution( $action_id );
    65             $action->execute();
    66             do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
    67             $this->store->mark_complete( $action_id );
    6896        } catch ( Exception $e ) {
    69             if ( $valid_action ) {
    70                 $this->store->mark_failure( $action_id );
    71                 do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
    72             } else {
    73                 do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
    74             }
     97            // This catch block exists for compatibility with PHP 5.6.
     98            $this->handle_action_error( $action_id, $e, $context, $valid_action );
     99        } finally {
     100            restore_error_handler();
    75101        }
    76102
    77103        if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
    78104            $this->schedule_next_instance( $action, $action_id );
     105        }
     106    }
     107
     108    /**
     109     * Marks actions as either having failed execution or failed validation, as appropriate.
     110     *
     111     * @param int       $action_id    Action ID.
     112     * @param Exception $e            Exception instance.
     113     * @param string    $context      Execution context.
     114     * @param bool      $valid_action If the action is valid.
     115     *
     116     * @return void
     117     */
     118    private function handle_action_error( $action_id, $e, $context, $valid_action ) {
     119        if ( $valid_action ) {
     120            $this->store->mark_failure( $action_id );
     121            /**
     122             * Runs when action execution fails.
     123             *
     124             * @param int       $action_id Action ID.
     125             * @param Exception $e         Exception instance.
     126             * @param string    $context   Execution context.
     127             */
     128            do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
     129        } else {
     130            /**
     131             * Runs when action validation fails.
     132             *
     133             * @param int       $action_id Action ID.
     134             * @param Exception $e         Exception instance.
     135             * @param string    $context   Execution context.
     136             */
     137            do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
    79138        }
    80139    }
     
    144203        }
    145204
    146         // Now let's fetch the first action (having the same hook) of *any status*ithin the same window.
     205        // Now let's fetch the first action (having the same hook) of *any status* within the same window.
    147206        unset( $query_args['status'] );
    148207        $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args );
    149208
    150         // If the IDs match, then actions for this hook must be consistently failing.
    151         return $first_action_id_with_the_same_hook === $first_failing_action_id;
     209        /**
     210         * If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a
     211         * way to observe and optionally override that assessment.
     212         *
     213         * @param bool                   $is_consistently_failing If the action is considered to be consistently failing.
     214         * @param ActionScheduler_Action $action                  The action being assessed.
     215         */
     216        return (bool) apply_filters(
     217            'action_scheduler_recurring_action_is_consistently_failing',
     218            $first_action_id_with_the_same_hook === $first_failing_action_id,
     219            $action
     220        );
    152221    }
    153222
  • action-scheduler/trunk/classes/actions/ActionScheduler_Action.php

    r2775803 r2910869  
    1010    protected $schedule = NULL;
    1111    protected $group = '';
     12
     13    /**
     14     * Priorities are conceptually similar to those used for regular WordPress actions.
     15     * Like those, a lower priority takes precedence over a higher priority and the default
     16     * is 10.
     17     *
     18     * Unlike regular WordPress actions, the priority of a scheduled action is strictly an
     19     * integer and should be kept within the bounds 0-255 (anything outside the bounds will
     20     * be brought back into the acceptable range).
     21     *
     22     * @var int
     23     */
     24    protected $priority = 10;
    1225
    1326    public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) {
     
    94107        return FALSE;
    95108    }
     109
     110    /**
     111     * Sets the priority of the action.
     112     *
     113     * @param int $priority Priority level (lower is higher priority). Should be in the range 0-255.
     114     *
     115     * @return void
     116     */
     117    public function set_priority( $priority ) {
     118        if ( $priority < 0 ) {
     119            $priority = 0;
     120        } elseif ( $priority > 255 ) {
     121            $priority = 255;
     122        }
     123
     124        $this->priority = (int) $priority;
     125    }
     126
     127    /**
     128     * Gets the action priority.
     129     *
     130     * @return int
     131     */
     132    public function get_priority() {
     133        return $this->priority;
     134    }
    96135}
  • action-scheduler/trunk/classes/data-stores/ActionScheduler_DBStore.php

    r2850016 r2910869  
    2626    protected static $max_index_length = 191;
    2727
     28    /** @var array List of claim filters. */
     29    protected $claim_filters = [
     30        'group'          => '',
     31        'hooks'          => '',
     32        'exclude-groups' => '',
     33    ];
     34
    2835    /**
    2936     * Initialize the data store
     
    8592                'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
    8693                'schedule'             => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
    87                 'group_id'             => $this->get_group_id( $action->get_group() ),
     94                'group_id'             => current( $this->get_group_ids( $action->get_group() ) ),
     95                'priority'             => $action->get_priority(),
    8896            );
    8997
     
    173181        );
    174182        $pending_status_placeholders = implode( ', ', array_fill( 0, count( $pending_statuses ), '%s' ) );
     183
    175184        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $pending_status_placeholders is hardcoded.
    176185        $where_clause = $wpdb->prepare(
     
    243252     * Get a group's ID based on its name/slug.
    244253     *
    245      * @param string $slug The string name of a group.
    246      * @param bool   $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
    247      *
    248      * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created.
    249      */
    250     protected function get_group_id( $slug, $create_if_not_exists = true ) {
    251         if ( empty( $slug ) ) {
    252             return 0;
    253         }
    254         /** @var \wpdb $wpdb */
    255         global $wpdb;
    256         $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
    257         if ( empty( $group_id ) && $create_if_not_exists ) {
    258             $group_id = $this->create_group( $slug );
    259         }
    260 
    261         return $group_id;
     254     * @param string|array $slugs                The string name of a group, or names for several groups.
     255     * @param bool         $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
     256     *
     257     * @return array The group IDs, if they exist or were successfully created. May be empty.
     258     */
     259    protected function get_group_ids( $slugs, $create_if_not_exists = true ) {
     260        $slugs     = (array) $slugs;
     261        $group_ids = array();
     262
     263        if ( empty( $slugs ) ) {
     264            return array();
     265        }
     266
     267        /** @var \wpdb $wpdb */
     268        global $wpdb;
     269
     270        foreach ( $slugs as $slug ) {
     271            $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
     272
     273            if ( empty( $group_id ) && $create_if_not_exists ) {
     274                $group_id = $this->create_group( $slug );
     275            }
     276
     277            if ( $group_id ) {
     278                $group_ids[] = $group_id;
     279            }
     280        }
     281
     282        return $group_ids;
    262283    }
    263284
     
    356377        $group = $data->group ? $data->group : '';
    357378
    358         return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
     379        return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group, $data->priority );
    359380    }
    360381
     
    798819
    799820    /**
     821     * Set a claim filter.
     822     *
     823     * @param string $filter_name Claim filter name.
     824     * @param mixed $filter_values Values to filter.
     825     * @return void
     826     */
     827    public function set_claim_filter( $filter_name, $filter_values ) {
     828        if ( isset( $this->claim_filters[ $filter_name ] ) ) {
     829            $this->claim_filters[ $filter_name ] = $filter_values;
     830        }
     831    }
     832
     833    /**
     834     * Get the claim filter value.
     835     *
     836     * @param string $filter_name Claim filter name.
     837     * @return mixed
     838     */
     839    public function get_claim_filter( $filter_name ) {
     840        if ( isset( $this->claim_filters[ $filter_name ] ) ) {
     841            return $this->claim_filters[ $filter_name ];
     842        }
     843
     844        return '';
     845    }
     846
     847    /**
    800848     * Mark actions claimed.
    801849     *
     
    814862        global $wpdb;
    815863
    816         $now  = as_get_datetime_object();
    817         $date = is_null( $before_date ) ? $now : clone $before_date;
    818 
     864        $now    = as_get_datetime_object();
     865        $date   = is_null( $before_date ) ? $now : clone $before_date;
    819866        // can't use $wpdb->update() because of the <= condition.
    820867        $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
     
    825872        );
    826873
     874        // Set claim filters.
     875        if ( ! empty( $hooks ) ) {
     876            $this->set_claim_filter( 'hooks', $hooks );
     877        } else {
     878            $hooks = $this->get_claim_filter( 'hooks' );
     879        }
     880        if ( ! empty( $group ) ) {
     881            $this->set_claim_filter( 'group', $group );
     882        } else {
     883            $group = $this->get_claim_filter( 'group' );
     884        }
     885
    827886        $where    = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s';
    828887        $params[] = $date->format( 'Y-m-d H:i:s' );
     
    835894        }
    836895
     896        $group_operator = 'IN';
     897        if ( empty( $group ) ) {
     898            $group = $this->get_claim_filter( 'exclude-groups' );
     899            $group_operator = 'NOT IN';
     900        }
     901
    837902        if ( ! empty( $group ) ) {
    838 
    839             $group_id = $this->get_group_id( $group, false );
    840 
    841             // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour.
    842             if ( empty( $group_id ) ) {
    843                 /* translators: %s: group name */
    844                 throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
    845             }
    846 
    847             $where    .= ' AND group_id = %d';
    848             $params[] = $group_id;
     903            $group_ids = $this->get_group_ids( $group, false );
     904
     905            // throw exception if no matching group(s) found, this matches ActionScheduler_wpPostStore's behaviour.
     906            if ( empty( $group_ids ) ) {
     907                throw new InvalidArgumentException(
     908                    sprintf(
     909                        /* translators: %s: group name(s) */
     910                        _n(
     911                            'The group "%s" does not exist.',
     912                            'The groups "%s" do not exist.',
     913                            is_array( $group ) ? count( $group ) : 1,
     914                            'action-scheduler'
     915                        ),
     916                        $group
     917                    )
     918                );
     919            }
     920
     921            $id_list = implode( ',', array_map( 'intval', $group_ids ) );
     922            $where   .= " AND group_id {$group_operator} ( $id_list )";
    849923        }
    850924
     
    856930         * @param string $order_by_sql
    857931         */
    858         $order    = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
     932        $order    = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
    859933        $params[] = $limit;
    860934
     
    913987
    914988        $sql = $wpdb->prepare(
    915             "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d",
     989            "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d ORDER BY priority ASC",
    916990            $claim_id
    917991        );
     
    10061080     * Add execution message to action log.
    10071081     *
     1082     * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress').
     1083     *
    10081084     * @param int $action_id Action ID.
    10091085     *
     
    10161092        $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
    10171093        $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
    1018         $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     1094
     1095        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     1096        $status_updated = $wpdb->query( $sql );
     1097
     1098        if ( ! $status_updated ) {
     1099            throw new Exception(
     1100                sprintf(
     1101                    /* translators: 1: action ID. 2: status slug. */
     1102                    __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
     1103                    $action_id,
     1104                    self::STATUS_RUNNING
     1105                )
     1106            );
     1107        }
    10191108    }
    10201109
  • action-scheduler/trunk/classes/data-stores/ActionScheduler_wpPostStore.php

    r2775803 r2910869  
    937937     * Log Execution.
    938938     *
     939     * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress').
     940     *
    939941     * @param string $action_id Action ID.
    940942     */
     
    948950
    949951        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    950         $wpdb->query(
     952        $status_updated = $wpdb->query(
    951953            $wpdb->prepare(
    952954                "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s",
     
    958960            )
    959961        );
     962
     963        if ( ! $status_updated ) {
     964            throw new Exception(
     965                sprintf(
     966                    /* translators: 1: action ID. 2: status slug. */
     967                    __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
     968                    $action_id,
     969                    self::STATUS_RUNNING
     970                )
     971            );
     972        }
    960973    }
    961974
  • action-scheduler/trunk/classes/migration/Runner.php

    r2340383 r2910869  
    8080        if ( $this->progress_bar ) {
    8181            /* translators: %d: amount of actions */
    82             $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), number_format_i18n( $batch_size ) ) );
     82            $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), $batch_size ) );
    8383            $this->progress_bar->set_count( $batch_size );
    8484        }
  • action-scheduler/trunk/classes/schema/ActionScheduler_StoreSchema.php

    r2850016 r2910869  
    1717     * @var int Increment this value to trigger a schema update.
    1818     */
    19     protected $schema_version = 6;
     19    protected $schema_version = 7;
    2020
    2121    public function __construct() {
     
    5050                        scheduled_date_gmt datetime NULL default '{$default_date}',
    5151                        scheduled_date_local datetime NULL default '{$default_date}',
     52                        priority tinyint unsigned NOT NULL default '10',
    5253                        args varchar($max_index_length),
    5354                        schedule longtext,
  • action-scheduler/trunk/functions.php

    r2850016 r2910869  
    1313 * @param string $group The group to assign this job to.
    1414 * @param bool   $unique Whether the action should be unique.
     15 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    1516 *
    1617 * @return int The action ID.
    1718 */
    18 function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false ) {
     19function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    1920    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    2021        return 0;
     
    3435     * @param array    $args       Action arguments.
    3536     * @param string   $group      Action group.
     37     * @param int      $priority   Action priority.
    3638     */
    37     $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group );
     39    $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority );
    3840    if ( null !== $pre ) {
    3941        return is_int( $pre ) ? $pre : 0;
    4042    }
    4143
    42     return ActionScheduler::factory()->async_unique( $hook, $args, $group, $unique );
     44    return ActionScheduler::factory()->create(
     45        array(
     46            'type'      => 'async',
     47            'hook'      => $hook,
     48            'arguments' => $args,
     49            'group'     => $group,
     50            'unique'    => $unique,
     51            'priority'  => $priority,
     52        )
     53    );
    4354}
    4455
     
    5162 * @param string $group The group to assign this job to.
    5263 * @param bool   $unique Whether the action should be unique.
     64 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    5365 *
    5466 * @return int The action ID.
    5567 */
    56 function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false ) {
     68function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    5769    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    5870        return 0;
     
    7385     * @param array    $args       Action arguments.
    7486     * @param string   $group      Action group.
     87     * @param int      $priorities Action priority.
    7588     */
    76     $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group );
     89    $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority );
    7790    if ( null !== $pre ) {
    7891        return is_int( $pre ) ? $pre : 0;
    7992    }
    8093
    81     return ActionScheduler::factory()->single_unique( $hook, $args, $timestamp, $group, $unique );
     94    return ActionScheduler::factory()->create(
     95        array(
     96            'type'      => 'single',
     97            'hook'      => $hook,
     98            'arguments' => $args,
     99            'when'      => $timestamp,
     100            'group'     => $group,
     101            'unique'    => $unique,
     102            'priority'  => $priority,
     103        )
     104    );
    82105}
    83106
     
    91114 * @param string $group The group to assign this job to.
    92115 * @param bool   $unique Whether the action should be unique.
     116 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    93117 *
    94118 * @return int The action ID.
    95119 */
    96 function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false ) {
    97     if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
     120function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
     121    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
     122        return 0;
     123    }
     124
     125    $interval = (int) $interval_in_seconds;
     126
     127    // We expect an integer and allow it to be passed using float and string types, but otherwise
     128    // should reject unexpected values.
     129    if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) {
     130        _doing_it_wrong(
     131            __METHOD__,
     132            sprintf(
     133                /* translators: 1: provided value 2: provided type. */
     134                esc_html__( 'An integer was expected but "%1$s" (%2$s) was received.', 'action-scheduler' ),
     135                esc_html( $interval_in_seconds ),
     136                esc_html( gettype( $interval_in_seconds ) )
     137            ),
     138            '3.6.0'
     139        );
     140
    98141        return 0;
    99142    }
     
    114157     * @param array    $args                Action arguments.
    115158     * @param string   $group               Action group.
     159     * @param int      $priority            Action priority.
    116160     */
    117     $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group );
     161    $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority );
    118162    if ( null !== $pre ) {
    119163        return is_int( $pre ) ? $pre : 0;
    120164    }
    121165
    122     return ActionScheduler::factory()->recurring_unique( $hook, $args, $timestamp, $interval_in_seconds, $group, $unique );
     166    return ActionScheduler::factory()->create(
     167        array(
     168            'type'      => 'recurring',
     169            'hook'      => $hook,
     170            'arguments' => $args,
     171            'when'      => $timestamp,
     172            'pattern'   => $interval_in_seconds,
     173            'group'     => $group,
     174            'unique'    => $unique,
     175            'priority'  => $priority,
     176        )
     177    );
    123178}
    124179
     
    144199 * @param string $group The group to assign this job to.
    145200 * @param bool   $unique Whether the action should be unique.
     201 * @param int    $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255.
    146202 *
    147203 * @return int The action ID.
    148204 */
    149 function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false ) {
     205function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
    150206    if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
    151207        return 0;
     
    167223     * @param array    $args       Action arguments.
    168224     * @param string   $group      Action group.
     225     * @param int      $priority   Action priority.
    169226     */
    170     $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group );
     227    $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority );
    171228    if ( null !== $pre ) {
    172229        return is_int( $pre ) ? $pre : 0;
    173230    }
    174231
    175     return ActionScheduler::factory()->cron_unique( $hook, $args, $timestamp, $schedule, $group, $unique );
     232    return ActionScheduler::factory()->create(
     233        array(
     234            'type'      => 'cron',
     235            'hook'      => $hook,
     236            'arguments' => $args,
     237            'when'      => $timestamp,
     238            'pattern'   => $schedule,
     239            'group'     => $group,
     240            'unique'    => $unique,
     241            'priority'  => $priority,
     242        )
     243    );
    176244}
    177245
  • action-scheduler/trunk/readme.txt

    r2850016 r2910869  
    44Requires at least: 5.2
    55Tested up to: 6.0
    6 Stable tag: 3.5.4
     6Stable tag: 3.6.0
    77License: GPLv3
    88Requires PHP: 5.6
     
    4747
    4848== Changelog ==
     49
     50= 3.6.0 - 2023-05-10 =
     51* Add $unique parameter to function signatures.
     52* Add a cast-to-int for extra safety before forming new DateTime object.
     53* Add a hook allowing exceptions for consistently failing recurring actions.
     54* Add action priorities.
     55* Add init hook.
     56* Always raise the time limit.
     57* Bump minimatch from 3.0.4 to 3.0.8.
     58* Bump yaml from 2.2.1 to 2.2.2.
     59* Defensive coding relating to gaps in declared schedule types.
     60* Do not process an action if it cannot be set to `in-progress`.
     61* Filter view labels (status names) should be translatable | #919.
     62* Fix WPCLI progress messages.
     63* Improve data-store initialization flow.
     64* Improve error handling across all supported PHP versions.
     65* Improve logic for flushing the runtime cache.
     66* Support exclusion of multiple groups.
     67* Update lint-staged and Node/NPM requirements.
     68* add CLI clean command.
     69* add CLI exclude-group filter.
     70* exclude past-due from list table all filter count.
     71* throwing an exception if as_schedule_recurring_action interval param is not of type integer.
    4972
    5073= 3.5.4 - 2023-01-17 =
Note: See TracChangeset for help on using the changeset viewer.