Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion inc/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ public function qdrant_deactivate() {
$over_limit = $this->table->get_posts_over_limit();

if ( ! empty( $over_limit ) ) {
wp_schedule_single_event( time(), 'hyve_delete_posts', [ $over_limit ] );
Scheduler::enqueue_async( 'hyve_delete_posts', [ $over_limit ] );
}

$this->table->update_storage( 'WordPress', 'Qdrant' );
Expand Down
10 changes: 4 additions & 6 deletions inc/DB_Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ function ( $id ) {
add_action( 'hyve_delete_posts', [ $this, 'delete_posts' ], 10, 1 );
add_action( 'hyve_update_posts', [ $this, 'update_posts' ] );

if ( ! wp_next_scheduled( 'hyve_update_posts' ) ) {
wp_schedule_event( time(), 'hourly', 'hyve_update_posts' );
}
Scheduler::ensure_recurring( 'hyve_update_posts', HOUR_IN_SECONDS, 'hourly' );

if ( ! $this->table_exists() || version_compare( $this->version, get_option( $this->table_name . '_db_version' ), '>' ) ) {
$this->create_table();
Expand Down Expand Up @@ -785,7 +783,7 @@ private function handle_processing_failure( $id, $post_id, $error, $allow_retry

if ( $will_retry ) {
set_transient( $transient, $attempts, DAY_IN_SECONDS );
wp_schedule_single_event( time() + ( MINUTE_IN_SECONDS * $attempts ), 'hyve_process_post', [ $id ] );
Scheduler::schedule_single( time() + ( MINUTE_IN_SECONDS * $attempts ), 'hyve_process_post', [ $id ] );
return;
}

Expand Down Expand Up @@ -870,7 +868,7 @@ public function update_posts() {
$this->add_post( $post_id, 'update' );
}

wp_schedule_single_event( time() + 60, 'hyve_update_posts' );
Scheduler::schedule_single( time() + 60, 'hyve_update_posts' );
}

/**
Expand Down Expand Up @@ -901,7 +899,7 @@ public function delete_posts( array $posts ): void {
$has_more = count( $posts ) > 20;

if ( $has_more ) {
wp_schedule_single_event( time() + 10, 'hyve_delete_posts', [ array_slice( $posts, 20 ) ] );
Scheduler::schedule_single( time() + 10, 'hyve_delete_posts', [ array_slice( $posts, 20 ) ] );
}
}

Expand Down
2 changes: 1 addition & 1 deletion inc/Main.php
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ public function update_meta( $post_id, $post, $update ) {
delete_post_meta( $post_id, '_hyve_moderation_failed' );
delete_post_meta( $post_id, '_hyve_moderation_review' );

wp_schedule_single_event( time(), 'hyve_update_posts' );
Scheduler::enqueue_async( 'hyve_update_posts' );
}

/**
Expand Down
4 changes: 2 additions & 2 deletions inc/Qdrant_API.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public function init() {
]
);

wp_schedule_single_event( time(), 'hyve_lite_migrate_data' );
Scheduler::enqueue_async( 'hyve_lite_migrate_data' );
}

return true;
Expand Down Expand Up @@ -411,7 +411,7 @@ public function migrate_data() {
update_option( 'hyve_qdrant_migration', $migration_status );

if ( $has_more ) {
wp_schedule_single_event( time() + 10, 'hyve_lite_migrate_data' );
Scheduler::schedule_single( time() + 10, 'hyve_lite_migrate_data' );
}
}

Expand Down
136 changes: 136 additions & 0 deletions inc/Scheduler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* Scheduler Class.
*
* @package Codeinwp\HyveLite
*/

namespace ThemeIsle\HyveLite;

/**
* Schedules background jobs through Action Scheduler when it is available,
* falling back to WP-Cron otherwise.
*
* Action Scheduler is not bundled with Hyve. When another plugin on the site
* provides it (e.g. WooCommerce), Hyve's background work runs on it for better
* reliability and visibility; otherwise the WP-Cron behaviour is preserved.
*
* @since 1.3.4
*/
class Scheduler {

/**
* Action Scheduler group for Hyve actions.
*
* @since 1.3.4
* @var string
*/
const GROUP = 'hyve';

/**
* Whether Action Scheduler is loaded and its data store is initialized.
*
* The `as_*` functions no-op with a `_doing_it_wrong` notice when called
* before the data store is set up on `init`, so scheduling must either
* wait for that or fall back to WP-Cron.
*
* @since 1.3.4
*
* @return bool
*/
private static function is_ready() {
return class_exists( 'ActionScheduler', false ) && \ActionScheduler::is_initialized();
}

/**
* Schedule a job to run as soon as possible.
*
* Replaces `wp_schedule_single_event( time(), ... )`.
*
* @since 1.3.4
*
* @param string $hook The hook to fire.
* @param list<mixed> $args Arguments to pass to the hook.
*
* @return int|bool Action ID when using Action Scheduler, otherwise the WP-Cron result.
*/
public static function enqueue_async( $hook, $args = [] ) {
if ( function_exists( 'as_enqueue_async_action' ) && self::is_ready() ) {
return as_enqueue_async_action( $hook, $args, self::GROUP );
}

return wp_schedule_single_event( time(), $hook, $args );
}

/**
* Schedule a job to run once at a given time.
*
* Replaces `wp_schedule_single_event( time() + $delay, ... )`.
*
* @since 1.3.4
*
* @param int $timestamp Unix timestamp when the job should run.
* @param string $hook The hook to fire.
* @param list<mixed> $args Arguments to pass to the hook.
*
* @return int|bool Action ID when using Action Scheduler, otherwise the WP-Cron result.
*/
public static function schedule_single( $timestamp, $hook, $args = [] ) {
if ( function_exists( 'as_schedule_single_action' ) && self::is_ready() ) {
return as_schedule_single_action( $timestamp, $hook, $args, self::GROUP );
}

return wp_schedule_single_event( $timestamp, $hook, $args );
}

/**
* Ensure a recurring job is scheduled exactly once.
*
* Replaces the `wp_next_scheduled()` guard around `wp_schedule_event()`.
*
* Self-heals if Action Scheduler later appears or disappears: when it is
* available, any legacy WP-Cron entry is removed and the job runs on Action
* Scheduler; otherwise it runs on WP-Cron.
*
* @since 1.3.4
*
* @param string $hook The hook to fire.
* @param int $interval_seconds Interval between runs, in seconds (Action Scheduler).
* @param string $recurrence WP-Cron schedule name used for the fallback (e.g. `hourly`).
* @param list<mixed> $args Arguments to pass to the hook.
*
* @return void
*/
public static function ensure_recurring( $hook, $interval_seconds, $recurrence, $args = [] ) {
$has_as = function_exists( 'as_has_scheduled_action' ) && function_exists( 'as_schedule_recurring_action' );

if ( $has_as && ! self::is_ready() ) {
// Called before `init`: retry once the data store is up, so the
// WP-Cron entry is only cleared when its replacement can be booked.
add_action(
'action_scheduler_init',
static function () use ( $hook, $interval_seconds, $recurrence, $args ) {
self::ensure_recurring( $hook, $interval_seconds, $recurrence, $args );
}
);

return;
}

if ( $has_as ) {
if ( wp_next_scheduled( $hook, $args ) ) {
wp_clear_scheduled_hook( $hook, $args );
}

if ( ! as_has_scheduled_action( $hook, ! empty( $args ) ? $args : null, self::GROUP ) ) {
as_schedule_recurring_action( time(), $interval_seconds, $hook, $args, self::GROUP );
}

return;
}

if ( ! wp_next_scheduled( $hook, $args ) ) {
wp_schedule_event( time(), $recurrence, $hook, $args );
}
}
}
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ parameters:
- %currentWorkingDirectory%/inc
bootstrapFiles:
- %currentWorkingDirectory%/tests/php/static-analysis-stubs/hyve-lite.php
- %currentWorkingDirectory%/tests/php/static-analysis-stubs/action-scheduler.stubs.php
includes:
- %currentWorkingDirectory%/vendor/szepeviktor/phpstan-wordpress/extension.neon
86 changes: 86 additions & 0 deletions tests/php/static-analysis-stubs/action-scheduler.stubs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php
/**
* Action Scheduler function stubs.
*
* Action Scheduler is not a dependency of Hyve; it is used only when another
* plugin on the site provides it. These stubs let PHPStan resolve the optional
* `as_*` calls in `Scheduler`.
*
* @package Codeinwp\HyveLite
*
* phpcs:ignoreFile
*/

if ( ! function_exists( 'as_enqueue_async_action' ) ) {
/**
* @param string $hook
* @param array<int, mixed> $args
* @param string $group
* @param bool $unique
* @param int $priority
*
* @return int
*/
function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
return 0;
}
}

if ( ! function_exists( 'as_schedule_single_action' ) ) {
/**
* @param int $timestamp
* @param string $hook
* @param array<int, mixed> $args
* @param string $group
* @param bool $unique
* @param int $priority
*
* @return int
*/
function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
return 0;
}
}

if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
/**
* @param int $timestamp
* @param int $interval_in_seconds
* @param string $hook
* @param array<int, mixed> $args
* @param string $group
* @param bool $unique
* @param int $priority
*
* @return int
*/
function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
return 0;
}
}

if ( ! function_exists( 'as_has_scheduled_action' ) ) {
/**
* @param string $hook
* @param array<int, mixed>|null $args
* @param string $group
*
* @return bool
*/
function as_has_scheduled_action( $hook, $args = null, $group = '' ) {
return false;
}
}

if ( ! class_exists( 'ActionScheduler' ) ) {
class ActionScheduler {
/**
* @param string|null $function_name
*
* @return bool
*/
public static function is_initialized( $function_name = null ) {
return false;
}
}
}
Loading