PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Hooks/Scheduler/DailyScheduler.php +145 -1 2.2.0 → 2.5.0 View file →
@@ -1,9 +1,15 @@
1 1 <?php
2 2
3 3 namespace FluentBooking\App\Hooks\Scheduler;
4 4
5 +use FluentBooking\App\Models\Booking;
6 +use FluentBooking\App\Models\Calendar;
7 +use FluentBooking\App\Models\CalendarSlot;
8 +use FluentBooking\App\Modules\MCP\Support\WriteGuard;
9 +use FluentBooking\App\Services\Helper;
5 10 use FluentBooking\App\Services\SummaryReportService;
11 +use FluentBooking\Framework\Support\Arr;
6 12
7 13 class DailyScheduler
8 14 {
9 15 public function register()
@@ -12,7 +18,145 @@
12 18 }
13 19
14 20 public function handleDailyTasks()
15 21 {
22 + // First and in its own try: MCP guard records live in options, so
23 + // nothing else expires them, and a failing summary must not block this.
24 + try {
25 + WriteGuard::purgeExpired();
26 + } catch (\Throwable $e) {
27 + if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) {
28 + error_log('FluentBooking MCP purge failed: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
29 + }
30 + }
31 +
16 32 SummaryReportService::maybeSendSummary();
33 +
34 + // Last, so a slow remote cannot hold up the summary email.
35 + try {
36 + $this->maybeSendUsageData();
37 + } catch (\Throwable $e) {
38 + if (defined('FLUENT_BOOKING_DEBUG') && FLUENT_BOOKING_DEBUG) {
39 + error_log('FluentBooking stats sharing failed: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
40 + }
41 + }
17 42 }
18 -}
43 +
44 + /**
45 + * Weekly usage report (counts only), sent only after an admin opted in.
46 + *
47 + * The maintenance/environment report rides along when due. `_fluent_last_m_run` is
48 + * shared by the Fluent plugins, so whichever reports first covers the rest for six days.
49 + */
50 + private function maybeSendUsageData()
51 + {
52 + $optin = (array) Helper::getMeta('option', 0, 'optin');
53 +
54 + /**
55 + * Whether FluentBooking may send usage data.
56 + *
57 + * @since 2.4.1
58 + *
59 + * @param bool $allowed True once an admin has opted in.
60 + */
61 + if (!apply_filters('fluent_booking/allow_share_stats', Arr::get($optin, 'share_stats') === 'yes')) {
62 + return;
63 + }
64 +
65 + $lastSent = (int) Arr::get($optin, 'last_sent_at', 0);
66 + if ($lastSent && (time() - $lastSent) < 6 * DAY_IN_SECONDS) {
67 + return;
68 + }
69 +
70 + $report = [
71 + 'product' => 'fluent-booking',
72 + 'product_version' => FLUENT_BOOKING_VERSION,
73 + // home_url(), not site_url(): the same site key licensing records under.
74 + 'site_url' => home_url(),
75 + 'metrics' => $this->getUsageMetrics()
76 + ];
77 +
78 + $lastMaintenance = (int) get_option('_fluent_last_m_run');
79 + if (!$lastMaintenance || (time() - $lastMaintenance) > 6 * DAY_IN_SECONDS) {
80 + $report['maintenance'] = $this->getMaintenanceData(Arr::get($optin, 'email', ''));
81 + }
82 +
83 + /**
84 + * The usage report sent to WPManageNinja once an admin has opted in.
85 + *
86 + * @since 2.4.1
87 + *
88 + * @param array $report product, product_version, site_url, metrics and, when due, maintenance
89 + */
90 + $report = apply_filters('fluent_booking/share_stats_data', $report);
91 +
92 + $response = wp_remote_post('https://fluentapi.wpmanageninja.com/stats', [
93 + 'timeout' => 10,
94 + 'headers' => ['Content-Type' => 'application/json'],
95 + 'body' => wp_json_encode($report),
96 + 'cookies' => []
97 + ]);
98 +
99 + $code = is_wp_error($response) ? 0 : (int) wp_remote_retrieve_response_code($response);
100 + if ($code < 200 || $code >= 300) {
101 + return;
102 + }
103 +
104 + $optin['last_sent_at'] = time();
105 + Helper::updateMeta('option', 0, 'optin', $optin);
106 +
107 + // Any 2xx counts, even `"maintenance": false`: a block the Worker rejected would be
108 + // rejected again next week, and it shows in the panel's errors.
109 + if (isset($report['maintenance'])) {
110 + update_option('_fluent_last_m_run', time());
111 + }
112 + }
113 +
114 + /**
115 + * Counts only, never a person. Keys are snake_case with the window in the name.
116 + */
117 + private function getUsageMetrics()
118 + {
119 + $metrics = [
120 + 'calendars' => Calendar::count(),
121 + 'events' => CalendarSlot::count(),
122 + 'bookings' => Booking::count(),
123 + 'bookings_30d' => Booking::where('created_at', '>=', gmdate('Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS))->count(),
124 + 'has_pro' => defined('FLUENT_BOOKING_PRO_DIR_FILE')
125 + ];
126 +
127 + $eventTypes = CalendarSlot::groupBy('event_type')
128 + ->selectRaw('event_type, COUNT(*) as total')
129 + ->pluck('total', 'event_type')
130 + ->toArray();
131 +
132 + foreach ($eventTypes as $type => $total) {
133 + $type = trim(preg_replace('/[^a-z0-9]+/', '_', strtolower((string) $type)), '_');
134 + if ($type) {
135 + $metrics['events_' . $type] = (int) $total;
136 + }
137 + }
138 +
139 + return $metrics;
140 + }
141 +
142 + /**
143 + * The same fields FluentCRM sends to /plugin-maintenance. The email is only the one the admin
144 + * gave when opting in to emails; the stats consent alone never sends an address.
145 + */
146 + private function getMaintenanceData($optinEmail)
147 + {
148 + global $wp_version;
149 +
150 + return [
151 + 'plugin_version' => FLUENT_BOOKING_VERSION,
152 + 'php_version' => PHP_VERSION,
153 + 'wp_version' => $wp_version,
154 + 'plugins' => (array) get_option('active_plugins'),
155 + 'site_lang' => get_bloginfo('language'),
156 + 'site_url' => home_url(),
157 + 'theme' => wp_get_theme()->get('Name'),
158 + 'site_title' => get_bloginfo('name'),
159 + 'admin_email' => (string) $optinEmail
160 + ];
161 + }
162 +}