# fluent-community/2.10.0/app/Hooks/Handlers/Scheduler.php

FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS &amp; Online Courses, version 2.10.0. 170 lines.

- Page: https://pluginprobe.com/plugins/fluent-community/2.10.0/code/app/Hooks/Handlers/Scheduler.php
- Raw: https://pluginprobe.com/plugins/fluent-community/2.10.0/raw/app/Hooks/Handlers/Scheduler.php
- Modified: 2026-09-14T14:31:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-community/2.10.0/code/app/Hooks/Handlers/Scheduler.php#L10-L20`.

```php
<?php

namespace FluentCommunity\App\Hooks\Handlers;

use FluentCommunity\App\Functions\Utility;
use FluentCommunity\Framework\Support\Arr;
use FluentCommunity\Framework\Support\DateTime;
use FluentCommunity\App\Services\Helper;
use FluentCommunity\App\Services\NotificationPref;
use FluentCommunity\Database\Migrations\NotificationPrefMigrator;

class Scheduler
{
    public function register()
    {
        add_action('fluent_community_scheduled_hour_jobs', function () {
            $this->checkDailyDigestSchedule();
            do_action('fluent_community/maybe_delete_draft_medias');
        }, 10);

        add_action('fluent_community_send_daily_digest_init', function () {
            do_action('fluent_community_send_daily_digest');
        }, 10);

        /*
         * Continuation for a preference backfill that ran out of request budget.
         * Not reachable through DBMigrator: boot/app.php stamps the db-version
         * option as soon as that returns, which closes the gate on any further
         * migrator pass.
         */
        add_action(NotificationPrefMigrator::RESUME_HOOK, function () {
            NotificationPrefMigrator::continueBackfill();
        }, 10);

        add_action('fluent_community_daily_jobs', function () {
            // let's fire the old email notifications hook
            do_action('fluent_community/remove_old_notifications');
            $this->maybeRemoveOldScheuledActionLogs();
        }, 10);

    }

    public function checkDailyDigestSchedule($willReset = false)
    {
        $notificationSettings = Utility::getEmailNotificationSettings();
        $globalStatus = Arr::get($notificationSettings, 'digest_email_status', 'no');

        if ($globalStatus != 'yes') {
            // Global Status is false
            // Check if any user enabled that or not
            // Answered from a denormalized option refreshed on the preference
            // write path. This used to be an hourly unindexed scan of the
            // notification receipts table looking for a handful of pref rows.
            $isEnabled = NotificationPref::hasAnyEnabled('digest');

            if (!$isEnabled) {
                // unset the scheduled action
                if (\as_next_scheduled_action('fluent_community_send_daily_digest_init')) {
                    \as_unschedule_all_actions('fluent_community_send_daily_digest_init', [], 'fluent-community');
                }
                return;
            }
        }

        $notificationDay = Arr::get($notificationSettings, 'digest_mail_day');
        $digestTime = Arr::get($notificationSettings, 'daily_digest_time', '09:00');

        // Let's check if we have the daily digest action scheduled or not
        if (!\as_next_scheduled_action('fluent_community_send_daily_digest_init')) {
            $timestamp = $this->getNextOccurrenceTimestamp(Helper::getFullDayName($notificationDay), $digestTime);
            if ($timestamp) {
                \as_schedule_single_action($timestamp, 'fluent_community_send_daily_digest_init', [], 'fluent-community', true);
            }
        }
    }

    private function maybeRemoveOldScheuledActionLogs($group_slug = 'fluent-community', $days_old = 7)
    {
        global $wpdb;

        // Get the timestamp for $days_old days ago
        $cutoff_date = gmdate('Y-m-d H:i:s', strtotime("-{$days_old} days"));

        // Get the group ID
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $group_id = $wpdb->get_var($wpdb->prepare(
            "SELECT group_id FROM {$wpdb->prefix}actionscheduler_groups WHERE slug = %s",
            $group_slug
        ));

        if (!$group_id) {
            return false; // Group not found
        }

        // Delete old actions and their associated logs in bounded batches. Action
        // Scheduler tables are read/written constantly by the queue runner, so a
        // single unbounded multi-table DELETE could hold a large lock and bloat the
        // transaction on busy sites. We loop while batches stay full and we are still
        // within a safe time budget (mirrors the other cleanup jobs in this plugin).
        $batchSize = 500;
        $totalDeleted = 0;

        do {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $actionIds = $wpdb->get_col($wpdb->prepare(
                "SELECT action_id
                 FROM {$wpdb->prefix}actionscheduler_actions
                 WHERE group_id = %d
                 AND status IN ('complete', 'failed')
                 AND scheduled_date_gmt < %s
                 LIMIT %d",
                $group_id, $cutoff_date, $batchSize
            ));

            if (!$actionIds) {
                break;
            }

            // Safe to interpolate: every id is cast to an integer.
            $ids = implode(',', array_map('intval', $actionIds));

            // Remove the associated logs first, then the actions themselves.
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $wpdb->query("DELETE FROM {$wpdb->prefix}actionscheduler_logs WHERE action_id IN ({$ids})");
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            $totalDeleted += (int) $wpdb->query("DELETE FROM {$wpdb->prefix}actionscheduler_actions WHERE action_id IN ({$ids})");

            $isFullBatch = count($actionIds) === $batchSize;
        } while ($isFullBatch && (microtime(true) - FLUENT_COMMUNITY_START_TIME) < 30);

        // Clean up orphaned claims (claims whose actions no longer exist)
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query("
        DELETE c
        FROM {$wpdb->prefix}actionscheduler_claims c
        LEFT JOIN {$wpdb->prefix}actionscheduler_actions a ON c.claim_id = a.claim_id
        WHERE a.action_id IS NULL");

        return $totalDeleted;
    }

    private function getNextOccurrenceTimestamp($dayname, $time)
    {
        $dayname = strtolower($dayname);
        $valid_days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
        if (!in_array($dayname, $valid_days, true)) {
            return false;
        }

        $current = current_datetime();
        $currentDay = strtolower($current->format('l'));
        $target = new \DateTime($current->format('Y-m-d') . ' ' . $time, wp_timezone());

        $currentDayIndex = array_search($currentDay, $valid_days, true);
        $targetDayIndex = array_search($dayname, $valid_days, true);
        $dayOffset = ($targetDayIndex - $currentDayIndex + 7) % 7;

        if ($dayOffset) {
            $target->modify('+' . $dayOffset . ' days');
        } elseif ($target <= $current) {
            $target->modify('+7 days');
        }

        $target->setTimezone(new \DateTimeZone('UTC'));

        return $target->getTimestamp();
    }

}

```
