PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / app / Hooks / Handlers / Scheduler.php

Scheduler.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.11.0, at app/Hooks/Handlers/Scheduler.php

170 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Hooks\Handlers;
4
5 use FluentCommunity\App\Functions\Utility;
6 use FluentCommunity\Framework\Support\Arr;
7 use FluentCommunity\Framework\Support\DateTime;
8 use FluentCommunity\App\Services\Helper;
9 use FluentCommunity\App\Services\NotificationPref;
10 use FluentCommunity\Database\Migrations\NotificationPrefMigrator;
11
12 class Scheduler
13 {
14 public function register()
15 {
16 add_action('fluent_community_scheduled_hour_jobs', function () {
17 $this->checkDailyDigestSchedule();
18 do_action('fluent_community/maybe_delete_draft_medias');
19 }, 10);
20
21 add_action('fluent_community_send_daily_digest_init', function () {
22 do_action('fluent_community_send_daily_digest');
23 }, 10);
24
25 /*
26 * Continuation for a preference backfill that ran out of request budget.
27 * Not reachable through DBMigrator: boot/app.php stamps the db-version
28 * option as soon as that returns, which closes the gate on any further
29 * migrator pass.
30 */
31 add_action(NotificationPrefMigrator::RESUME_HOOK, function () {
32 NotificationPrefMigrator::continueBackfill();
33 }, 10);
34
35 add_action('fluent_community_daily_jobs', function () {
36 // let's fire the old email notifications hook
37 do_action('fluent_community/remove_old_notifications');
38 $this->maybeRemoveOldScheuledActionLogs();
39 }, 10);
40
41 }
42
43 public function checkDailyDigestSchedule($willReset = false)
44 {
45 $notificationSettings = Utility::getEmailNotificationSettings();
46 $globalStatus = Arr::get($notificationSettings, 'digest_email_status', 'no');
47
48 if ($globalStatus != 'yes') {
49 // Global Status is false
50 // Check if any user enabled that or not
51 // Answered from a denormalized option refreshed on the preference
52 // write path. This used to be an hourly unindexed scan of the
53 // notification receipts table looking for a handful of pref rows.
54 $isEnabled = NotificationPref::hasAnyEnabled('digest');
55
56 if (!$isEnabled) {
57 // unset the scheduled action
58 if (\as_next_scheduled_action('fluent_community_send_daily_digest_init')) {
59 \as_unschedule_all_actions('fluent_community_send_daily_digest_init', [], 'fluent-community');
60 }
61 return;
62 }
63 }
64
65 $notificationDay = Arr::get($notificationSettings, 'digest_mail_day');
66 $digestTime = Arr::get($notificationSettings, 'daily_digest_time', '09:00');
67
68 // Let's check if we have the daily digest action scheduled or not
69 if (!\as_next_scheduled_action('fluent_community_send_daily_digest_init')) {
70 $timestamp = $this->getNextOccurrenceTimestamp(Helper::getFullDayName($notificationDay), $digestTime);
71 if ($timestamp) {
72 \as_schedule_single_action($timestamp, 'fluent_community_send_daily_digest_init', [], 'fluent-community', true);
73 }
74 }
75 }
76
77 private function maybeRemoveOldScheuledActionLogs($group_slug = 'fluent-community', $days_old = 7)
78 {
79 global $wpdb;
80
81 // Get the timestamp for $days_old days ago
82 $cutoff_date = gmdate('Y-m-d H:i:s', strtotime("-{$days_old} days"));
83
84 // Get the group ID
85 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
86 $group_id = $wpdb->get_var($wpdb->prepare(
87 "SELECT group_id FROM {$wpdb->prefix}actionscheduler_groups WHERE slug = %s",
88 $group_slug
89 ));
90
91 if (!$group_id) {
92 return false; // Group not found
93 }
94
95 // Delete old actions and their associated logs in bounded batches. Action
96 // Scheduler tables are read/written constantly by the queue runner, so a
97 // single unbounded multi-table DELETE could hold a large lock and bloat the
98 // transaction on busy sites. We loop while batches stay full and we are still
99 // within a safe time budget (mirrors the other cleanup jobs in this plugin).
100 $batchSize = 500;
101 $totalDeleted = 0;
102
103 do {
104 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
105 $actionIds = $wpdb->get_col($wpdb->prepare(
106 "SELECT action_id
107 FROM {$wpdb->prefix}actionscheduler_actions
108 WHERE group_id = %d
109 AND status IN ('complete', 'failed')
110 AND scheduled_date_gmt < %s
111 LIMIT %d",
112 $group_id, $cutoff_date, $batchSize
113 ));
114
115 if (!$actionIds) {
116 break;
117 }
118
119 // Safe to interpolate: every id is cast to an integer.
120 $ids = implode(',', array_map('intval', $actionIds));
121
122 // Remove the associated logs first, then the actions themselves.
123 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
124 $wpdb->query("DELETE FROM {$wpdb->prefix}actionscheduler_logs WHERE action_id IN ({$ids})");
125 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
126 $totalDeleted += (int) $wpdb->query("DELETE FROM {$wpdb->prefix}actionscheduler_actions WHERE action_id IN ({$ids})");
127
128 $isFullBatch = count($actionIds) === $batchSize;
129 } while ($isFullBatch && (microtime(true) - FLUENT_COMMUNITY_START_TIME) < 30);
130
131 // Clean up orphaned claims (claims whose actions no longer exist)
132 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
133 $wpdb->query("
134 DELETE c
135 FROM {$wpdb->prefix}actionscheduler_claims c
136 LEFT JOIN {$wpdb->prefix}actionscheduler_actions a ON c.claim_id = a.claim_id
137 WHERE a.action_id IS NULL");
138
139 return $totalDeleted;
140 }
141
142 private function getNextOccurrenceTimestamp($dayname, $time)
143 {
144 $dayname = strtolower($dayname);
145 $valid_days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
146 if (!in_array($dayname, $valid_days, true)) {
147 return false;
148 }
149
150 $current = current_datetime();
151 $currentDay = strtolower($current->format('l'));
152 $target = new \DateTime($current->format('Y-m-d') . ' ' . $time, wp_timezone());
153
154 $currentDayIndex = array_search($currentDay, $valid_days, true);
155 $targetDayIndex = array_search($dayname, $valid_days, true);
156 $dayOffset = ($targetDayIndex - $currentDayIndex + 7) % 7;
157
158 if ($dayOffset) {
159 $target->modify('+' . $dayOffset . ' days');
160 } elseif ($target <= $current) {
161 $target->modify('+7 days');
162 }
163
164 $target->setTimezone(new \DateTimeZone('UTC'));
165
166 return $target->getTimestamp();
167 }
168
169 }
170