Commit e76a374189b for woocommerce
commit e76a374189bf880ee5f5cdeae3d39b8ed98e8fac
Author: Barry Hughes <3594411+barryhughes@users.noreply.github.com>
Date: Thu Oct 29 15:40:29 2026 -0700
Migrate the WooCommerce bootstrap scheduling calls to ActionSchedulerUtil (#70154)
The recurring actions, the tracking event and the hold-stock cancel schedule go through the util, so unique is honoured on every loaded copy. add_recurring_action_wrappers() is attached through when_ready() instead of action_scheduler_init, which copies before 3.6.0 never fire. The tracker interval from wp_get_schedules() is cast to an integer before it reaches the typed util.
Split from #69847 (WOOPLUG-2435).
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-bootstrap b/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-bootstrap
new file mode 100644
index 00000000000..015a2bb07cd
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-bootstrap
@@ -0,0 +1,3 @@
+Significance: patch
+Type: update
+Comment: Covered by the base PR changelog entry (#69846).
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index 85a2b024b14..23bb45c8336 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -31,6 +31,7 @@ use Automattic\WooCommerce\Internal\ProductDownloads\ApprovedDirectories\Registe
use Automattic\WooCommerce\Internal\ProductImage\MatchImageBySKU;
use Automattic\WooCommerce\Internal\RestockRefundedItemsAdjuster;
use Automattic\WooCommerce\Internal\Settings\OptionSanitizer;
+use Automattic\WooCommerce\Internal\Utilities\ActionSchedulerUtil;
use Automattic\WooCommerce\Internal\Utilities\LegacyRestApiStub;
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
use Automattic\WooCommerce\Internal\Admin\EmailImprovements\EmailImprovements;
@@ -374,7 +375,7 @@ final class WooCommerce {
add_action( 'update_option_woocommerce_allow_tracking', array( $this, 'get_tracking_history' ), 10, 2 );
add_action( 'update_option_woocommerce_allow_tracking', array( $this, 'handle_tracking_setting_change' ), 10, 2 );
add_action( 'action_scheduler_ensure_recurring_actions', array( $this, 'register_recurring_actions' ) );
- add_action( 'action_scheduler_init', array( $this, 'add_recurring_action_wrappers' ) );
+ ActionSchedulerUtil::when_ready( array( $this, 'add_recurring_action_wrappers' ) );
add_filter( 'robots_txt', array( $this, 'robots_txt' ) );
add_filter( 'wp_plugin_dependencies_slug', array( $this, 'convert_woocommerce_slug' ) );
@@ -1761,7 +1762,7 @@ final class WooCommerce {
$scheduled_sales_time = strtotime( '00:00 tomorrow' );
}
- as_schedule_recurring_action( $scheduled_sales_time, DAY_IN_SECONDS, 'woocommerce_scheduled_sales', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $scheduled_sales_time, DAY_IN_SECONDS, 'woocommerce_scheduled_sales', array(), 'woocommerce', true );
$held_duration = get_option( 'woocommerce_hold_stock_minutes', '60' );
@@ -1773,7 +1774,7 @@ final class WooCommerce {
*/
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $held_duration ) );
- as_schedule_single_action( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_single( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders', array(), 'woocommerce', true );
}
@@ -1790,23 +1791,23 @@ final class WooCommerce {
// so it doesn't occur in the same request. WooCommerce Admin also schedules
// a daily cron that gets lost due to a race condition. WC_Privacy's background
// processing instance updates the cron schedule from within a cron job.
- as_schedule_recurring_action( time() + 10, DAY_IN_SECONDS, 'woocommerce_cleanup_personal_data', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( time() + 10, DAY_IN_SECONDS, 'woocommerce_cleanup_personal_data', array(), 'woocommerce', true );
- as_schedule_recurring_action( $tomorrow_3am, DAY_IN_SECONDS, 'woocommerce_cleanup_logs', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $tomorrow_3am, DAY_IN_SECONDS, 'woocommerce_cleanup_logs', array(), 'woocommerce', true );
- as_schedule_recurring_action( $tomorrow_6am, 12 * HOUR_IN_SECONDS, 'woocommerce_cleanup_sessions', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $tomorrow_6am, 12 * HOUR_IN_SECONDS, 'woocommerce_cleanup_sessions', array(), 'woocommerce', true );
- as_schedule_recurring_action( $tomorrow_6am, 15 * DAY_IN_SECONDS, 'woocommerce_geoip_updater', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $tomorrow_6am, 15 * DAY_IN_SECONDS, 'woocommerce_geoip_updater', array(), 'woocommerce', true );
// Schedule the action to send tracking events if tracking is enabled.
$this->schedule_tracking_action();
- as_schedule_recurring_action( $tomorrow_3am, DAY_IN_SECONDS, 'woocommerce_cleanup_rate_limits_wrapper', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $tomorrow_3am, DAY_IN_SECONDS, 'woocommerce_cleanup_rate_limits_wrapper', array(), 'woocommerce', true );
- as_schedule_recurring_action( $tomorrow_3am, DAY_IN_SECONDS, 'wc_admin_daily_wrapper', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_recurring( $tomorrow_3am, DAY_IN_SECONDS, 'wc_admin_daily_wrapper', array(), 'woocommerce', true );
// Note: this is potentially redundant when the core package exists.
- as_schedule_single_action( time() + 10, 'generate_category_lookup_table_wrapper', array(), 'woocommerce', true );
+ ActionSchedulerUtil::schedule_single( time() + 10, 'generate_category_lookup_table_wrapper', array(), 'woocommerce', true );
}
/**
@@ -1849,8 +1850,8 @@ final class WooCommerce {
$tracker_recurrence = apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' );
$tracker_recurrence = is_string( $tracker_recurrence ) ? $tracker_recurrence : 'daily';
$core_internals = wp_get_schedules();
- $interval = $core_internals[ $tracker_recurrence ]['interval'] ?? DAY_IN_SECONDS;
- as_schedule_recurring_action( time() + 10, $interval, 'woocommerce_tracker_send_event_wrapper', array(), 'woocommerce', true );
+ $interval = (int) ( $core_internals[ $tracker_recurrence ]['interval'] ?? DAY_IN_SECONDS );
+ ActionSchedulerUtil::schedule_recurring( time() + 10, $interval, 'woocommerce_tracker_send_event_wrapper', array(), 'woocommerce', true );
}
/**
diff --git a/plugins/woocommerce/includes/wc-formatting-functions.php b/plugins/woocommerce/includes/wc-formatting-functions.php
index 387f70df552..f2651d55d56 100644
--- a/plugins/woocommerce/includes/wc-formatting-functions.php
+++ b/plugins/woocommerce/includes/wc-formatting-functions.php
@@ -10,6 +10,7 @@
use Automattic\WooCommerce\Enums\WeightUnit;
use Automattic\WooCommerce\Internal\Settings\OptionSanitizer;
+use Automattic\WooCommerce\Internal\Utilities\ActionSchedulerUtil;
use Automattic\WooCommerce\Utilities\I18nUtil;
use Automattic\WooCommerce\Utilities\NumberUtil;
@@ -1245,12 +1246,13 @@ function wc_format_option_hold_stock_minutes( $value, $option, $raw_value ) {
$value = ! empty( $raw_value ) ? absint( $raw_value ) : ''; // Allow > 0 or set to ''.
$value_changed = (string) get_option( 'woocommerce_hold_stock_minutes', '60' ) !== (string) $value;
- $next_scheduled_action = function_exists( 'as_next_scheduled_action' )
+ $use_action_scheduler = ActionSchedulerUtil::is_ready();
+ $next_scheduled_action = $use_action_scheduler
? as_next_scheduled_action( 'woocommerce_cancel_unpaid_orders', array(), 'woocommerce' )
: wp_next_scheduled( 'woocommerce_cancel_unpaid_orders' );
// Clear existing scheduled events.
- if ( $value_changed && function_exists( 'as_unschedule_all_actions' ) ) {
+ if ( $value_changed && $use_action_scheduler ) {
as_unschedule_all_actions( 'woocommerce_cancel_unpaid_orders' );
} elseif ( $value_changed ) {
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
@@ -1266,8 +1268,8 @@ function wc_format_option_hold_stock_minutes( $value, $option, $raw_value ) {
*/
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $value ) );
- if ( function_exists( 'as_schedule_single_action' ) ) {
- as_schedule_single_action( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders', array(), 'woocommerce', true );
+ if ( $use_action_scheduler ) {
+ ActionSchedulerUtil::schedule_single( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders', array(), 'woocommerce', true );
} else {
wp_schedule_single_event( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
}
diff --git a/plugins/woocommerce/tests/php/includes/wc-formatting-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-formatting-functions-test.php
index c729e57d323..dcfa8547440 100644
--- a/plugins/woocommerce/tests/php/includes/wc-formatting-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-formatting-functions-test.php
@@ -353,4 +353,26 @@ class WC_Formatting_Functions_Test extends \WC_Unit_Test_Case {
'A blank hold stock value should remove the cancellation action.'
);
}
+
+ /**
+ * @testdox Saving a hold stock value falls back to WP-Cron when Action Scheduler is not ready.
+ */
+ public function test_hold_stock_value_falls_back_to_wp_cron_when_action_scheduler_is_not_ready(): void {
+ update_option( 'woocommerce_hold_stock_minutes', '60' );
+ as_unschedule_all_actions( 'woocommerce_cancel_unpaid_orders' );
+ wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
+ $this->register_legacy_proxy_function_mocks(
+ array(
+ 'function_exists' => fn( string $function_name ): bool => 'as_schedule_single_action' !== $function_name && function_exists( $function_name ),
+ )
+ );
+
+ $expected_time = time() + HOUR_IN_SECONDS;
+ wc_format_option_hold_stock_minutes( '60', array(), '60' );
+
+ $this->assertFalse( as_next_scheduled_action( 'woocommerce_cancel_unpaid_orders' ), 'Nothing should be scheduled through Action Scheduler while it is not ready.' );
+ $this->assertEqualsWithDelta( $expected_time, wp_next_scheduled( 'woocommerce_cancel_unpaid_orders' ), 1, 'The cancellation event should fall back to WP-Cron.' );
+
+ wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
+ }
}