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/Http/Controllers/SettingsController.php +88 -2 2.3.0 → 2.5.0 View file →
@@ -87,8 +87,9 @@
87 87 ]
88 88 ];
89 89
90 90 $settings['all_countries'] = Countries::get();
91 + $settings['share_stats'] = Arr::get((array) Helper::getMeta('option', 0, 'optin'), 'share_stats', '');
91 92
92 93 return apply_filters('fluent_booking/general_settings', $settings);
93 94 }
94 95
@@ -107,11 +108,12 @@
107 108 $santizedSettings['email_footer'] = wp_kses_post($setting['email_footer']);
108 109 }
109 110 $formattedSettings[$settingKey] = $santizedSettings;
110 111 }
111 - $formattedSettings['time_format'] = $request->get('timeFormat');
112 + $formattedSettings['time_format'] = sanitize_text_field($request->get('timeFormat'));
112 113
113 - update_option('_fluent_booking_settings', $formattedSettings, 'no');
114 + // The option also holds keys saved elsewhere, e.g. theme from updateThemeSettings().
115 + update_option('_fluent_booking_settings', array_merge((array) get_option('_fluent_booking_settings', []), $formattedSettings), 'no');
114 116
115 117 return [
116 118 'message' => __('Settings updated successfully', 'fluent-booking'),
117 119 'settings' => $formattedSettings
@@ -258,8 +260,92 @@
258 260 return $this->sendSuccess([
259 261 'message' => __('Settings are saved', 'fluent-booking'),
260 262 'featureModules' => $settings
261 263 ]);
264 + }
265 +
266 + public function updateOptin(Request $request)
267 + {
268 + // Validated before sanitizing: sanitize_email() turns a typo into '', which would pass
269 + // `nullable` and drop the address without telling anyone.
270 + $this->validate([
271 + 'share_stats' => sanitize_text_field($request->get('share_stats', '')),
272 + 'optin_email' => trim((string) $request->get('optin_email', ''))
273 + ], [
274 + 'share_stats' => 'nullable|in:yes,no',
275 + 'optin_email' => 'nullable|email'
276 + ], [
277 + 'optin_email.email' => __('Please enter a valid email address', 'fluent-booking')
278 + ]);
279 +
280 + $data = [
281 + 'share_stats' => sanitize_text_field($request->get('share_stats', '')),
282 + 'optin_email' => sanitize_email($request->get('optin_email', '')),
283 + 'optin_name' => sanitize_text_field($request->get('optin_name', ''))
284 + ];
285 +
286 + // One record: the stats decision, the opt-in email, when the prompt was dismissed and
287 + // when stats were last sent.
288 + $optin = (array) Helper::getMeta('option', 0, 'optin');
289 +
290 + if ($data['share_stats']) {
291 + $optin['share_stats'] = $data['share_stats'];
292 +
293 + // Opting back in sends straight away rather than waiting out the old interval.
294 + if ($data['share_stats'] === 'no') {
295 + unset($optin['last_sent_at']);
296 + }
297 + }
298 +
299 + // Closing the prompt only snoozes it; it says nothing about stats.
300 + // See AdminMenuHandler::showOptinPrompt().
301 + if ($request->get('dismiss') === 'yes') {
302 + $optin['dismissed_at'] = time();
303 + }
304 +
305 + $optinStatus = '';
306 + if ($data['optin_email']) {
307 + // Reported with the usage data instead of the site's admin email.
308 + $optin['email'] = $data['optin_email'];
309 + $optinStatus = $this->subscribeToNewsletter($data['optin_email'], $data['optin_name']);
310 + }
311 +
312 + Helper::updateMeta('option', 0, 'optin', $optin);
313 +
314 + return [
315 + 'message' => __('Settings updated successfully', 'fluent-booking'),
316 + 'share_stats' => Arr::get($optin, 'share_stats', ''),
317 + 'optin_status' => $optinStatus
318 + ];
319 + }
320 +
321 + /**
322 + * Hands the admin's name and email to the licensing Worker, which forwards them to
323 + * fluentbooking.com's FluentCRM. Pro is matched later by site url, so it must be home_url().
324 + *
325 + * @return string 'confirm_email', 'subscribed' or 'queued'
326 + */
327 + private function subscribeToNewsletter($email, $name)
328 + {
329 + $response = wp_remote_post('https://fluentapi.wpmanageninja.com/subscribe', [
330 + 'timeout' => 15,
331 + 'headers' => ['Content-Type' => 'application/json'],
332 + 'cookies' => [],
333 + 'body' => wp_json_encode([
334 + 'product' => 'fluent-booking',
335 + 'product_version' => FLUENT_BOOKING_VERSION,
336 + 'site_url' => home_url(),
337 + 'email' => $email,
338 + 'full_name' => $name,
339 + 'consent_version' => '2026-09-a',
340 + 'locale' => get_user_locale()
341 + ])
342 + ]);
343 +
344 + $body = json_decode(wp_remote_retrieve_body($response), true);
345 + $status = is_array($body) ? Arr::get($body, 'status') : '';
346 +
347 + return in_array($status, ['confirm_email', 'subscribed'], true) ? $status : 'queued';
262 348 }
263 349
264 350 public function installPlugin(Request $request)
265 351 {