diff --git a/classes/ActionScheduler_ActionFactory.php b/classes/ActionScheduler_ActionFactory.php index 8d97417c5..11f8b037f 100644 --- a/classes/ActionScheduler_ActionFactory.php +++ b/classes/ActionScheduler_ActionFactory.php @@ -44,6 +44,28 @@ public function get_stored_action( $status, $hook, array $args = array(), Action return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group ); } + /** + * 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. + * + * @param string $hook The hook to trigger when this action runs + * @param array $args Args to pass when the hook is triggered + * @param string $group A group to put the action in + * + * @return string The ID of the stored action + */ + public function async( $hook, $args = array(), $group = '' ) { + $schedule = new ActionScheduler_NullSchedule(); + $action = new ActionScheduler_Action( $hook, $args, $schedule, $group ); + return $this->store( $action ); + } + /** * @param string $hook The hook to trigger when this action runs * @param array $args Args to pass when the hook is triggered diff --git a/classes/ActionScheduler_ListTable.php b/classes/ActionScheduler_ListTable.php index 95f97d6b4..e94b86705 100644 --- a/classes/ActionScheduler_ListTable.php +++ b/classes/ActionScheduler_ListTable.php @@ -380,7 +380,7 @@ protected function get_schedule_display_string( ActionScheduler_Schedule $schedu $schedule_display_string = ''; if ( ! $schedule->next() ) { - return $schedule_display_string; + return '0000-00-00 00:00:00'; } $next_timestamp = $schedule->next()->getTimestamp(); diff --git a/classes/abstracts/ActionScheduler_Store.php b/classes/abstracts/ActionScheduler_Store.php index 59ac236af..c96ca1f3b 100644 --- a/classes/abstracts/ActionScheduler_Store.php +++ b/classes/abstracts/ActionScheduler_Store.php @@ -149,7 +149,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()->next() : $scheduled_date; if ( ! $next ) { - throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) ); + return '0000-00-00 00:00:00'; } $next->setTimezone( new DateTimeZone( 'UTC' ) ); @@ -166,7 +166,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()->next() : $scheduled_date; if ( ! $next ) { - throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) ); + return '0000-00-00 00:00:00'; } ActionScheduler_TimezoneHelper::set_local_timezone( $next ); diff --git a/functions.php b/functions.php index 2f27d7e7e..e67764ae3 100644 --- a/functions.php +++ b/functions.php @@ -4,6 +4,18 @@ * General API functions for scheduling actions */ +/** + * Enqueue an action to run one time, as soon as possible + * + * @param string $hook The hook to trigger. + * @param array $args Arguments to pass when the hook triggers. + * @param string $group The group to assign this job to. + * @return string The action ID. + */ +function as_enqueue_async_action( $hook, $args = array(), $group = '' ) { + return ActionScheduler::factory()->async( $hook, $args, $group ); +} + /** * Schedule an action to run one time * @@ -109,10 +121,12 @@ function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) { * @param array $args * @param string $group * - * @return int|bool The timestamp for the next occurrence, or false if nothing was found + * @return int|bool The timestamp for the next occurrence of a scheduled action, true for an async action or false if there is no matching, pending action. */ function as_next_scheduled_action( $hook, $args = NULL, $group = '' ) { - $params = array(); + $params = array( + 'status' => ActionScheduler_Store::STATUS_PENDING, + ); if ( is_array($args) ) { $params['args'] = $args; } @@ -127,6 +141,8 @@ function as_next_scheduled_action( $hook, $args = NULL, $group = '' ) { $next = $job->get_schedule()->next(); if ( $next ) { return (int)($next->format('U')); + } elseif ( NULL === $next ) { // pending async action with NullSchedule + return true; } return false; } diff --git a/tests/phpunit/procedural_api/procedural_api_Test.php b/tests/phpunit/procedural_api/procedural_api_Test.php index 543add798..c54925955 100644 --- a/tests/phpunit/procedural_api/procedural_api_Test.php +++ b/tests/phpunit/procedural_api/procedural_api_Test.php @@ -50,6 +50,32 @@ public function test_get_next() { $this->assertEquals( $time->getTimestamp(), $next ); } + public function test_get_next_async() { + $hook = md5(rand()); + $action_id = as_enqueue_async_action( $hook ); + + $next = as_next_scheduled_action( $hook ); + + $this->assertTrue( $next ); + + $store = ActionScheduler::store(); + + // Completed async actions should still return false + $store->mark_complete( $action_id ); + $next = as_next_scheduled_action( $hook ); + $this->assertFalse( $next ); + + // Failed async actions should still return false + $store->mark_failure( $action_id ); + $next = as_next_scheduled_action( $hook ); + $this->assertFalse( $next ); + + // Cancelled async actions should still return false + $store->cancel_action( $action_id ); + $next = as_next_scheduled_action( $hook ); + $this->assertFalse( $next ); + } + public function provider_time_hook_args_group() { $time = time() + 60 * 2; $hook = md5( rand() );