Commit 13a1d49593f for woocommerce
commit 13a1d49593fc3f7d3c2795ab2b930e08de14e9d4
Author: Barry Hughes <3594411+barryhughes@users.noreply.github.com>
Date: Thu Oct 29 13:03:55 2026 -0700
Probe the loaded Action Scheduler instead of the registry version (#70157)
* Probe the loaded Action Scheduler instead of the registry version
The download handler requires the UNIQUE_ARGS feature from the loaded copy before tracking partial downloads. PluginsHelper reads action status from the active store and SchedulerTraits checks the store for cancel_actions_by_group(). The inbox engine checks for its scheduled action through the util, and PTKPatternsStore defers its cleanup with when_ready().
Split from #69847 (WOOPLUG-2435).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Track partial downloads on every loaded copy and keep the inbox search fallback below 3.3.0
* Drop the inbox engine guard now that the util sees running actions on every copy
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-probes-readiness b/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-probes-readiness
new file mode 100644
index 00000000000..015a2bb07cd
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-wooplug-2435-migrate-as-calls-probes-readiness
@@ -0,0 +1,3 @@
+Significance: patch
+Type: update
+Comment: Covered by the base PR changelog entry (#69846).
diff --git a/plugins/woocommerce/includes/class-wc-download-handler.php b/plugins/woocommerce/includes/class-wc-download-handler.php
index b917442972f..a6920133931 100644
--- a/plugins/woocommerce/includes/class-wc-download-handler.php
+++ b/plugins/woocommerce/includes/class-wc-download-handler.php
@@ -10,6 +10,7 @@
defined( 'ABSPATH' ) || exit;
+use Automattic\WooCommerce\Internal\Utilities\ActionSchedulerUtil;
use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil;
/**
@@ -1043,7 +1044,6 @@ class WC_Download_Handler {
* @param bool $defer If tracking the download should be deferred.
*
* @return void
- * @throws Exception If the active version of Action Scheduler is less than 3.6.0.
*
* @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed.
*/
@@ -1074,13 +1074,7 @@ class WC_Download_Handler {
*/
$window = absint( apply_filters( 'woocommerce_partial_download_tracking_window', 30 * MINUTE_IN_SECONDS, $download->get_id() ) );
- // If we do not have Action Scheduler 3.6.0+ (this would be an unexpected scenario) then we cannot
- // track partial downloads, because we require support for unique actions.
- if ( version_compare( ActionScheduler_Versions::instance()->latest_version(), '3.6.0', '<' ) ) {
- throw new Exception( 'Support for unique scheduled actions is not currently available.' );
- }
-
- as_schedule_single_action(
+ ActionSchedulerUtil::schedule_single(
time() + $window,
self::TRACK_DOWNLOAD_CALLBACK,
array(
@@ -1099,7 +1093,7 @@ class WC_Download_Handler {
'id' => $download->get_id(),
'user_id' => $user_id,
'ip' => $user_ip_address,
- 'deferred' => $defer ? 'yes' : 'no',
+ 'deferred' => wc_bool_to_string( $defer ),
)
);
}
diff --git a/plugins/woocommerce/src/Admin/PluginsHelper.php b/plugins/woocommerce/src/Admin/PluginsHelper.php
index 43023b307f4..45b6d2b6218 100644
--- a/plugins/woocommerce/src/Admin/PluginsHelper.php
+++ b/plugins/woocommerce/src/Admin/PluginsHelper.php
@@ -8,7 +8,6 @@
namespace Automattic\WooCommerce\Admin;
use ActionScheduler;
-use ActionScheduler_DBStore;
use ActionScheduler_QueueRunner;
use Automatic_Upgrader_Skin;
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsyncPluginsInstallLogger;
@@ -756,12 +755,11 @@ class PluginsHelper {
$data = array();
foreach ( $actions as $action_id => $action ) {
- $store = new ActionScheduler_DBStore();
$args = $action->get_args();
$data[] = array(
'job_id' => $args[1],
'plugins' => $args[0],
- 'status' => $store->get_status( $action_id ),
+ 'status' => ActionScheduler::store()->get_status( $action_id ),
);
}
diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
index 2c08206be37..20914bcf11c 100644
--- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
+++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
@@ -14,6 +14,7 @@ use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;
use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\StoredStateSetupForProducts;
+use Automattic\WooCommerce\Internal\Utilities\ActionSchedulerUtil;
/**
* Remote Inbox Notifications engine.
@@ -61,8 +62,8 @@ class RemoteInboxNotificationsEngine extends RemoteSpecsEngine {
function () {
$queue = WC()->queue();
// Custom queues, including subclasses, must retain their search overrides.
- if ( \WC_Action_Queue::class === get_class( $queue ) && function_exists( 'as_has_scheduled_action' ) ) {
- if ( as_has_scheduled_action( 'woocommerce_run_on_woocommerce_admin_updated', array(), 'woocommerce-remote-inbox-engine' ) ) {
+ if ( \WC_Action_Queue::class === get_class( $queue ) ) {
+ if ( ActionSchedulerUtil::has_scheduled_action( 'woocommerce_run_on_woocommerce_admin_updated', array(), 'woocommerce-remote-inbox-engine' ) ) {
return;
}
} else {
diff --git a/plugins/woocommerce/src/Admin/Schedulers/SchedulerTraits.php b/plugins/woocommerce/src/Admin/Schedulers/SchedulerTraits.php
index e6ac4583f57..85f6879c28e 100644
--- a/plugins/woocommerce/src/Admin/Schedulers/SchedulerTraits.php
+++ b/plugins/woocommerce/src/Admin/Schedulers/SchedulerTraits.php
@@ -354,8 +354,11 @@ trait SchedulerTraits {
* Clears all queued actions.
*/
public static function clear_queued_actions() {
- if ( version_compare( \ActionScheduler_Versions::instance()->latest_version(), '3.0', '>=' ) ) {
- \ActionScheduler::store()->cancel_actions_by_group( static::$group );
+ $store = \ActionScheduler::store();
+ // PHPStan sees the bundled copy, where the method exists; copies before 3.0.0 lack it.
+ // @phpstan-ignore-next-line function.alreadyNarrowedType -- see comment above.
+ if ( $store instanceof \ActionScheduler_Store && method_exists( $store, 'cancel_actions_by_group' ) ) {
+ $store->cancel_actions_by_group( static::$group ?? '' );
} else {
$actions = static::get_actions();
foreach ( $actions as $action ) {
diff --git a/plugins/woocommerce/src/Blocks/Patterns/PTKPatternsStore.php b/plugins/woocommerce/src/Blocks/Patterns/PTKPatternsStore.php
index 1df05b99ac2..2bb6d0d3eb5 100644
--- a/plugins/woocommerce/src/Blocks/Patterns/PTKPatternsStore.php
+++ b/plugins/woocommerce/src/Blocks/Patterns/PTKPatternsStore.php
@@ -2,6 +2,7 @@
namespace Automattic\WooCommerce\Blocks\Patterns;
+use Automattic\WooCommerce\Internal\Utilities\ActionSchedulerUtil;
use WP_Upgrader;
/**
@@ -75,16 +76,11 @@ class PTKPatternsStore {
return;
}
- if ( did_action( 'action_scheduler_init' ) ) {
- as_unschedule_all_actions( self::FETCH_PATTERNS_ACTION, array(), 'woocommerce' );
- } else {
- add_action(
- 'action_scheduler_init',
- function () {
- as_unschedule_all_actions( self::FETCH_PATTERNS_ACTION, array(), 'woocommerce' );
- }
- );
- }
+ ActionSchedulerUtil::when_ready(
+ function () {
+ as_unschedule_all_actions( self::FETCH_PATTERNS_ACTION, array(), 'woocommerce' );
+ }
+ );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Patterns/PTKPatternsStoreTest.php b/plugins/woocommerce/tests/php/src/Blocks/Patterns/PTKPatternsStoreTest.php
index a21c9402051..7464d2d9912 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Patterns/PTKPatternsStoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Patterns/PTKPatternsStoreTest.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Blocks\Patterns;
use Automattic\WooCommerce\Blocks\Patterns\PTKClient;
use Automattic\WooCommerce\Blocks\Patterns\PTKPatternsStore;
+use Automattic\WooCommerce\Proxies\LegacyProxy;
use WP_Error;
/**
@@ -53,6 +54,9 @@ class PTKPatternsStoreTest extends \WP_UnitTestCase {
delete_option( PTKPatternsStore::OPTION_NAME );
delete_option( 'woocommerce_allow_tracking' );
+ // A test that fails between registering and resetting a proxy mock must not leak it into later tests.
+ wc_get_container()->get( LegacyProxy::class )->reset();
+
parent::tearDown();
}
@@ -475,30 +479,26 @@ class PTKPatternsStoreTest extends \WP_UnitTestCase {
}
/**
- * Test flush_cached_patterns defers unscheduling when called before action_scheduler_init.
+ * Test flush_cached_patterns defers unscheduling when called before Action Scheduler is ready.
*
- * This test simulates the scenario where flush_cached_patterns is called during early
- * initialization, before Action Scheduler has been initialized.
+ * ActionSchedulerUtil::when_ready() decides readiness through the LegacyProxy, so the test makes
+ * both `action_scheduler_init` and `init` look unfired there, which is how an early call sees them.
*/
public function test_flush_cached_patterns_defers_unscheduling_before_action_scheduler_init() {
- global $wp_actions;
-
// Schedule an action.
as_schedule_single_action( time(), 'fetch_patterns', array(), 'woocommerce' );
$this->assertTrue( as_has_scheduled_action( 'fetch_patterns', array(), 'woocommerce' ), 'Action should be scheduled initially' );
- // Save the original action_scheduler_init count.
- $original_count = $wp_actions['action_scheduler_init'] ?? 0;
-
- // Simulate that action_scheduler_init has not yet fired by unsetting it.
- if ( isset( $wp_actions['action_scheduler_init'] ) ) {
- unset( $wp_actions['action_scheduler_init'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
- }
-
- // Verify did_action returns 0 (not fired).
- $this->assertEquals( 0, did_action( 'action_scheduler_init' ), 'action_scheduler_init should appear not fired' );
+ $proxy = wc_get_container()->get( LegacyProxy::class );
+ $proxy->register_function_mocks(
+ array(
+ 'did_action' => function ( $hook ) {
+ return in_array( $hook, array( 'action_scheduler_init', 'init' ), true ) ? 0 : did_action( $hook );
+ },
+ )
+ );
- // Call flush_cached_patterns before action_scheduler_init has fired.
+ // Call flush_cached_patterns before Action Scheduler is ready.
$this->pattern_store->flush_cached_patterns();
// The option should be deleted immediately.
@@ -508,6 +508,8 @@ class PTKPatternsStoreTest extends \WP_UnitTestCase {
// The action should still be scheduled (unscheduling is deferred).
$this->assertTrue( as_has_scheduled_action( 'fetch_patterns', array(), 'woocommerce' ), 'Action should still be scheduled before action_scheduler_init fires' );
+ $proxy->reset();
+
/**
* Simulate action_scheduler_init firing.
*
@@ -519,7 +521,9 @@ class PTKPatternsStoreTest extends \WP_UnitTestCase {
// After action_scheduler_init fires, the action should be unscheduled.
$this->assertFalse( as_has_scheduled_action( 'fetch_patterns', array(), 'woocommerce' ), 'Action should be unscheduled after action_scheduler_init fires' );
- // Restore the original action count.
- $wp_actions['action_scheduler_init'] = $original_count; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ // The deferred unscheduling runs once: a later firing must leave a newly scheduled action alone.
+ as_schedule_single_action( time(), 'fetch_patterns', array(), 'woocommerce' );
+ do_action( 'action_scheduler_init' );
+ $this->assertTrue( as_has_scheduled_action( 'fetch_patterns', array(), 'woocommerce' ), 'The deferred unscheduling should not run a second time' );
}
}