diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a52e39..f2bc49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ If you like Advanced Cron Scheduler plugin, please take a moment to [give a 5-st All notable changes to this project will be documented in this file. +## 1.1.0 +Release Date: 25th June, 2023 + +* Added: Option to disable check for past-due actions. +* Tweak: Changed `init` hook to `action_scheduler_init`. +* Updated: Action Scheduler library to v3.6.1. +* Tested up to WordPress 6.3. + ## 1.0.9 Release Date: 15th November, 2022 diff --git a/composer.json b/composer.json index 7a5cc88..6e4dc4f 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ { "name": "Sayan Datta", "email": "iamsayan@protonmail.com", - "homepage": "http://sayandatta.in", + "homepage": "http://www.sayandatta.co.in", "role": "Developer" } ], diff --git a/composer.lock b/composer.lock index da09836..7a63eb4 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "woocommerce/action-scheduler", - "version": "3.5.3", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", - "reference": "63ef5af013ca3a6efdd8ef8e9363ac70778713cb" + "reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/63ef5af013ca3a6efdd8ef8e9363ac70778713cb", - "reference": "63ef5af013ca3a6efdd8ef8e9363ac70778713cb", + "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/7fd383cad3d64b419ec81bcd05bab44355a6e6ef", + "reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef", "shasum": "" }, "require-dev": { @@ -42,9 +42,9 @@ "homepage": "https://actionscheduler.org/", "support": { "issues": "https://github.com/woocommerce/action-scheduler/issues", - "source": "https://github.com/woocommerce/action-scheduler/tree/3.5.3" + "source": "https://github.com/woocommerce/action-scheduler/tree/3.6.1" }, - "time": "2022-11-09T17:45:19+00:00" + "time": "2023-06-14T19:23:12+00:00" } ], "packages-dev": [], diff --git a/includes/Base/Actions.php b/includes/Base/Actions.php index eacbce6..3d41a4b 100644 --- a/includes/Base/Actions.php +++ b/includes/Base/Actions.php @@ -34,8 +34,9 @@ public function register() { * Register settings link. */ public function settings_link( $links ) { + $links[] = '' . __( 'Settings', 'migrate-wp-cron-to-action-scheduler' ) . ''; $links[] = '' . __( 'Action Scheduler', 'migrate-wp-cron-to-action-scheduler' ) . ''; - + return $links; } diff --git a/includes/Base/AdminNotice.php b/includes/Base/AdminNotice.php index 223e0a0..2be8022 100644 --- a/includes/Base/AdminNotice.php +++ b/includes/Base/AdminNotice.php @@ -34,36 +34,12 @@ public function register() { * Show internal admin notices. */ public function notice() { - global $wp_version; - - // Show a warning to sites running PHP < 5.6 - if ( version_compare( $wp_version, '5.2.0', '<' ) ) { - deactivate_plugins( $this->plugin ); - if ( isset( $_GET['activate'] ) ) { // phpcs:ignore - unset( $_GET['activate'] ); // phpcs:ignore - } - /* translators: %s: Plugin Name */ - echo '

' . sprintf( esc_html__( 'Your version of WordPress is below the minimum version of WordPress required by %s plugin. Please upgrade WordPress to 5.2.0 or later.', 'migrate-wp-cron-to-action-scheduler' ), esc_html( $this->name ) ) . '

'; - return; - } - - // Show a warning to sites running PHP < 5.6 - if ( version_compare( PHP_VERSION, '5.6', '<' ) ) { - deactivate_plugins( $this->plugin ); - if ( isset( $_GET['activate'] ) ) { // phpcs:ignore - unset( $_GET['activate'] ); // phpcs:ignore - } - /* translators: %s: Plugin Name */ - echo '

' . sprintf( esc_html__( 'Your version of PHP is below the minimum version of PHP required by %s plugin. Please contact your host and request that your version be upgraded to 5.6 or later.', 'migrate-wp-cron-to-action-scheduler' ), esc_html( $this->name ) ) . '

'; - return; - } - // Check transient, if available display notice if ( get_transient( 'acswp-show-notice-on-activation' ) !== false ) { ?>

here to view Action Scheduler tasks.', 'migrate-wp-cron-to-action-scheduler' ), 'Advanced Cron Scheduler', esc_html( ACSWP_PLUGIN_VERSION ), esc_url( admin_url( 'tools.php?page=action-scheduler' ) ) ); ?>

+ printf( wp_kses_post( __( 'Thanks for installing %1$s v%2$s plugin. Click here to view Action Scheduler tasks.', 'migrate-wp-cron-to-action-scheduler' ) ), 'Advanced Cron Scheduler', esc_html( ACSWP_VERSION ), esc_url( admin_url( 'tools.php?page=action-scheduler' ) ) ); ?>

action( 'admin_bar_menu', 'admin_bar' ); + $this->filter( 'action_scheduler_check_pastdue_actions', 'past_due_actions', 100 ); } /** @@ -45,4 +46,13 @@ public function admin_bar( $wp_admin_bar ) { $wp_admin_bar->add_node( $args ); } } + + /** + * Add admin bar content. + */ + public function past_due_actions( $check ) { + $is_disabled = (bool) get_option( 'acswp_disable_past_due_checking' ); + + return $is_disabled ? false : $check; + } } \ No newline at end of file diff --git a/includes/Core/Connection.php b/includes/Core/Connection.php index 9f8ec87..2d738e6 100644 --- a/includes/Core/Connection.php +++ b/includes/Core/Connection.php @@ -40,7 +40,8 @@ public function register() { $this->filter( 'pre_clear_scheduled_hook', 'pre_clear_scheduled_hook', 10, 3 ); $this->filter( 'pre_get_scheduled_event', 'pre_get_scheduled_event', 10, 4 ); $this->filter( 'pre_get_ready_cron_jobs', 'pre_get_ready_cron_jobs' ); - $this->action( 'init', 'register_crons', 10 ); + $this->action( 'action_scheduler_init', 'register_crons' ); + $this->action( 'shutdown', 'clear_crons' ); } /** @@ -421,7 +422,7 @@ public function pre_get_ready_cron_jobs( $pre ) { $action = \ActionScheduler::store()->fetch_action( $action_id ); $hook = $action->get_hook(); - $key = md5( serialize( $action->get_args() ) ); + $key = md5( serialize( $action->get_args() ) ); // PHPCS:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize $value = [ 'args' => $action->get_args(), '_job' => $action, @@ -463,4 +464,11 @@ public function register_crons() { \wp_schedule_event( $event->timestamp, $event->schedule, $event->hook, $event->args ); } } + + /** + * Clean crons + */ + public function clear_crons() { + $this->events = []; + } } \ No newline at end of file diff --git a/includes/Core/MigrateActions.php b/includes/Core/MigrateActions.php index 6102531..43d0144 100644 --- a/includes/Core/MigrateActions.php +++ b/includes/Core/MigrateActions.php @@ -74,7 +74,7 @@ public function regenerate_crons() { } $statement = $wpdb->prepare( "SELECT a.action_id, a.hook, a.scheduled_date_gmt, a.args, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.status=%s AND g.slug=%s", 'pending', 'mwpcac' ); - $values = $wpdb->get_results( $statement, ARRAY_A ); + $values = $wpdb->get_results( $statement, ARRAY_A ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared foreach ( $values as $value ) { $this->generate_wp_cron( strtotime( $value['scheduled_date_gmt'] ), $value['hook'], json_decode( $value['args'], true ) ); $this->cancel_scheduled_action( $value['action_id'] ); @@ -96,7 +96,7 @@ private function generate_wp_cron( $timestamp, $hook, $args ) { } // get keys - $key = md5( serialize( $args ) ); + $key = md5( serialize( $args ) ); // PHPCS:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize $crons[ $timestamp ][ $hook ][ $key ] = [ 'schedule' => false, @@ -121,7 +121,7 @@ private function remove_wp_cron( $timestamp, $hook, $args ) { $crons = _get_cron_array(); // get keys - $key = md5( serialize( $args ) ); + $key = md5( serialize( $args ) ); // PHPCS:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize unset( $crons[ $timestamp ][ $hook ][ $key ] ); diff --git a/includes/Core/Settings.php b/includes/Core/Settings.php index 8112863..2b1b337 100644 --- a/includes/Core/Settings.php +++ b/includes/Core/Settings.php @@ -32,11 +32,26 @@ public function register() { * Register custom settings. */ public function register_fields() { - register_setting( 'general', 'acswp_admin_bar' ); - add_settings_field( 'acswp_admin_bar', __( 'Show Admin Bar', 'migrate-wp-cron-to-action-scheduler' ), [ $this, 'admin_bar_field' ], 'general' ); + $fields = [ + 'acswp_admin_bar' => __( 'Show Admin Bar', 'migrate-wp-cron-to-action-scheduler' ), + 'acswp_unique_action' => __( 'Enable Unique Actions', 'migrate-wp-cron-to-action-scheduler' ), + 'acswp_disable_past_due_checking' => __( 'Disable Past-Due Checking', 'migrate-wp-cron-to-action-scheduler' ), + ]; - register_setting( 'general', 'acswp_unique_action' ); - add_settings_field( 'acswp_unique_action', __( 'Enable Unique Actions', 'migrate-wp-cron-to-action-scheduler' ), [ $this, 'unique_action_field' ], 'general' ); + add_settings_section( 'acswp-settings', __( 'Advanced Cron Scheduler', 'migrate-wp-cron-to-action-scheduler' ), [ $this, 'description' ], 'general' ); + + foreach ( $fields as $field => $name ) { + register_setting( 'general', $field ); + add_settings_field( $field, $name, [ $this, str_replace( 'acswp_', '', $field ) . '_field' ], 'general', 'acswp-settings' ); + } + } + + /* + * Print settings field + */ + public function description() { ?> +
+ + + setup(); + } + + return self::$instance; } -} -register_activation_hook( __FILE__, 'acswp_plugin_activation' ); -/** - * The code that runs during plugin deactivation - */ -if ( ! function_exists( 'acswp_plugin_deactivation' ) ) { - function acswp_plugin_deactivation() { - ACSWP\Plugin\Base\Deactivate::deactivate(); + /** + * Instantiate the plugin. + */ + private function setup() { + // Define plugin constants. + $this->define_constants(); + + if ( ! $this->is_requirements_meet() ) { + return; + } + + // Include required files. + $this->includes(); + + // Instantiate services. + $this->instantiate(); + + // Loaded action. + do_action( 'acswp/loaded' ); + } + + /** + * Check that the WordPress and PHP setup meets the plugin requirements. + * + * @return bool + */ + private function is_requirements_meet() { + + // Check WordPress version. + if ( version_compare( get_bloginfo( 'version' ), $this->wordpress_version, '<' ) ) { + /* translators: WordPress Version */ + $this->messages[] = sprintf( esc_html__( 'You are using the outdated WordPress, please update it to version %s or higher.', 'migrate-wp-cron-to-action-scheduler' ), $this->wordpress_version ); + } + + // Check PHP version. + if ( version_compare( phpversion(), $this->php_version, '<' ) ) { + /* translators: PHP Version */ + $this->messages[] = sprintf( esc_html__( 'Advanced Cron Scheduler for WordPress requires PHP version %s or above. Please update PHP to run this plugin.', 'migrate-wp-cron-to-action-scheduler' ), $this->php_version ); + } + + if ( empty( $this->messages ) ) { + return true; + } + + // Auto-deactivate plugin. + add_action( 'admin_init', [ $this, 'auto_deactivate' ] ); + add_action( 'admin_notices', [ $this, 'activation_error' ] ); + + return false; + } + + /** + * Auto-deactivate plugin if requirements are not met, and display a notice. + */ + public function auto_deactivate() { + deactivate_plugins( ACSWP_BASENAME ); + if ( isset( $_GET['activate'] ) ) { // phpcs:ignore + unset( $_GET['activate'] ); // phpcs:ignore + } + } + + /** + * Error notice on plugin activation. + */ + public function activation_error() { + ?> +
+

+ ', $this->messages ); // phpcs:ignore ?> +

+
+ version ); + define( 'ACSWP_FILE', __FILE__ ); + define( 'ACSWP_PATH', dirname( ACSWP_FILE ) . '/' ); + define( 'ACSWP_URL', plugins_url( '', ACSWP_FILE ) . '/' ); + define( 'ACSWP_BASENAME', plugin_basename( ACSWP_FILE ) ); + } + + /** + * Include the required files. + */ + private function includes() { + include dirname( __FILE__ ) . '/vendor/autoload.php'; + } + + /** + * Instantiate services. + */ + private function instantiate() { + // Activation hook. + register_activation_hook( ACSWP_FILE, + function () { + ACSWP\Plugin\Base\Activate::activate(); + } + ); + + // Deactivation hook. + register_deactivation_hook( ACSWP_FILE, + function () { + ACSWP\Plugin\Base\Deactivate::deactivate(); + } + ); + + // Init ACSWP Classes. + ACSWP\Plugin\Loader::register_services(); } } -register_deactivation_hook( __FILE__, 'acswp_plugin_deactivation' ); /** - * Initialize all the core classes of the plugin + * Returns the main instance of ACSWP to prevent the need to use globals. + * + * @return ACSWP */ -if ( ! function_exists( 'acswp_plugin_init' ) ) { - function acswp_plugin_init() { - if ( class_exists( 'ACSWP\Plugin\\Loader' ) ) { - ACSWP\Plugin\Loader::register_services(); - } - } +function acswp() { + return ACSWP::get(); } -acswp_plugin_init(); \ No newline at end of file + +// Start it. +acswp(); \ No newline at end of file diff --git a/readme.txt b/readme.txt index ac6cedd..8214e13 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === Advanced Cron Scheduler for WordPress === -Contributors: Infosatech +Contributors: infosatech Tags: scheduler, cron, wp cron, debug, cron manager Requires at least: 5.2 -Tested up to: 6.1 -Stable tag: 1.0.9 +Tested up to: 6.3 +Stable tag: 1.1.0 Requires PHP: 5.6 Donate link: https://www.paypal.me/iamsayan/ License: GPLv3 @@ -95,6 +95,14 @@ Yes, our plugins work independently of themes you are using. As long as your web If you like Advanced Cron Scheduler, please take a moment to [give a 5-star rating](https://wordpress.org/support/plugin/migrate-wp-cron-to-action-scheduler/reviews/#new-post). It helps to keep development and support going strong. Thank you! += 1.1.0 = +Release Date: 25th June, 2023 + +* Added: Option to disable check for past-due actions. +* Tweak: Changed `init` hook to `action_scheduler_init`. +* Updated: Action Scheduler library to v3.6.1. +* Tested up to WordPress 6.3. + = 1.0.9 = Release Date: 15th November, 2022 @@ -157,4 +165,4 @@ Release Date: 2nd March, 2021 * Initial Release. == Upgrade Notice == -Deactivate and Re-activate the plugin after Update process is completed. \ No newline at end of file +Deactivate and Re-activate the plugin after Update process is completed if anything not works. \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php index d11b1b6..ba960e3 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -3,8 +3,21 @@ // autoload.php @generated by Composer if (PHP_VERSION_ID < 50600) { - echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - exit(1); + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, $err); + } elseif (!headers_sent()) { + echo $err; + } + } + trigger_error( + $err, + E_USER_ERROR + ); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index afef3fa..a72151c 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -42,6 +42,9 @@ */ class ClassLoader { + /** @var \Closure(string):void */ + private static $includeFile; + /** @var ?string */ private $vendorDir; @@ -106,6 +109,7 @@ class ClassLoader public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); } /** @@ -425,7 +429,8 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - includeFile($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -555,18 +560,26 @@ private function findFileWithExtension($class, $ext) return false; } -} -/** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - * @private - */ -function includeFile($file) -{ - include $file; + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index c6b54af..51e734a 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -98,7 +98,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } @@ -119,7 +119,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { - $constraint = $parser->parseConstraints($constraint); + $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); @@ -328,7 +328,9 @@ private static function getInstalled() if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } @@ -340,12 +342,17 @@ private static function getInstalled() // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; } else { self::$installed = array(); } } - $installed[] = self::$installed; + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } return $installed; } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 7cf9d19..28b93f5 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -12,7 +12,7 @@ 'ACSWP\\Plugin\\Base\\BaseController' => $baseDir . '/includes/Base/BaseController.php', 'ACSWP\\Plugin\\Base\\Deactivate' => $baseDir . '/includes/Base/Deactivate.php', 'ACSWP\\Plugin\\Base\\Localization' => $baseDir . '/includes/Base/Localization.php', - 'ACSWP\\Plugin\\Core\\AdminBar' => $baseDir . '/includes/Core/AdminBar.php', + 'ACSWP\\Plugin\\Core\\Admin' => $baseDir . '/includes/Core/Admin.php', 'ACSWP\\Plugin\\Core\\Connection' => $baseDir . '/includes/Core/Connection.php', 'ACSWP\\Plugin\\Core\\MigrateActions' => $baseDir . '/includes/Core/MigrateActions.php', 'ACSWP\\Plugin\\Core\\Settings' => $baseDir . '/includes/Core/Settings.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 963ba59..07762cd 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -31,25 +31,18 @@ public static function getLoader() $loader->register(true); - $includeFiles = \Composer\Autoload\ComposerStaticInit89768520ea85c32bebf6f7d5c391bf98::$files; - foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire89768520ea85c32bebf6f7d5c391bf98($fileIdentifier, $file); + $filesToLoad = \Composer\Autoload\ComposerStaticInit89768520ea85c32bebf6f7d5c391bf98::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); } return $loader; } } - -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ -function composerRequire89768520ea85c32bebf6f7d5c391bf98($fileIdentifier, $file) -{ - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - - require $file; - } -} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 983e597..59538c2 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -31,7 +31,7 @@ class ComposerStaticInit89768520ea85c32bebf6f7d5c391bf98 'ACSWP\\Plugin\\Base\\BaseController' => __DIR__ . '/../..' . '/includes/Base/BaseController.php', 'ACSWP\\Plugin\\Base\\Deactivate' => __DIR__ . '/../..' . '/includes/Base/Deactivate.php', 'ACSWP\\Plugin\\Base\\Localization' => __DIR__ . '/../..' . '/includes/Base/Localization.php', - 'ACSWP\\Plugin\\Core\\AdminBar' => __DIR__ . '/../..' . '/includes/Core/AdminBar.php', + 'ACSWP\\Plugin\\Core\\Admin' => __DIR__ . '/../..' . '/includes/Core/Admin.php', 'ACSWP\\Plugin\\Core\\Connection' => __DIR__ . '/../..' . '/includes/Core/Connection.php', 'ACSWP\\Plugin\\Core\\MigrateActions' => __DIR__ . '/../..' . '/includes/Core/MigrateActions.php', 'ACSWP\\Plugin\\Core\\Settings' => __DIR__ . '/../..' . '/includes/Core/Settings.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 8c5399a..13982b8 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "woocommerce/action-scheduler", - "version": "3.5.3", - "version_normalized": "3.5.3.0", + "version": "3.6.1", + "version_normalized": "3.6.1.0", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", - "reference": "63ef5af013ca3a6efdd8ef8e9363ac70778713cb" + "reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/63ef5af013ca3a6efdd8ef8e9363ac70778713cb", - "reference": "63ef5af013ca3a6efdd8ef8e9363ac70778713cb", + "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/7fd383cad3d64b419ec81bcd05bab44355a6e6ef", + "reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef", "shasum": "" }, "require-dev": { @@ -21,7 +21,7 @@ "wp-cli/wp-cli": "~2.5.0", "yoast/phpunit-polyfills": "^1.0" }, - "time": "2022-11-09T17:45:19+00:00", + "time": "2023-06-14T19:23:12+00:00", "type": "wordpress-plugin", "extra": { "scripts-description": { @@ -39,7 +39,7 @@ "homepage": "https://actionscheduler.org/", "support": { "issues": "https://github.com/woocommerce/action-scheduler/issues", - "source": "https://github.com/woocommerce/action-scheduler/tree/3.5.3" + "source": "https://github.com/woocommerce/action-scheduler/tree/3.6.1" }, "install-path": "../woocommerce/action-scheduler" } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 2544fbe..79072b5 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'iamsayan/advanced-cron-scheduler', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '58d737fb147940028f308df9b00c011e4c720fae', + 'reference' => '78cc9df48327ea0b1c7d18c8296aaa3af5731ed5', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,16 +13,16 @@ 'iamsayan/advanced-cron-scheduler' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '58d737fb147940028f308df9b00c011e4c720fae', + 'reference' => '78cc9df48327ea0b1c7d18c8296aaa3af5731ed5', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'woocommerce/action-scheduler' => array( - 'pretty_version' => '3.5.3', - 'version' => '3.5.3.0', - 'reference' => '63ef5af013ca3a6efdd8ef8e9363ac70778713cb', + 'pretty_version' => '3.6.1', + 'version' => '3.6.1.0', + 'reference' => '7fd383cad3d64b419ec81bcd05bab44355a6e6ef', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../woocommerce/action-scheduler', 'aliases' => array(), diff --git a/vendor/woocommerce/action-scheduler/README.md b/vendor/woocommerce/action-scheduler/README.md new file mode 100644 index 0000000..bdfa2a6 --- /dev/null +++ b/vendor/woocommerce/action-scheduler/README.md @@ -0,0 +1,35 @@ +# Action Scheduler - Job Queue for WordPress [![Build Status](https://travis-ci.org/woocommerce/action-scheduler.png?branch=master)](https://travis-ci.org/woocommerce/action-scheduler) [![codecov](https://codecov.io/gh/woocommerce/action-scheduler/branch/master/graph/badge.svg)](https://codecov.io/gh/woocommerce/action-scheduler) + +Action Scheduler is a scalable, traceable job queue for background processing large sets of actions in WordPress. It's specially designed to be distributed in WordPress plugins. + +Action Scheduler works by triggering an action hook to run at some time in the future. Each hook can be scheduled with unique data, to allow callbacks to perform operations on that data. The hook can also be scheduled to run on one or more occassions. + +Think of it like an extension to `do_action()` which adds the ability to delay and repeat a hook. + +## Battle-Tested Background Processing + +Every month, Action Scheduler processes millions of payments for [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/), webhooks for [WooCommerce](https://wordpress.org/plugins/woocommerce/), as well as emails and other events for a range of other plugins. + +It's been seen on live sites processing queues in excess of 50,000 jobs and doing resource intensive operations, like processing payments and creating orders, at a sustained rate of over 10,000 / hour without negatively impacting normal site operations. + +This is all on infrastructure and WordPress sites outside the control of the plugin author. + +If your plugin needs background processing, especially of large sets of tasks, Action Scheduler can help. + +## Learn More + +To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org). + +There you will find: + +* [Usage guide](https://actionscheduler.org/usage/): instructions on installing and using Action Scheduler +* [WP CLI guide](https://actionscheduler.org/wp-cli/): instructions on running Action Scheduler at scale via WP CLI +* [API Reference](https://actionscheduler.org/api/): complete reference guide for all API functions +* [Administration Guide](https://actionscheduler.org/admin/): guide to managing scheduled actions via the administration screen +* [Guide to Background Processing at Scale](https://actionscheduler.org/perf/): instructions for running Action Scheduler at scale via the default WP Cron queue runner + +## Credits + +Action Scheduler is developed and maintained by [Automattic](http://automattic.com/) with significant early development completed by [Flightless](https://flightless.us/). + +Collaboration is cool. We'd love to work with you to improve Action Scheduler. [Pull Requests](https://github.com/woocommerce/action-scheduler/pulls) welcome. diff --git a/vendor/woocommerce/action-scheduler/action-scheduler.php b/vendor/woocommerce/action-scheduler/action-scheduler.php index 6955c72..2bee49c 100644 --- a/vendor/woocommerce/action-scheduler/action-scheduler.php +++ b/vendor/woocommerce/action-scheduler/action-scheduler.php @@ -5,7 +5,7 @@ * Description: A robust scheduling library for use in WordPress plugins. * Author: Automattic * Author URI: https://automattic.com/ - * Version: 3.5.3 + * Version: 3.6.1 * License: GPLv3 * * Copyright 2019 Automattic, Inc. (https://automattic.com/contact/) @@ -26,27 +26,27 @@ * @package ActionScheduler */ -if ( ! function_exists( 'action_scheduler_register_3_dot_5_dot_3' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION. +if ( ! function_exists( 'action_scheduler_register_3_dot_6_dot_1' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION. if ( ! class_exists( 'ActionScheduler_Versions', false ) ) { require_once __DIR__ . '/classes/ActionScheduler_Versions.php'; add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 ); } - add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_5_dot_3', 0, 0 ); // WRCS: DEFINED_VERSION. + add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_6_dot_1', 0, 0 ); // WRCS: DEFINED_VERSION. /** * Registers this version of Action Scheduler. */ - function action_scheduler_register_3_dot_5_dot_3() { // WRCS: DEFINED_VERSION. + function action_scheduler_register_3_dot_6_dot_1() { // WRCS: DEFINED_VERSION. $versions = ActionScheduler_Versions::instance(); - $versions->register( '3.5.3', 'action_scheduler_initialize_3_dot_5_dot_3' ); // WRCS: DEFINED_VERSION. + $versions->register( '3.6.1', 'action_scheduler_initialize_3_dot_6_dot_1' ); // WRCS: DEFINED_VERSION. } /** * Initializes this version of Action Scheduler. */ - function action_scheduler_initialize_3_dot_5_dot_3() { // WRCS: DEFINED_VERSION. + function action_scheduler_initialize_3_dot_6_dot_1() { // WRCS: DEFINED_VERSION. // A final safety check is required even here, because historic versions of Action Scheduler // followed a different pattern (in some unusual cases, we could reach this point and the // ActionScheduler class is already defined—so we need to guard against that). @@ -58,7 +58,7 @@ function action_scheduler_initialize_3_dot_5_dot_3() { // WRCS: DEFINED_VERSION. // Support usage in themes - load this version if no plugin has loaded a version yet. if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) { - action_scheduler_initialize_3_dot_5_dot_3(); // WRCS: DEFINED_VERSION. + action_scheduler_initialize_3_dot_6_dot_1(); // WRCS: DEFINED_VERSION. do_action( 'action_scheduler_pre_theme_init' ); ActionScheduler_Versions::initialize_latest_version(); } diff --git a/vendor/woocommerce/action-scheduler/changelog.txt b/vendor/woocommerce/action-scheduler/changelog.txt new file mode 100644 index 0000000..7ce001c --- /dev/null +++ b/vendor/woocommerce/action-scheduler/changelog.txt @@ -0,0 +1,112 @@ +*** Changelog *** + += 3.6.1 - 2023-06-14 = +* Document new optional `$priority` arg for various API functions. +* Document the new `--exclude-groups` WP CLI option. +* Document the new `action_scheduler_init` hook. +* Ensure actions within each claim are executed in the expected order. +* Fix incorrect text domain. +* Remove SHOW TABLES usage when checking if tables exist. + += 3.6.0 - 2023-05-10 = +* Add $unique parameter to function signatures. +* Add a cast-to-int for extra safety before forming new DateTime object. +* Add a hook allowing exceptions for consistently failing recurring actions. +* Add action priorities. +* Add init hook. +* Always raise the time limit. +* Bump minimatch from 3.0.4 to 3.0.8. +* Bump yaml from 2.2.1 to 2.2.2. +* Defensive coding relating to gaps in declared schedule types. +* Do not process an action if it cannot be set to `in-progress`. +* Filter view labels (status names) should be translatable | #919. +* Fix WPCLI progress messages. +* Improve data-store initialization flow. +* Improve error handling across all supported PHP versions. +* Improve logic for flushing the runtime cache. +* Support exclusion of multiple groups. +* Update lint-staged and Node/NPM requirements. +* add CLI clean command. +* add CLI exclude-group filter. +* exclude past-due from list table all filter count. +* throwing an exception if as_schedule_recurring_action interval param is not of type integer. + += 3.5.4 - 2023-01-17 = +* Add pre filters during action registration. +* Async scheduling. +* Calculate timeouts based on total actions. +* Correctly order the parameters for `ActionScheduler_ActionFactory`'s calls to `single_unique`. +* Fetch action in memory first before releasing claim to avoid deadlock. +* PHP 8.2: declare property to fix creation of dynamic property warning. +* PHP 8.2: fix "Using ${var} in strings is deprecated, use {$var} instead". +* Prevent `undefined variable` warning for `$num_pastdue_actions`. + += 3.5.3 - 2022-11-09 = +* Query actions with partial match. + += 3.5.2 - 2022-09-16 = +* Fix - erroneous 3.5.1 release. + += 3.5.1 - 2022-09-13 = +* Maintenance on A/S docs. +* fix: PHP 8.2 deprecated notice. + += 3.5.0 - 2022-08-25 = +* Add - The active view link within the "Tools > Scheduled Actions" screen is now clickable. +* Add - A warning when there are past-due actions. +* Enhancement - Added the ability to schedule unique actions via an atomic operation. +* Enhancement - Improvements to cache invalidation when processing batches (when running on WordPress 6.0+). +* Enhancement - If a recurring action is found to be consistently failing, it will stop being rescheduled. +* Enhancement - Adds a new "Past Due" view to the scheduled actions list table. + += 3.4.2 - 2022-06-08 = +* Fix - Change the include for better linting. +* Fix - update: Added Action scheduler completed action hook. + += 3.4.1 - 2022-05-24 = +* Fix - Change the include for better linting. +* Fix - Fix the documented return type. + += 3.4.0 - 2021-10-29 = +* Enhancement - Number of items per page can now be set for the Scheduled Actions view (props @ovidiul). #771 +* Fix - Do not lower the max_execution_time if it is already set to 0 (unlimited) (props @barryhughes). #755 +* Fix - Avoid triggering autoloaders during the version resolution process (props @olegabr). #731 & #776 +* Dev - ActionScheduler_wcSystemStatus PHPCS fixes (props @ovidiul). #761 +* Dev - ActionScheduler_DBLogger.php PHPCS fixes (props @ovidiul). #768 +* Dev - Fixed phpcs for ActionScheduler_Schedule_Deprecated (props @ovidiul). #762 +* Dev - Improve actions table indicies (props @glagonikas). #774 & #777 +* Dev - PHPCS fixes for ActionScheduler_DBStore.php (props @ovidiul). #769 & #778 +* Dev - PHPCS Fixes for ActionScheduler_Abstract_ListTable (props @ovidiul). #763 & #779 +* Dev - Adds new filter action_scheduler_claim_actions_order_by to allow tuning of the claim query (props @glagonikas). #773 +* Dev - PHPCS fixes for ActionScheduler_WpPostStore class (props @ovidiul). #780 + += 3.3.0 - 2021-09-15 = +* Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645 +* Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519 +* Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645 +* Dev - Now supports queries that use multiple statuses. #649 +* Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723 + += 3.2.1 - 2021-06-21 = +* Fix - Add extra safety/account for different versions of AS and different loading patterns. #714 +* Fix - Handle hidden columns (Tools → Scheduled Actions) | #600. + += 3.2.0 - 2021-06-03 = +* Fix - Add "no ordering" option to as_next_scheduled_action(). +* Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634. +* Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634. +* Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas). +* Fix - Fix unit tests infrastructure and adapt tests to PHP 8. +* Fix - Identify in-use data store. +* Fix - Improve test_migration_is_scheduled. +* Fix - PHP notice on list table. +* Fix - Speed up clean up and batch selects. +* Fix - Update pending dependencies. +* Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array(). +* Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility. +* Fix - add is_initialized() to docs. +* Fix - fix file permissions. +* Fix - fixes #664 by replacing __ with esc_html__. + += 3.1.6 - 2020-05-12 = +* Change log starts. diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionFactory.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionFactory.php index 52fa658..2fd46a7 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionFactory.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ActionFactory.php @@ -13,10 +13,14 @@ class ActionScheduler_ActionFactory { * @param array $args Args to pass to callbacks when the hook is triggered. * @param ActionScheduler_Schedule $schedule The action's schedule. * @param string $group A group to put the action in. + * @param int $priority The action priority. * * @return ActionScheduler_Action An instance of the stored action. */ public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) { + // The 6th parameter ($priority) is not formally declared in the method signature to maintain compatibility with + // third-party subclasses created before this param was added. + $priority = func_num_args() >= 6 ? (int) func_get_arg( 5 ) : 10; switch ( $status ) { case ActionScheduler_Store::STATUS_PENDING: @@ -36,28 +40,27 @@ public function get_stored_action( $status, $hook, array $args = array(), Action $action_class = apply_filters( 'action_scheduler_stored_action_class', $action_class, $status, $hook, $args, $schedule, $group ); $action = new $action_class( $hook, $args, $schedule, $group ); + $action->set_priority( $priority ); /** * Allow 3rd party code to change the instantiated action for a given hook, args, schedule and group. * - * @param ActionScheduler_Action $action The instantiated action. - * @param string $hook The instantiated action's hook. - * @param array $args The instantiated action's args. + * @param ActionScheduler_Action $action The instantiated action. + * @param string $hook The instantiated action's hook. + * @param array $args The instantiated action's args. * @param ActionScheduler_Schedule $schedule The instantiated action's schedule. - * @param string $group The instantiated action's group. + * @param string $group The instantiated action's group. + * @param int $priority The action priority. */ - return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group ); + return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group, $priority ); } /** * Enqueue an action to run one time, as soon as possible (rather a specific scheduled time). * - * This method creates a new action with the NULLSchedule. This schedule maps to a MySQL datetime string of - * 0000-00-00 00:00:00. This is done to create a psuedo "async action" type that is fully backward compatible. - * Existing queries to claim actions claim by date, meaning actions scheduled for 0000-00-00 00:00:00 will - * always be claimed prior to actions scheduled for a specific date. This makes sure that any async action is - * given priority in queue processing. This has the added advantage of making sure async actions can be - * claimed by both the existing WP Cron and WP CLI runners, as well as a new async request runner. + * This method creates a new action using the NullSchedule. In practice, this results in an action scheduled to + * execute "now". Therefore, it will generally run as soon as possible but is not prioritized ahead of other actions + * that are already past-due. * * @param string $hook The hook to trigger when this action runs. * @param array $args Args to pass when the hook is triggered. @@ -146,7 +149,7 @@ public function recurring( $hook, $args = array(), $first = null, $interval = nu */ public function recurring_unique( $hook, $args = array(), $first = null, $interval = null, $group = '', $unique = true ) { if ( empty( $interval ) ) { - return $this->single_unique( $hook, $unique, $args, $first, $group ); + return $this->single_unique( $hook, $args, $first, $group, $unique ); } $date = as_get_datetime_object( $first ); $schedule = new ActionScheduler_IntervalSchedule( $date, $interval ); @@ -188,7 +191,7 @@ public function cron( $hook, $args = array(), $base_timestamp = null, $schedule **/ public function cron_unique( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '', $unique = true ) { if ( empty( $schedule ) ) { - return $this->single_unique( $hook, $unique, $args, $base_timestamp, $group ); + return $this->single_unique( $hook, $args, $base_timestamp, $group, $unique ); } $date = as_get_datetime_object( $base_timestamp ); $cron = CronExpression::factory( $schedule ); @@ -232,9 +235,86 @@ public function repeat( $action ) { $schedule_class = get_class( $schedule ); $new_schedule = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() ); $new_action = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() ); + $new_action->set_priority( $action->get_priority() ); return $this->store( $new_action ); } + /** + * Creates a scheduled action. + * + * This general purpose method can be used in place of specific methods such as async(), + * async_unique(), single() or single_unique(), etc. + * + * @internal Not intended for public use, should not be overriden by subclasses. + * @throws Exception May be thrown if invalid options are passed. + * + * @param array $options { + * Describes the action we wish to schedule. + * + * @type string $type Must be one of 'async', 'cron', 'recurring', or 'single'. + * @type string $hook The hook to be executed. + * @type array $arguments Arguments to be passed to the callback. + * @type string $group The action group. + * @type bool $unique If the action should be unique. + * @type int $when Timestamp. Indicates when the action, or first instance of the action in the case + * of recurring or cron actions, becomes due. + * @type int|string $pattern Recurrence pattern. This is either an interval in seconds for recurring actions + * or a cron expression for cron actions. + * @type int $priority Lower values means higher priority. Should be in the range 0-255. + * } + * + * @return int + */ + public function create( array $options = array() ) { + $defaults = array( + 'type' => 'single', + 'hook' => '', + 'arguments' => array(), + 'group' => '', + 'unique' => false, + 'when' => time(), + 'pattern' => null, + 'priority' => 10, + ); + + $options = array_merge( $defaults, $options ); + + // Cron/recurring actions without a pattern are treated as single actions (this gives calling code the ability + // to use functions like as_schedule_recurring_action() to schedule recurring as well as single actions). + if ( ( 'cron' === $options['type'] || 'recurring' === $options['type'] ) && empty( $options['pattern'] ) ) { + $options['type'] = 'single'; + } + + switch ( $options['type'] ) { + case 'async': + $schedule = new ActionScheduler_NullSchedule(); + break; + + case 'cron': + $date = as_get_datetime_object( $options['when'] ); + $cron = CronExpression::factory( $options['pattern'] ); + $schedule = new ActionScheduler_CronSchedule( $date, $cron ); + break; + + case 'recurring': + $date = as_get_datetime_object( $options['when'] ); + $schedule = new ActionScheduler_IntervalSchedule( $date, $options['pattern'] ); + break; + + case 'single': + $date = as_get_datetime_object( $options['when'] ); + $schedule = new ActionScheduler_SimpleSchedule( $date ); + break; + + default: + throw new Exception( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." ); + } + + $action = new ActionScheduler_Action( $options['hook'], $options['arguments'], $schedule, $options['group'] ); + $action->set_priority( $options['priority'] ); + return $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action ); + } + /** * Save action to database. * diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_AdminView.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_AdminView.php index 7648594..b747b0a 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_AdminView.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_AdminView.php @@ -147,9 +147,17 @@ protected function check_pastdue_actions() { $threshold_seconds = ( int ) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS ); $threshhold_min = ( int ) apply_filters( 'action_scheduler_pastdue_actions_min', 1 ); - # Allow third-parties to preempt the default check logic. + // Set fallback value for past-due actions count. + $num_pastdue_actions = 0; + + // Allow third-parties to preempt the default check logic. $check = apply_filters( 'action_scheduler_pastdue_actions_check_pre', null ); + // If no third-party preempted and there are no past-due actions, return early. + if ( ! is_null( $check ) ) { + return; + } + # Scheduled actions query arguments. $query_args = array( 'date' => as_get_datetime_object( time() - $threshold_seconds ), diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_Compatibility.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_Compatibility.php index 85e0ed9..bb28023 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_Compatibility.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_Compatibility.php @@ -4,7 +4,6 @@ * Class ActionScheduler_Compatibility */ class ActionScheduler_Compatibility { - /** * Converts a shorthand byte value to an integer byte value. * @@ -89,21 +88,18 @@ public static function raise_time_limit( $limit = 0 ) { $limit = (int) $limit; $max_execution_time = (int) ini_get( 'max_execution_time' ); - /* - * If the max execution time is already unlimited (zero), or if it exceeds or is equal to the proposed - * limit, there is no reason for us to make further changes (we never want to lower it). - */ - if ( - 0 === $max_execution_time - || ( $max_execution_time >= $limit && $limit !== 0 ) - ) { + // If the max execution time is already set to zero (unlimited), there is no reason to make a further change. + if ( 0 === $max_execution_time ) { return; } + // Whichever of $max_execution_time or $limit is higher is the amount by which we raise the time limit. + $raise_by = 0 === $limit || $limit > $max_execution_time ? $limit : $max_execution_time; + if ( function_exists( 'wc_set_time_limit' ) ) { - wc_set_time_limit( $limit ); + wc_set_time_limit( $raise_by ); } 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 - @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + @set_time_limit( $raise_by ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } } diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ListTable.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ListTable.php index 9e631f7..8d16815 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ListTable.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_ListTable.php @@ -252,7 +252,7 @@ private static function human_interval( $interval, $periods_to_include = 2 ) { */ protected function get_recurrence( $action ) { $schedule = $action->get_schedule(); - if ( $schedule->is_recurring() ) { + if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) { $recurrence = $schedule->get_recurrence(); if ( is_numeric( $recurrence ) ) { @@ -471,7 +471,7 @@ protected function get_schedule_display_string( ActionScheduler_Schedule $schedu return __( 'async', 'action-scheduler' ); } - if ( ! $schedule->get_date() ) { + if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) { return '0000-00-00 00:00:00'; } diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueCleaner.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueCleaner.php index 49cd44b..6f2a696 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueCleaner.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueCleaner.php @@ -18,6 +18,14 @@ class ActionScheduler_QueueCleaner { */ private $month_in_seconds = 2678400; + /** + * @var string[] Default list of statuses purged by the cleaner process. + */ + private $default_statuses_to_purge = [ + ActionScheduler_Store::STATUS_COMPLETE, + ActionScheduler_Store::STATUS_CANCELED, + ]; + /** * ActionScheduler_QueueCleaner constructor. * @@ -29,46 +37,113 @@ public function __construct( ActionScheduler_Store $store = null, $batch_size = $this->batch_size = $batch_size; } + /** + * Default queue cleaner process used by queue runner. + * + * @return array + */ public function delete_old_actions() { + /** + * Filter the minimum scheduled date age for action deletion. + * + * @param int $retention_period Minimum scheduled age in seconds of the actions to be deleted. + */ $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds ); - $cutoff = as_get_datetime_object($lifespan.' seconds ago'); - $statuses_to_purge = array( - ActionScheduler_Store::STATUS_COMPLETE, - ActionScheduler_Store::STATUS_CANCELED, - ); + try { + $cutoff = as_get_datetime_object( $lifespan . ' seconds ago' ); + } catch ( Exception $e ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* Translators: %s is the exception message. */ + esc_html__( 'It was not possible to determine a valid cut-off time: %s.', 'action-scheduler' ), + esc_html( $e->getMessage() ) + ), + '3.5.5' + ); + + return array(); + } + + + /** + * Filter the statuses when cleaning the queue. + * + * @param string[] $default_statuses_to_purge Action statuses to clean. + */ + $statuses_to_purge = (array) apply_filters( 'action_scheduler_default_cleaner_statuses', $this->default_statuses_to_purge ); + + return $this->clean_actions( $statuses_to_purge, $cutoff, $this->get_batch_size() ); + } + + /** + * Delete selected actions limited by status and date. + * + * @param string[] $statuses_to_purge List of action statuses to purge. Defaults to canceled, complete. + * @param DateTime $cutoff_date Date limit for selecting actions. Defaults to 31 days ago. + * @param int|null $batch_size Maximum number of actions per status to delete. Defaults to 20. + * @param string $context Calling process context. Defaults to `old`. + * @return array Actions deleted. + */ + public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) { + $batch_size = $batch_size !== null ? $batch_size : $this->batch_size; + $cutoff = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' ); + $lifespan = time() - $cutoff->getTimestamp(); + if ( empty( $statuses_to_purge ) ) { + $statuses_to_purge = $this->default_statuses_to_purge; + } + $deleted_actions = []; foreach ( $statuses_to_purge as $status ) { $actions_to_delete = $this->store->query_actions( array( 'status' => $status, 'modified' => $cutoff, 'modified_compare' => '<=', - 'per_page' => $this->get_batch_size(), + 'per_page' => $batch_size, 'orderby' => 'none', ) ); - foreach ( $actions_to_delete as $action_id ) { - try { - $this->store->delete_action( $action_id ); - } catch ( Exception $e ) { - - /** - * Notify 3rd party code of exceptions when deleting a completed action older than the retention period - * - * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their - * actions. - * - * @since 2.0.0 - * - * @param int $action_id The scheduled actions ID in the data store - * @param Exception $e The exception thrown when attempting to delete the action from the data store - * @param int $lifespan The retention period, in seconds, for old actions - * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch - */ - do_action( 'action_scheduler_failed_old_action_deletion', $action_id, $e, $lifespan, count( $actions_to_delete ) ); - } + $deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) ); + } + + return $deleted_actions; + } + + /** + * @param int[] $actions_to_delete List of action IDs to delete. + * @param int $lifespan Minimum scheduled age in seconds of the actions being deleted. + * @param string $context Context of the delete request. + * @return array Deleted action IDs. + */ + private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) { + $deleted_actions = []; + if ( $lifespan === null ) { + $lifespan = $this->month_in_seconds; + } + + foreach ( $actions_to_delete as $action_id ) { + try { + $this->store->delete_action( $action_id ); + $deleted_actions[] = $action_id; + } catch ( Exception $e ) { + /** + * Notify 3rd party code of exceptions when deleting a completed action older than the retention period + * + * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their + * actions. + * + * @param int $action_id The scheduled actions ID in the data store + * @param Exception $e The exception thrown when attempting to delete the action from the data store + * @param int $lifespan The retention period, in seconds, for old actions + * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch + * @since 2.0.0 + * + */ + do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) ); } } + return $deleted_actions; } /** diff --git a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueRunner.php b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueRunner.php index 9e8f14e..96925b2 100644 --- a/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueRunner.php +++ b/vendor/woocommerce/action-scheduler/classes/ActionScheduler_QueueRunner.php @@ -14,6 +14,9 @@ class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner { /** @var ActionScheduler_QueueRunner */ private static $runner = null; + /** @var int */ + private $processed_actions_count = 0; + /** * @return ActionScheduler_QueueRunner * @codeCoverageIgnore @@ -125,17 +128,18 @@ public function run( $context = 'WP Cron' ) { ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() ); do_action( 'action_scheduler_before_process_queue' ); $this->run_cleanup(); - $processed_actions = 0; + + $this->processed_actions_count = 0; if ( false === $this->has_maximum_concurrent_batches() ) { $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 ); do { - $processed_actions_in_batch = $this->do_batch( $batch_size, $context ); - $processed_actions += $processed_actions_in_batch; - } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $processed_actions ) ); // keep going until we run out of actions, time, or memory + $processed_actions_in_batch = $this->do_batch( $batch_size, $context ); + $this->processed_actions_count += $processed_actions_in_batch; + } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory } do_action( 'action_scheduler_after_process_queue' ); - return $processed_actions; + return $this->processed_actions_count; } /** @@ -162,7 +166,7 @@ protected function do_batch( $size = 100, $context = '' ) { $this->process_action( $action_id, $context ); $processed_actions++; - if ( $this->batch_limits_exceeded( $processed_actions ) ) { + if ( $this->batch_limits_exceeded( $processed_actions + $this->processed_actions_count ) ) { break; } } @@ -181,9 +185,15 @@ protected function do_batch( $size = 100, $context = '' ) { protected function clear_caches() { /* * Calling wp_cache_flush_runtime() lets us clear the runtime cache without invalidating the external object - * cache, so we will always prefer this when it is available (but it was only introduced in WordPress 6.0). + * cache, so we will always prefer this method (as compared to calling wp_cache_flush()) when it is available. + * + * However, this function was only introduced in WordPress 6.0. Additionally, the preferred way of detecting if + * it is supported changed in WordPress 6.1 so we use two different methods to decide if we should utilize it. */ - if ( function_exists( 'wp_cache_flush_runtime' ) ) { + $flushing_runtime_cache_explicitly_supported = function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_runtime' ); + $flushing_runtime_cache_implicitly_supported = ! function_exists( 'wp_cache_supports' ) && function_exists( 'wp_cache_flush_runtime' ); + + if ( $flushing_runtime_cache_explicitly_supported || $flushing_runtime_cache_implicitly_supported ) { wp_cache_flush_runtime(); } elseif ( ! wp_using_ext_object_cache() diff --git a/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php new file mode 100644 index 0000000..ff6e57a --- /dev/null +++ b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Clean_Command.php @@ -0,0 +1,125 @@ +] + * : The maximum number of actions to delete per batch. Defaults to 20. + * + * [--batches=] + * : Limit execution to a number of batches. Defaults to 0, meaning batches will continue all eligible actions are deleted. + * + * [--status=] + * : Only clean actions with the specified status. Defaults to Canceled, Completed. Define multiple statuses as a comma separated string (without spaces), e.g. `--status=complete,failed,canceled` + * + * [--before=] + * : Only delete actions with scheduled date older than this. Defaults to 31 days. e.g `--before='7 days ago'`, `--before='02-Feb-2020 20:20:20'` + * + * [--pause=] + * : The number of seconds to pause between batches. Default no pause. + * + * @param array $args Positional arguments. + * @param array $assoc_args Keyed arguments. + * @throws \WP_CLI\ExitException When an error occurs. + * + * @subcommand clean + */ + public function clean( $args, $assoc_args ) { + // Handle passed arguments. + $batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 20 ) ); + $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) ); + $status = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'status', '' ) ); + $status = array_filter( array_map( 'trim', $status ) ); + $before = \WP_CLI\Utils\get_flag_value( $assoc_args, 'before', '' ); + $sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 ); + + $batches_completed = 0; + $actions_deleted = 0; + $unlimited = $batches === 0; + try { + $lifespan = as_get_datetime_object( $before ); + } catch ( Exception $e ) { + $lifespan = null; + } + + try { + // Custom queue cleaner instance. + $cleaner = new ActionScheduler_QueueCleaner( null, $batch ); + + // Clean actions for as long as possible. + while ( $unlimited || $batches_completed < $batches ) { + if ( $sleep && $batches_completed > 0 ) { + sleep( $sleep ); + } + + $deleted = count( $cleaner->clean_actions( $status, $lifespan, null,'CLI' ) ); + if ( $deleted <= 0 ) { + break; + } + $actions_deleted += $deleted; + $batches_completed++; + $this->print_success( $deleted ); + } + } catch ( Exception $e ) { + $this->print_error( $e ); + } + + $this->print_total_batches( $batches_completed ); + if ( $batches_completed > 1 ) { + $this->print_success( $actions_deleted ); + } + } + + /** + * Print WP CLI message about how many batches of actions were processed. + * + * @param int $batches_processed + */ + protected function print_total_batches( int $batches_processed ) { + WP_CLI::log( + sprintf( + /* translators: %d refers to the total number of batches processed */ + _n( '%d batch processed.', '%d batches processed.', $batches_processed, 'action-scheduler' ), + $batches_processed + ) + ); + } + + /** + * Convert an exception into a WP CLI error. + * + * @param Exception $e The error object. + * + * @throws \WP_CLI\ExitException + */ + protected function print_error( Exception $e ) { + WP_CLI::error( + sprintf( + /* translators: %s refers to the exception error message */ + __( 'There was an error deleting an action: %s', 'action-scheduler' ), + $e->getMessage() + ) + ); + } + + /** + * Print a success message with the number of completed actions. + * + * @param int $actions_deleted + */ + protected function print_success( int $actions_deleted ) { + WP_CLI::success( + sprintf( + /* translators: %d refers to the total number of actions deleted */ + _n( '%d action deleted.', '%d actions deleted.', $actions_deleted, 'action-scheduler' ), + $actions_deleted + ) + ); + } +} diff --git a/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php index c33de68..4681daa 100644 --- a/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php +++ b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_QueueRunner.php @@ -90,7 +90,7 @@ protected function setup_progress_bar() { $count = count( $this->actions ); $this->progress_bar = new ProgressBar( /* translators: %d: amount of actions */ - sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), number_format_i18n( $count ) ), + sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), $count ), $count ); } diff --git a/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php index 70b052e..2c68a38 100644 --- a/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php +++ b/vendor/woocommerce/action-scheduler/classes/WP_CLI/ActionScheduler_WPCLI_Scheduler_command.php @@ -55,6 +55,9 @@ public function fix_schema( $args, $assoc_args ) { * [--group=] * : Only run actions from the specified group. Omitting this option runs actions from all groups. * + * [--exclude-groups=] + * : 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. + * * [--free-memory-on=] * : The number of actions to process between freeing memory. 0 disables freeing memory. Default 50. * @@ -72,15 +75,16 @@ public function fix_schema( $args, $assoc_args ) { */ public function run( $args, $assoc_args ) { // Handle passed arguments. - $batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) ); - $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) ); - $clean = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) ); - $hooks = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) ); - $hooks = array_filter( array_map( 'trim', $hooks ) ); - $group = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' ); - $free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 ); - $sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 ); - $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false ); + $batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) ); + $batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) ); + $clean = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) ); + $hooks = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) ); + $hooks = array_filter( array_map( 'trim', $hooks ) ); + $group = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' ); + $exclude_groups = \WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude-groups', '' ); + $free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 ); + $sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 ); + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false ); ActionScheduler_DataController::set_free_ticks( $free_on ); ActionScheduler_DataController::set_sleep_time( $sleep ); @@ -88,6 +92,13 @@ public function run( $args, $assoc_args ) { $batches_completed = 0; $actions_completed = 0; $unlimited = $batches === 0; + if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) { + $exclude_groups = $this->parse_comma_separated_string( $exclude_groups ); + + if ( ! empty( $exclude_groups ) ) { + ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups ); + } + } try { // Custom queue cleaner instance. @@ -116,6 +127,17 @@ public function run( $args, $assoc_args ) { $this->print_success( $actions_completed ); } + /** + * Converts a string of comma-separated values into an array of those same values. + * + * @param string $string The string of one or more comma separated values. + * + * @return array + */ + private function parse_comma_separated_string( $string ): array { + return array_filter( str_getcsv( $string ) ); + } + /** * Print WP CLI message about how many actions are about to be processed. * @@ -126,9 +148,9 @@ public function run( $args, $assoc_args ) { protected function print_total_actions( $total ) { WP_CLI::log( sprintf( - /* translators: %d refers to how many scheduled taks were found to run */ + /* translators: %d refers to how many scheduled tasks were found to run */ _n( 'Found %d scheduled task', 'Found %d scheduled tasks', $total, 'action-scheduler' ), - number_format_i18n( $total ) + $total ) ); } @@ -145,7 +167,7 @@ protected function print_total_batches( $batches_completed ) { sprintf( /* translators: %d refers to the total number of batches executed */ _n( '%d batch executed.', '%d batches executed.', $batches_completed, 'action-scheduler' ), - number_format_i18n( $batches_completed ) + $batches_completed ) ); } @@ -179,9 +201,9 @@ protected function print_error( Exception $e ) { protected function print_success( $actions_completed ) { WP_CLI::success( sprintf( - /* translators: %d refers to the total number of taskes completed */ + /* translators: %d refers to the total number of tasks completed */ _n( '%d scheduled task completed.', '%d scheduled tasks completed.', $actions_completed, 'action-scheduler' ), - number_format_i18n( $actions_completed ) + $actions_completed ) ); } diff --git a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php index e8873f1..8a0109e 100644 --- a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php +++ b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php @@ -153,11 +153,41 @@ public static function init( $plugin_file ) { add_action( 'init', array( $store, 'init' ), 1, 0 ); add_action( 'init', array( $logger, 'init' ), 1, 0 ); add_action( 'init', array( $runner, 'init' ), 1, 0 ); + + add_action( + 'init', + /** + * Runs after the active store's init() method has been called. + * + * It would probably be preferable to have $store->init() (or it's parent method) set this itself, + * once it has initialized, however that would cause problems in cases where a custom data store is in + * use and it has not yet been updated to follow that same logic. + */ + function () { + self::$data_store_initialized = true; + + /** + * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point. + * + * @since 3.5.5 + */ + do_action( 'action_scheduler_init' ); + }, + 1 + ); } else { $admin_view->init(); $store->init(); $logger->init(); $runner->init(); + self::$data_store_initialized = true; + + /** + * Fires when Action Scheduler is ready: it is safe to use the procedural API after this point. + * + * @since 3.5.5 + */ + do_action( 'action_scheduler_init' ); } if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) { @@ -166,14 +196,13 @@ public static function init( $plugin_file ) { if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' ); + WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' ); if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) { $command = new Migration_Command(); $command->register(); } } - self::$data_store_initialized = true; - /** * Handle WP comment cleanup after migration. */ @@ -192,8 +221,12 @@ public static function init( $plugin_file ) { */ public static function is_initialized( $function_name = null ) { if ( ! self::$data_store_initialized && ! empty( $function_name ) ) { - $message = sprintf( __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), esc_attr( $function_name ) ); - error_log( $message, E_WARNING ); + $message = sprintf( + /* translators: %s function name. */ + __( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ), + esc_attr( $function_name ) + ); + error_log( $message ); } return self::$data_store_initialized; diff --git a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php index ccc997f..8d1465f 100644 --- a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php +++ b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php @@ -673,24 +673,34 @@ protected function display_filter_by_status() { // Helper to set 'all' filter when not set on status counts passed in. if ( ! isset( $this->status_counts['all'] ) ) { - $this->status_counts = array( 'all' => array_sum( $this->status_counts ) ) + $this->status_counts; + $all_count = array_sum( $this->status_counts ); + if ( isset( $this->status_counts['past-due'] ) ) { + $all_count -= $this->status_counts['past-due']; + } + $this->status_counts = array( 'all' => $all_count ) + $this->status_counts; } - foreach ( $this->status_counts as $status_name => $count ) { + // Translated status labels. + $status_labels = ActionScheduler_Store::instance()->get_status_labels(); + $status_labels['all'] = _x( 'All', 'status labels', 'action-scheduler' ); + $status_labels['past-due'] = _x( 'Past-due', 'status labels', 'action-scheduler' ); + + foreach ( $this->status_counts as $status_slug => $count ) { if ( 0 === $count ) { continue; } - if ( $status_name === $request_status || ( empty( $request_status ) && 'all' === $status_name ) ) { + if ( $status_slug === $request_status || ( empty( $request_status ) && 'all' === $status_slug ) ) { $status_list_item = '
  • %3$s (%4$d)
  • '; } else { $status_list_item = '
  • %3$s (%4$d)
  • '; } - $status_filter_url = ( 'all' === $status_name ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_name ); + $status_name = isset( $status_labels[ $status_slug ] ) ? $status_labels[ $status_slug ] : ucfirst( $status_slug ); + $status_filter_url = ( 'all' === $status_slug ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_slug ); $status_filter_url = remove_query_arg( array( 'paged', 's' ), $status_filter_url ); - $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_name ), esc_url( $status_filter_url ), esc_html( ucfirst( $status_name ) ), absint( $count ) ); + $status_list_items[] = sprintf( $status_list_item, esc_attr( $status_slug ), esc_url( $status_filter_url ), esc_html( $status_name ), absint( $count ) ); } if ( $status_list_items ) { diff --git a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php index f2d7d9f..2f95702 100644 --- a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php +++ b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php @@ -48,30 +48,56 @@ public function __construct( ActionScheduler_Store $store = null, ActionSchedule * Generally, this should be capitalised and not localised as it's a proper noun. */ public function process_action( $action_id, $context = '' ) { - try { - $valid_action = false; - do_action( 'action_scheduler_before_execute', $action_id, $context ); + // Temporarily override the error handler while we process the current action. + set_error_handler( + /** + * Temporary error handler which can catch errors and convert them into exceptions. This faciliates more + * robust error handling across all supported PHP versions. + * + * @throws Exception + * + * @param int $type Error level expressed as an integer. + * @param string $message Error message. + */ + function ( $type, $message ) { + throw new Exception( $message ); + }, + E_USER_ERROR | E_RECOVERABLE_ERROR + ); - if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) { - do_action( 'action_scheduler_execution_ignored', $action_id, $context ); - return; + /* + * The nested try/catch structure is required because we potentially need to convert thrown errors into + * exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same* + * structure). + */ + try { + try { + $valid_action = false; + do_action( 'action_scheduler_before_execute', $action_id, $context ); + + if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) { + do_action( 'action_scheduler_execution_ignored', $action_id, $context ); + return; + } + + $valid_action = true; + do_action( 'action_scheduler_begin_execute', $action_id, $context ); + + $action = $this->store->fetch_action( $action_id ); + $this->store->log_execution( $action_id ); + $action->execute(); + do_action( 'action_scheduler_after_execute', $action_id, $action, $context ); + $this->store->mark_complete( $action_id ); + } catch ( Throwable $e ) { + // Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for + // compatibility with ActionScheduler_Logger. + throw new Exception( $e->getMessage(), $e->getCode(), $e->getPrevious() ); } - - $valid_action = true; - do_action( 'action_scheduler_begin_execute', $action_id, $context ); - - $action = $this->store->fetch_action( $action_id ); - $this->store->log_execution( $action_id ); - $action->execute(); - do_action( 'action_scheduler_after_execute', $action_id, $action, $context ); - $this->store->mark_complete( $action_id ); } catch ( Exception $e ) { - if ( $valid_action ) { - $this->store->mark_failure( $action_id ); - do_action( 'action_scheduler_failed_execution', $action_id, $e, $context ); - } else { - do_action( 'action_scheduler_failed_validation', $action_id, $e, $context ); - } + // This catch block exists for compatibility with PHP 5.6. + $this->handle_action_error( $action_id, $e, $context, $valid_action ); + } finally { + restore_error_handler(); } if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) { @@ -79,6 +105,39 @@ public function process_action( $action_id, $context = '' ) { } } + /** + * Marks actions as either having failed execution or failed validation, as appropriate. + * + * @param int $action_id Action ID. + * @param Exception $e Exception instance. + * @param string $context Execution context. + * @param bool $valid_action If the action is valid. + * + * @return void + */ + private function handle_action_error( $action_id, $e, $context, $valid_action ) { + if ( $valid_action ) { + $this->store->mark_failure( $action_id ); + /** + * Runs when action execution fails. + * + * @param int $action_id Action ID. + * @param Exception $e Exception instance. + * @param string $context Execution context. + */ + do_action( 'action_scheduler_failed_execution', $action_id, $e, $context ); + } else { + /** + * Runs when action validation fails. + * + * @param int $action_id Action ID. + * @param Exception $e Exception instance. + * @param string $context Execution context. + */ + do_action( 'action_scheduler_failed_validation', $action_id, $e, $context ); + } + } + /** * Schedule the next instance of the action if necessary. * @@ -143,12 +202,22 @@ private function recurring_action_is_consistently_failing( ActionScheduler_Actio return false; } - // Now let's fetch the first action (having the same hook) of *any status*ithin the same window. + // Now let's fetch the first action (having the same hook) of *any status* within the same window. unset( $query_args['status'] ); $first_action_id_with_the_same_hook = $this->store->query_actions( $query_args ); - // If the IDs match, then actions for this hook must be consistently failing. - return $first_action_id_with_the_same_hook === $first_failing_action_id; + /** + * If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a + * way to observe and optionally override that assessment. + * + * @param bool $is_consistently_failing If the action is considered to be consistently failing. + * @param ActionScheduler_Action $action The action being assessed. + */ + return (bool) apply_filters( + 'action_scheduler_recurring_action_is_consistently_failing', + $first_action_id_with_the_same_hook === $first_failing_action_id, + $action + ); } /** @@ -223,9 +292,14 @@ protected function get_execution_time() { * @return bool */ protected function time_likely_to_be_exceeded( $processed_actions ) { + $execution_time = $this->get_execution_time(); + $max_execution_time = $this->get_time_limit(); + + // Safety against division by zero errors. + if ( 0 === $processed_actions ) { + return $execution_time >= $max_execution_time; + } - $execution_time = $this->get_execution_time(); - $max_execution_time = $this->get_time_limit(); $time_per_action = $execution_time / $processed_actions; $estimated_time = $execution_time + ( $time_per_action * 3 ); $likely_to_be_exceeded = $estimated_time > $max_execution_time; diff --git a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php index 2334fda..3fd259e 100644 --- a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php +++ b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php @@ -25,7 +25,7 @@ abstract class ActionScheduler_Abstract_Schema { /** * @var array Names of tables that will be registered by this class. */ - protected $tables = []; + protected $tables = array(); /** * Can optionally be used by concrete classes to carry out additional initialization work @@ -90,10 +90,10 @@ private function schema_update_required() { $plugin_option_name = 'schema-'; switch ( static::class ) { - case 'ActionScheduler_StoreSchema' : + case 'ActionScheduler_StoreSchema': $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker'; break; - case 'ActionScheduler_LoggerSchema' : + case 'ActionScheduler_LoggerSchema': $plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker'; break; } @@ -129,7 +129,7 @@ private function mark_schema_update_complete() { * @return void */ private function update_table( $table ) { - require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $definition = $this->get_table_definition( $table ); if ( $definition ) { $updated = dbDelta( $definition ); @@ -148,7 +148,7 @@ private function update_table( $table ) { * table prefix for the current blog */ protected function get_full_table_name( $table ) { - return $GLOBALS[ 'wpdb' ]->prefix . $table; + return $GLOBALS['wpdb']->prefix . $table; } /** @@ -159,14 +159,19 @@ protected function get_full_table_name( $table ) { public function tables_exist() { global $wpdb; - $existing_tables = $wpdb->get_col( 'SHOW TABLES' ); - $expected_tables = array_map( - function ( $table_name ) use ( $wpdb ) { - return $wpdb->prefix . $table_name; - }, - $this->tables - ); + $tables_exist = true; - return count( array_intersect( $existing_tables, $expected_tables ) ) === count( $expected_tables ); + foreach ( $this->tables as $table_name ) { + $table_name = $wpdb->prefix . $table_name; + $pattern = str_replace( '_', '\\_', $table_name ); + $existing_table = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pattern ) ); + + if ( $existing_table !== $table_name ) { + $tables_exist = false; + break; + } + } + + return $tables_exist; } } diff --git a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php index 6c4093e..a555293 100644 --- a/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php +++ b/vendor/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php @@ -257,7 +257,7 @@ protected function validate_sql_comparator( $comparison_operator ) { protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; if ( ! $next ) { - return '0000-00-00 00:00:00'; + $next = date_create(); } $next->setTimezone( new DateTimeZone( 'UTC' ) ); @@ -274,7 +274,7 @@ protected function get_scheduled_date_string( ActionScheduler_Action $action, Da protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) { $next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date; if ( ! $next ) { - return '0000-00-00 00:00:00'; + $next = date_create(); } ActionScheduler_TimezoneHelper::set_local_timezone( $next ); diff --git a/vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_Action.php b/vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_Action.php index f538f50..ddf33d5 100644 --- a/vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_Action.php +++ b/vendor/woocommerce/action-scheduler/classes/actions/ActionScheduler_Action.php @@ -10,6 +10,19 @@ class ActionScheduler_Action { protected $schedule = NULL; protected $group = ''; + /** + * Priorities are conceptually similar to those used for regular WordPress actions. + * Like those, a lower priority takes precedence over a higher priority and the default + * is 10. + * + * Unlike regular WordPress actions, the priority of a scheduled action is strictly an + * integer and should be kept within the bounds 0-255 (anything outside the bounds will + * be brought back into the acceptable range). + * + * @var int + */ + protected $priority = 10; + public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) { $schedule = empty( $schedule ) ? new ActionScheduler_NullSchedule() : $schedule; $this->set_hook($hook); @@ -93,4 +106,30 @@ public function get_group() { public function is_finished() { return FALSE; } + + /** + * Sets the priority of the action. + * + * @param int $priority Priority level (lower is higher priority). Should be in the range 0-255. + * + * @return void + */ + public function set_priority( $priority ) { + if ( $priority < 0 ) { + $priority = 0; + } elseif ( $priority > 255 ) { + $priority = 255; + } + + $this->priority = (int) $priority; + } + + /** + * Gets the action priority. + * + * @return int + */ + public function get_priority() { + return $this->priority; + } } diff --git a/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php b/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php index c62e7f9..16fc37f 100644 --- a/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php +++ b/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php @@ -25,6 +25,13 @@ class ActionScheduler_DBStore extends ActionScheduler_Store { /** @var int */ protected static $max_index_length = 191; + /** @var array List of claim filters. */ + protected $claim_filters = [ + 'group' => '', + 'hooks' => '', + 'exclude-groups' => '', + ]; + /** * Initialize the data store * @@ -84,7 +91,8 @@ private function save_action_to_db( ActionScheduler_Action $action, DateTime $da 'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ), 'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ), 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize - 'group_id' => $this->get_group_id( $action->get_group() ), + 'group_id' => current( $this->get_group_ids( $action->get_group() ) ), + 'priority' => $action->get_priority(), ); $args = wp_json_encode( $action->get_args() ); @@ -172,6 +180,7 @@ private function build_where_clause_for_insert( $data, $table_name, $unique ) { ActionScheduler_Store::STATUS_RUNNING, ); $pending_status_placeholders = implode( ', ', array_fill( 0, count( $pending_statuses ), '%s' ) ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $pending_status_placeholders is hardcoded. $where_clause = $wpdb->prepare( " @@ -242,23 +251,35 @@ protected function get_args_for_query( $args ) { /** * Get a group's ID based on its name/slug. * - * @param string $slug The string name of a group. - * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group. + * @param string|array $slugs The string name of a group, or names for several groups. + * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group. * - * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created. + * @return array The group IDs, if they exist or were successfully created. May be empty. */ - protected function get_group_id( $slug, $create_if_not_exists = true ) { - if ( empty( $slug ) ) { - return 0; + protected function get_group_ids( $slugs, $create_if_not_exists = true ) { + $slugs = (array) $slugs; + $group_ids = array(); + + if ( empty( $slugs ) ) { + return array(); } + /** @var \wpdb $wpdb */ global $wpdb; - $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) ); - if ( empty( $group_id ) && $create_if_not_exists ) { - $group_id = $this->create_group( $slug ); + + foreach ( $slugs as $slug ) { + $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) ); + + if ( empty( $group_id ) && $create_if_not_exists ) { + $group_id = $this->create_group( $slug ); + } + + if ( $group_id ) { + $group_ids[] = $group_id; + } } - return $group_id; + return $group_ids; } /** @@ -355,7 +376,7 @@ protected function make_action_from_db_record( $data ) { } $group = $data->group ? $data->group : ''; - return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group ); + return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group, $data->priority ); } /** @@ -796,6 +817,33 @@ protected function generate_claim_id() { return $wpdb->insert_id; } + /** + * Set a claim filter. + * + * @param string $filter_name Claim filter name. + * @param mixed $filter_values Values to filter. + * @return void + */ + public function set_claim_filter( $filter_name, $filter_values ) { + if ( isset( $this->claim_filters[ $filter_name ] ) ) { + $this->claim_filters[ $filter_name ] = $filter_values; + } + } + + /** + * Get the claim filter value. + * + * @param string $filter_name Claim filter name. + * @return mixed + */ + public function get_claim_filter( $filter_name ) { + if ( isset( $this->claim_filters[ $filter_name ] ) ) { + return $this->claim_filters[ $filter_name ]; + } + + return ''; + } + /** * Mark actions claimed. * @@ -813,9 +861,8 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu /** @var \wpdb $wpdb */ global $wpdb; - $now = as_get_datetime_object(); - $date = is_null( $before_date ) ? $now : clone $before_date; - + $now = as_get_datetime_object(); + $date = is_null( $before_date ) ? $now : clone $before_date; // can't use $wpdb->update() because of the <= condition. $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; $params = array( @@ -824,6 +871,18 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu current_time( 'mysql' ), ); + // Set claim filters. + if ( ! empty( $hooks ) ) { + $this->set_claim_filter( 'hooks', $hooks ); + } else { + $hooks = $this->get_claim_filter( 'hooks' ); + } + if ( ! empty( $group ) ) { + $this->set_claim_filter( 'group', $group ); + } else { + $group = $this->get_claim_filter( 'group' ); + } + $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; $params[] = $date->format( 'Y-m-d H:i:s' ); $params[] = self::STATUS_PENDING; @@ -834,18 +893,33 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu $params = array_merge( $params, array_values( $hooks ) ); } - if ( ! empty( $group ) ) { - - $group_id = $this->get_group_id( $group, false ); + $group_operator = 'IN'; + if ( empty( $group ) ) { + $group = $this->get_claim_filter( 'exclude-groups' ); + $group_operator = 'NOT IN'; + } - // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour. - if ( empty( $group_id ) ) { - /* translators: %s: group name */ - throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) ); + if ( ! empty( $group ) ) { + $group_ids = $this->get_group_ids( $group, false ); + + // throw exception if no matching group(s) found, this matches ActionScheduler_wpPostStore's behaviour. + if ( empty( $group_ids ) ) { + throw new InvalidArgumentException( + sprintf( + /* translators: %s: group name(s) */ + _n( + 'The group "%s" does not exist.', + 'The groups "%s" do not exist.', + is_array( $group ) ? count( $group ) : 1, + 'action-scheduler' + ), + $group + ) + ); } - $where .= ' AND group_id = %d'; - $params[] = $group_id; + $id_list = implode( ',', array_map( 'intval', $group_ids ) ); + $where .= " AND group_id {$group_operator} ( $id_list )"; } /** @@ -855,7 +929,7 @@ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = nu * * @param string $order_by_sql */ - $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); + $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); $params[] = $limit; $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders @@ -912,7 +986,7 @@ public function find_actions_by_claim_id( $claim_id ) { $cut_off = $before_date->format( 'Y-m-d H:i:s' ); $sql = $wpdb->prepare( - "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", + "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC", $claim_id ); @@ -935,8 +1009,31 @@ public function find_actions_by_claim_id( $claim_id ) { public function release_claim( ActionScheduler_ActionClaim $claim ) { /** @var \wpdb $wpdb */ global $wpdb; - $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), array( 'claim_id' => $claim->get_id() ), array( '%d' ), array( '%d' ) ); + /** + * Deadlock warning: This function modifies actions to release them from claims that have been processed. Earlier, we used to it in a atomic query, i.e. we would update all actions belonging to a particular claim_id with claim_id = 0. + * While this was functionally correct, it would cause deadlock, since this update query will hold a lock on the claim_id_.. index on the action table. + * This allowed the possibility of a race condition, where the claimer query is also running at the same time, then the claimer query will also try to acquire a lock on the claim_id_.. index, and in this case if claim release query has already progressed to the point of acquiring the lock, but have not updated yet, it would cause a deadlock. + * + * We resolve this by getting all the actions_id that we want to release claim from in a separate query, and then releasing the claim on each of them. This way, our lock is acquired on the action_id index instead of the claim_id index. Note that the lock on claim_id will still be acquired, but it will only when we actually make the update, rather than when we select the actions. + */ + $action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", $claim->get_id() ) ); + + $row_updates = 0; + if ( count( $action_ids ) > 0 ) { + $action_id_string = implode( ',', array_map( 'absint', $action_ids ) ); + $row_updates = $wpdb->query( "UPDATE {$wpdb->actionscheduler_actions} SET claim_id = 0 WHERE action_id IN ({$action_id_string})" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + $wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) ); + + if ( $row_updates < count( $action_ids ) ) { + throw new RuntimeException( + sprintf( + __( 'Unable to release actions from claim id %d.', 'action-scheduler' ), + $claim->get_id() + ) + ); + } } /** @@ -982,6 +1079,8 @@ public function mark_failure( $action_id ) { /** * Add execution message to action log. * + * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress'). + * * @param int $action_id Action ID. * * @return void @@ -992,7 +1091,20 @@ public function log_execution( $action_id ) { $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $status_updated = $wpdb->query( $sql ); + + if ( ! $status_updated ) { + throw new Exception( + sprintf( + /* translators: 1: action ID. 2: status slug. */ + __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ), + $action_id, + self::STATUS_RUNNING + ) + ); + } } /** diff --git a/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php b/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php index 7883ca8..7c6b06d 100644 --- a/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php +++ b/vendor/woocommerce/action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php @@ -936,6 +936,8 @@ private function get_post_column( $action_id, $column_name ) { /** * Log Execution. * + * @throws Exception If the action status cannot be updated to self::STATUS_RUNNING ('in-progress'). + * * @param string $action_id Action ID. */ public function log_execution( $action_id ) { @@ -947,7 +949,7 @@ public function log_execution( $action_id ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $wpdb->query( + $status_updated = $wpdb->query( $wpdb->prepare( "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", self::STATUS_RUNNING, @@ -957,6 +959,17 @@ public function log_execution( $action_id ) { self::POST_TYPE ) ); + + if ( ! $status_updated ) { + throw new Exception( + sprintf( + /* translators: 1: action ID. 2: status slug. */ + __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ), + $action_id, + self::STATUS_RUNNING + ) + ); + } } /** diff --git a/vendor/woocommerce/action-scheduler/classes/migration/Runner.php b/vendor/woocommerce/action-scheduler/classes/migration/Runner.php index 867c5de..2304a79 100644 --- a/vendor/woocommerce/action-scheduler/classes/migration/Runner.php +++ b/vendor/woocommerce/action-scheduler/classes/migration/Runner.php @@ -79,7 +79,7 @@ public function run( $batch_size = 10 ) { if ( $this->progress_bar ) { /* translators: %d: amount of actions */ - $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), number_format_i18n( $batch_size ) ) ); + $this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), $batch_size ) ); $this->progress_bar->set_count( $batch_size ); } diff --git a/vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php b/vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php index 0ca9f7c..1b1afec 100644 --- a/vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php +++ b/vendor/woocommerce/action-scheduler/classes/schedules/ActionScheduler_NullSchedule.php @@ -5,6 +5,9 @@ */ class ActionScheduler_NullSchedule extends ActionScheduler_SimpleSchedule { + /** @var DateTime|null */ + protected $scheduled_date; + /** * Make the $date param optional and default to null. * diff --git a/vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php b/vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php index 2506f01..a894d4e 100644 --- a/vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php +++ b/vendor/woocommerce/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php @@ -16,7 +16,7 @@ class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema { /** * @var int Increment this value to trigger a schema update. */ - protected $schema_version = 6; + protected $schema_version = 7; public function __construct() { $this->tables = [ @@ -47,14 +47,15 @@ protected function get_table_definition( $table ) { action_id bigint(20) unsigned NOT NULL auto_increment, hook varchar(191) NOT NULL, status varchar(20) NOT NULL, - scheduled_date_gmt datetime NULL default '${default_date}', - scheduled_date_local datetime NULL default '${default_date}', + scheduled_date_gmt datetime NULL default '{$default_date}', + scheduled_date_local datetime NULL default '{$default_date}', + priority tinyint unsigned NOT NULL default '10', args varchar($max_index_length), schedule longtext, group_id bigint(20) unsigned NOT NULL default '0', attempts int(11) NOT NULL default '0', - last_attempt_gmt datetime NULL default '${default_date}', - last_attempt_local datetime NULL default '${default_date}', + last_attempt_gmt datetime NULL default '{$default_date}', + last_attempt_local datetime NULL default '{$default_date}', claim_id bigint(20) unsigned NOT NULL default '0', extended_args varchar(8000) DEFAULT NULL, PRIMARY KEY (action_id), @@ -71,7 +72,7 @@ protected function get_table_definition( $table ) { return "CREATE TABLE {$table_name} ( claim_id bigint(20) unsigned NOT NULL auto_increment, - date_created_gmt datetime NULL default '${default_date}', + date_created_gmt datetime NULL default '{$default_date}', PRIMARY KEY (claim_id), KEY date_created_gmt (date_created_gmt) ) $charset_collate"; @@ -111,16 +112,16 @@ public function update_schema_5_0( $table, $db_version ) { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $table_name = $wpdb->prefix . 'actionscheduler_actions'; - $table_list = $wpdb->get_col( "SHOW TABLES LIKE '${table_name}'" ); + $table_list = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" ); $default_date = self::DEFAULT_DATE; if ( ! empty( $table_list ) ) { $query = " - ALTER TABLE ${table_name} - MODIFY COLUMN scheduled_date_gmt datetime NULL default '${default_date}', - MODIFY COLUMN scheduled_date_local datetime NULL default '${default_date}', - MODIFY COLUMN last_attempt_gmt datetime NULL default '${default_date}', - MODIFY COLUMN last_attempt_local datetime NULL default '${default_date}' + ALTER TABLE {$table_name} + MODIFY COLUMN scheduled_date_gmt datetime NULL default '{$default_date}', + MODIFY COLUMN scheduled_date_local datetime NULL default '{$default_date}', + MODIFY COLUMN last_attempt_gmt datetime NULL default '{$default_date}', + MODIFY COLUMN last_attempt_local datetime NULL default '{$default_date}' "; $wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } diff --git a/vendor/woocommerce/action-scheduler/functions.php b/vendor/woocommerce/action-scheduler/functions.php index 30ffc52..9770f4f 100644 --- a/vendor/woocommerce/action-scheduler/functions.php +++ b/vendor/woocommerce/action-scheduler/functions.php @@ -12,14 +12,45 @@ * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. + * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. */ -function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false ) { +function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } - return ActionScheduler::factory()->async_unique( $hook, $args, $group, $unique ); + + /** + * Provides an opportunity to short-circuit the default process for enqueuing async + * actions. + * + * Returning a value other than null from the filter will short-circuit the normal + * process. The expectation in such a scenario is that callbacks will return an integer + * representing the enqueued action ID (enqueued using some alternative process) or else + * zero. + * + * @param int|null $pre_option The value to return instead of the option value. + * @param string $hook Action hook. + * @param array $args Action arguments. + * @param string $group Action group. + * @param int $priority Action priority. + */ + $pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority ); + if ( null !== $pre ) { + return is_int( $pre ) ? $pre : 0; + } + + return ActionScheduler::factory()->create( + array( + 'type' => 'async', + 'hook' => $hook, + 'arguments' => $args, + 'group' => $group, + 'unique' => $unique, + 'priority' => $priority, + ) + ); } /** @@ -30,14 +61,47 @@ function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. + * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. */ -function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false ) { +function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } - return ActionScheduler::factory()->single_unique( $hook, $args, $timestamp, $group, $unique ); + + /** + * Provides an opportunity to short-circuit the default process for enqueuing single + * actions. + * + * Returning a value other than null from the filter will short-circuit the normal + * process. The expectation in such a scenario is that callbacks will return an integer + * representing the scheduled action ID (scheduled using some alternative process) or else + * zero. + * + * @param int|null $pre_option The value to return instead of the option value. + * @param int $timestamp When the action will run. + * @param string $hook Action hook. + * @param array $args Action arguments. + * @param string $group Action group. + * @param int $priorities Action priority. + */ + $pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority ); + if ( null !== $pre ) { + return is_int( $pre ) ? $pre : 0; + } + + return ActionScheduler::factory()->create( + array( + 'type' => 'single', + 'hook' => $hook, + 'arguments' => $args, + 'when' => $timestamp, + 'group' => $group, + 'unique' => $unique, + 'priority' => $priority, + ) + ); } /** @@ -49,14 +113,68 @@ function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. + * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. */ -function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false ) { +function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } - return ActionScheduler::factory()->recurring_unique( $hook, $args, $timestamp, $interval_in_seconds, $group, $unique ); + + $interval = (int) $interval_in_seconds; + + // We expect an integer and allow it to be passed using float and string types, but otherwise + // should reject unexpected values. + if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: 1: provided value 2: provided type. */ + esc_html__( 'An integer was expected but "%1$s" (%2$s) was received.', 'action-scheduler' ), + esc_html( $interval_in_seconds ), + esc_html( gettype( $interval_in_seconds ) ) + ), + '3.6.0' + ); + + return 0; + } + + /** + * Provides an opportunity to short-circuit the default process for enqueuing recurring + * actions. + * + * Returning a value other than null from the filter will short-circuit the normal + * process. The expectation in such a scenario is that callbacks will return an integer + * representing the scheduled action ID (scheduled using some alternative process) or else + * zero. + * + * @param int|null $pre_option The value to return instead of the option value. + * @param int $timestamp When the action will run. + * @param int $interval_in_seconds How long to wait between runs. + * @param string $hook Action hook. + * @param array $args Action arguments. + * @param string $group Action group. + * @param int $priority Action priority. + */ + $pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority ); + if ( null !== $pre ) { + return is_int( $pre ) ? $pre : 0; + } + + return ActionScheduler::factory()->create( + array( + 'type' => 'recurring', + 'hook' => $hook, + 'arguments' => $args, + 'when' => $timestamp, + 'pattern' => $interval_in_seconds, + 'group' => $group, + 'unique' => $unique, + 'priority' => $priority, + ) + ); } /** @@ -80,14 +198,49 @@ function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, * @param array $args Arguments to pass when the hook triggers. * @param string $group The group to assign this job to. * @param bool $unique Whether the action should be unique. + * @param int $priority Lower values take precedence over higher values. Defaults to 10, with acceptable values falling in the range 0-255. * * @return int The action ID. */ -function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false ) { +function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) { return 0; } - return ActionScheduler::factory()->cron_unique( $hook, $args, $timestamp, $schedule, $group, $unique ); + + /** + * Provides an opportunity to short-circuit the default process for enqueuing cron + * actions. + * + * Returning a value other than null from the filter will short-circuit the normal + * process. The expectation in such a scenario is that callbacks will return an integer + * representing the scheduled action ID (scheduled using some alternative process) or else + * zero. + * + * @param int|null $pre_option The value to return instead of the option value. + * @param int $timestamp When the action will run. + * @param string $schedule Cron-like schedule string. + * @param string $hook Action hook. + * @param array $args Action arguments. + * @param string $group Action group. + * @param int $priority Action priority. + */ + $pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority ); + if ( null !== $pre ) { + return is_int( $pre ) ? $pre : 0; + } + + return ActionScheduler::factory()->create( + array( + 'type' => 'cron', + 'hook' => $hook, + 'arguments' => $args, + 'when' => $timestamp, + 'pattern' => $schedule, + 'group' => $group, + 'unique' => $unique, + 'priority' => $priority, + ) + ); } /** diff --git a/vendor/woocommerce/action-scheduler/readme.txt b/vendor/woocommerce/action-scheduler/readme.txt new file mode 100644 index 0000000..46bf2f2 --- /dev/null +++ b/vendor/woocommerce/action-scheduler/readme.txt @@ -0,0 +1,156 @@ +=== Action Scheduler === +Contributors: Automattic, wpmuguru, claudiosanches, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, royho, barryhughes-1 +Tags: scheduler, cron +Requires at least: 5.2 +Tested up to: 6.0 +Stable tag: 3.6.1 +License: GPLv3 +Requires PHP: 5.6 + +Action Scheduler - Job Queue for WordPress + +== Description == + +Action Scheduler is a scalable, traceable job queue for background processing large sets of actions in WordPress. It's specially designed to be distributed in WordPress plugins. + +Action Scheduler works by triggering an action hook to run at some time in the future. Each hook can be scheduled with unique data, to allow callbacks to perform operations on that data. The hook can also be scheduled to run on one or more occassions. + +Think of it like an extension to `do_action()` which adds the ability to delay and repeat a hook. + +## Battle-Tested Background Processing + +Every month, Action Scheduler processes millions of payments for [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/), webhooks for [WooCommerce](https://wordpress.org/plugins/woocommerce/), as well as emails and other events for a range of other plugins. + +It's been seen on live sites processing queues in excess of 50,000 jobs and doing resource intensive operations, like processing payments and creating orders, at a sustained rate of over 10,000 / hour without negatively impacting normal site operations. + +This is all on infrastructure and WordPress sites outside the control of the plugin author. + +If your plugin needs background processing, especially of large sets of tasks, Action Scheduler can help. + +## Learn More + +To learn more about how to Action Scheduler works, and how to use it in your plugin, check out the docs on [ActionScheduler.org](https://actionscheduler.org). + +There you will find: + +* [Usage guide](https://actionscheduler.org/usage/): instructions on installing and using Action Scheduler +* [WP CLI guide](https://actionscheduler.org/wp-cli/): instructions on running Action Scheduler at scale via WP CLI +* [API Reference](https://actionscheduler.org/api/): complete reference guide for all API functions +* [Administration Guide](https://actionscheduler.org/admin/): guide to managing scheduled actions via the administration screen +* [Guide to Background Processing at Scale](https://actionscheduler.org/perf/): instructions for running Action Scheduler at scale via the default WP Cron queue runner + +## Credits + +Action Scheduler is developed and maintained by [Automattic](http://automattic.com/) with significant early development completed by [Flightless](https://flightless.us/). + +Collaboration is cool. We'd love to work with you to improve Action Scheduler. [Pull Requests](https://github.com/woocommerce/action-scheduler/pulls) welcome. + +== Changelog == + += 3.6.1 - 2023-06-14 = +* Document new optional `$priority` arg for various API functions. +* Document the new `--exclude-groups` WP CLI option. +* Document the new `action_scheduler_init` hook. +* Ensure actions within each claim are executed in the expected order. +* Fix incorrect text domain. +* Remove SHOW TABLES usage when checking if tables exist. + += 3.6.0 - 2023-05-10 = +* Add $unique parameter to function signatures. +* Add a cast-to-int for extra safety before forming new DateTime object. +* Add a hook allowing exceptions for consistently failing recurring actions. +* Add action priorities. +* Add init hook. +* Always raise the time limit. +* Bump minimatch from 3.0.4 to 3.0.8. +* Bump yaml from 2.2.1 to 2.2.2. +* Defensive coding relating to gaps in declared schedule types. +* Do not process an action if it cannot be set to `in-progress`. +* Filter view labels (status names) should be translatable | #919. +* Fix WPCLI progress messages. +* Improve data-store initialization flow. +* Improve error handling across all supported PHP versions. +* Improve logic for flushing the runtime cache. +* Support exclusion of multiple groups. +* Update lint-staged and Node/NPM requirements. +* add CLI clean command. +* add CLI exclude-group filter. +* exclude past-due from list table all filter count. +* throwing an exception if as_schedule_recurring_action interval param is not of type integer. + += 3.5.4 - 2023-01-17 = +* Add pre filters during action registration. +* Async scheduling. +* Calculate timeouts based on total actions. +* Correctly order the parameters for `ActionScheduler_ActionFactory`'s calls to `single_unique`. +* Fetch action in memory first before releasing claim to avoid deadlock. +* PHP 8.2: declare property to fix creation of dynamic property warning. +* PHP 8.2: fix "Using ${var} in strings is deprecated, use {$var} instead". +* Prevent `undefined variable` warning for `$num_pastdue_actions`. + += 3.5.3 - 2022-11-09 = +* Query actions with partial match. + += 3.5.2 - 2022-09-16 = +* Fix - erroneous 3.5.1 release. + += 3.5.1 - 2022-09-13 = +* Maintenance on A/S docs. +* fix: PHP 8.2 deprecated notice. + += 3.5.0 - 2022-08-25 = +* Add - The active view link within the "Tools > Scheduled Actions" screen is now clickable. +* Add - A warning when there are past-due actions. +* Enhancement - Added the ability to schedule unique actions via an atomic operation. +* Enhancement - Improvements to cache invalidation when processing batches (when running on WordPress 6.0+). +* Enhancement - If a recurring action is found to be consistently failing, it will stop being rescheduled. +* Enhancement - Adds a new "Past Due" view to the scheduled actions list table. + += 3.4.2 - 2022-06-08 = +* Fix - Change the include for better linting. +* Fix - update: Added Action scheduler completed action hook. + += 3.4.1 - 2022-05-24 = +* Fix - Change the include for better linting. +* Fix - Fix the documented return type. + += 3.4.0 - 2021-10-29 = +* Enhancement - Number of items per page can now be set for the Scheduled Actions view (props @ovidiul). #771 +* Fix - Do not lower the max_execution_time if it is already set to 0 (unlimited) (props @barryhughes). #755 +* Fix - Avoid triggering autoloaders during the version resolution process (props @olegabr). #731 & #776 +* Dev - ActionScheduler_wcSystemStatus PHPCS fixes (props @ovidiul). #761 +* Dev - ActionScheduler_DBLogger.php PHPCS fixes (props @ovidiul). #768 +* Dev - Fixed phpcs for ActionScheduler_Schedule_Deprecated (props @ovidiul). #762 +* Dev - Improve actions table indicies (props @glagonikas). #774 & #777 +* Dev - PHPCS fixes for ActionScheduler_DBStore.php (props @ovidiul). #769 & #778 +* Dev - PHPCS Fixes for ActionScheduler_Abstract_ListTable (props @ovidiul). #763 & #779 +* Dev - Adds new filter action_scheduler_claim_actions_order_by to allow tuning of the claim query (props @glagonikas). #773 +* Dev - PHPCS fixes for ActionScheduler_WpPostStore class (props @ovidiul). #780 + += 3.3.0 - 2021-09-15 = +* Enhancement - Adds as_has_scheduled_action() to provide a performant way to test for existing actions. #645 +* Fix - Improves compatibility with environments where NO_ZERO_DATE is enabled. #519 +* Fix - Adds safety checks to guard against errors when our database tables cannot be created. #645 +* Dev - Now supports queries that use multiple statuses. #649 +* Dev - Minimum requirements for WordPress and PHP bumped (to 5.2 and 5.6 respectively). #723 + += 3.2.1 - 2021-06-21 = +* Fix - Add extra safety/account for different versions of AS and different loading patterns. #714 +* Fix - Handle hidden columns (Tools → Scheduled Actions) | #600. + += 3.2.0 - 2021-06-03 = +* Fix - Add "no ordering" option to as_next_scheduled_action(). +* Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634. +* Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634. +* Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas). +* Fix - Fix unit tests infrastructure and adapt tests to PHP 8. +* Fix - Identify in-use data store. +* Fix - Improve test_migration_is_scheduled. +* Fix - PHP notice on list table. +* Fix - Speed up clean up and batch selects. +* Fix - Update pending dependencies. +* Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array(). +* Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility. +* Fix - add is_initialized() to docs. +* Fix - fix file permissions. +* Fix - fixes #664 by replacing __ with esc_html__.