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 +116 -132 1.5.21 → 2.5.0 View file →
@@ -5,9 +5,11 @@
5 5 use FluentBooking\App\App;
6 6 use FluentBooking\App\Services\GlobalModules\GlobalModules;
7 7 use FluentBooking\App\Hooks\Handlers\AdminMenuHandler;
8 8 use FluentBooking\App\Services\Helper;
9 +use FluentBooking\App\Services\CurrenciesHelper;
9 10 use FluentBooking\App\Services\Libs\Countries;
11 +use FluentBooking\App\Services\OnboardingService;
10 12 use FluentBooking\Framework\Http\Request\Request;
11 13 use FluentBooking\Framework\Support\Arr;
12 14
13 15 class SettingsController extends Controller
@@ -80,13 +82,14 @@
80 82 'email_footer' => [
81 83 'wrapper_class' => 'fc_full_width fc_mb_0 fc_wp_editor',
82 84 'type' => 'wp-editor-field',
83 85 'label' => __('Email Footer for Booking related emails (Optional)', 'fluent-booking'),
84 - 'inline_help' => __('You may include your business name, address etc here, for example: <br />You have received this email because signed up for an event or made a booking on our website.', 'fluent-booking')
86 + 'inline_help' => __('You may include your business name, address, etc. here. For example: <br />You have received this email because you signed up for an event or made a booking on our website.', 'fluent-booking')
85 87 ]
86 88 ];
87 89
88 90 $settings['all_countries'] = Countries::get();
91 + $settings['share_stats'] = Arr::get((array) Helper::getMeta('option', 0, 'optin'), 'share_stats', '');
89 92
90 93 return apply_filters('fluent_booking/general_settings', $settings);
91 94 }
92 95
@@ -105,11 +108,12 @@
105 108 $santizedSettings['email_footer'] = wp_kses_post($setting['email_footer']);
106 109 }
107 110 $formattedSettings[$settingKey] = $santizedSettings;
108 111 }
109 - $formattedSettings['time_format'] = $request->get('timeFormat');
112 + $formattedSettings['time_format'] = sanitize_text_field($request->get('timeFormat'));
110 113
111 - 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');
112 116
113 117 return [
114 118 'message' => __('Settings updated successfully', 'fluent-booking'),
115 119 'settings' => $formattedSettings
@@ -119,16 +123,22 @@
119 123 public function updatePaymentSettings(Request $request)
120 124 {
121 125 $paymentSettings = $request->get('payments', []);
122 126
123 - $currency = Arr::get($paymentSettings, 'currency');
127 + $currency = Arr::get($paymentSettings, 'currency', 'USD');
124 128 $isActive = Arr::get($paymentSettings, 'is_active', 'no');
129 + $numberFormat = Arr::get($paymentSettings, 'number_format', 'comma_separated');
130 + $currencyPosition = Arr::get($paymentSettings, 'currency_position', 'left');
125 131
126 132 update_option('fluent_booking_global_payment_settings', [
127 - 'currency' => sanitize_text_field($currency),
128 - 'is_active' => ($isActive == 'yes') ? 'yes' : 'no'
133 + 'currency' => sanitize_text_field($currency),
134 + 'is_active' => $isActive == 'yes' ? 'yes' : 'no',
135 + 'number_format' => $numberFormat == 'comma_separated' ? 'comma_separated' : 'dot_separated',
136 + 'currency_position' => sanitize_text_field($currencyPosition)
129 137 ], 'no');
130 138
139 + CurrenciesHelper::getGlobalCurrencySettings(true);
140 +
131 141 return [
132 142 'message' => __('Settings updated successfully', 'fluent-booking')
133 143 ];
134 144 }
@@ -156,9 +166,9 @@
156 166 if (!$settings) {
157 167 $settings = (object)[];
158 168 }
159 169
160 - $featuresPrefs = Helper::getPrefSettins(false);
170 + $featuresPrefs = Helper::getPrefSettings(false);
161 171
162 172 if (empty($featuresPrefs['frontend']['render_type'])) {
163 173 $featuresPrefs['frontend']['render_type'] = 'standalone';
164 174 }
@@ -195,9 +205,8 @@
195 205 }
196 206
197 207 public function getPages(Request $request)
198 208 {
199 -
200 209 $db = App::getInstance('db');
201 210
202 211 $allPages = $db->table('posts')->where('post_type', 'page')
203 212 ->where('post_status', 'publish')
@@ -209,9 +218,9 @@
209 218 foreach ($allPages as $page) {
210 219 $pages[] = [
211 220 'id' => $page->ID,
212 221 'name' => $page->post_name,
213 - 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards')
222 + 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-booking')
214 223 ];
215 224 }
216 225
217 226 return [
@@ -228,9 +237,9 @@
228 237 }
229 238
230 239 $settings = $request->get('settings', []);
231 240
232 - $prefSettings = Helper::getPrefSettins(false);
241 + $prefSettings = Helper::getPrefSettings(false);
233 242
234 243 $settings = wp_parse_args($settings, $prefSettings);
235 244
236 245 $settings = Arr::only($settings, array_keys($prefSettings));
@@ -253,10 +262,100 @@
253 262 'featureModules' => $settings
254 263 ]);
255 264 }
256 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';
348 + }
349 +
257 350 public function installPlugin(Request $request)
258 351 {
352 + if (!current_user_can('install_plugins')) {
353 + return $this->sendError([
354 + 'message' => __('You do not have permission to install plugins', 'fluent-booking')
355 + ]);
356 + }
357 +
259 358 $pluginId = $request->get('plugin_id');
260 359
261 360 $allowedPlugins = [
262 361 'fluent-smtp' => [
@@ -283,8 +382,13 @@
283 382 'name' => 'FluentBoards',
284 383 'repo-slug' => 'fluent-boards',
285 384 'file' => 'fluent-boards.php',
286 385 ],
386 + 'fluent-cart' => [
387 + 'name' => 'FluentCart',
388 + 'repo-slug' => 'fluent-cart',
389 + 'file' => 'fluent-cart.php',
390 + ]
287 391 ];
288 392
289 393 if (!isset($allowedPlugins[$pluginId])) {
290 394 return $this->sendError([
@@ -291,131 +395,11 @@
291 395 'message' => __('This action is not allowed', 'fluent-booking')
292 396 ]);
293 397 }
294 398
295 - $this->backgroundInstaller($allowedPlugins[$pluginId], $pluginId);
399 + OnboardingService::backgroundInstaller($allowedPlugins[$pluginId]);
296 400
297 401 return $this->sendSuccess([
298 - 'message' => __('Plugin is being installed in the background', 'fluent-booking')
402 + 'message' => __('Plugin has been installed Successfully', 'fluent-booking')
299 403 ]);
300 - }
301 -
302 - private function backgroundInstaller($plugin_to_install, $plugin_id)
303 - {
304 - if (!empty($plugin_to_install['repo-slug'])) {
305 - require_once ABSPATH . 'wp-admin/includes/file.php';
306 - require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
307 - require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
308 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
309 -
310 - WP_Filesystem();
311 -
312 - $skin = new \Automatic_Upgrader_Skin();
313 - $upgrader = new \WP_Upgrader($skin);
314 - $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
315 - $plugin_slug = $plugin_to_install['repo-slug'];
316 - $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
317 - $installed = false;
318 - $activate = false;
319 -
320 - // See if the plugin is installed already.
321 - if (isset($installed_plugins[$plugin_file])) {
322 - $installed = true;
323 - $activate = !is_plugin_active($installed_plugins[$plugin_file]);
324 - }
325 -
326 - // Install this thing!
327 - if (!$installed) {
328 - // Suppress feedback.
329 - ob_start();
330 -
331 - try {
332 - $plugin_information = plugins_api(
333 - 'plugin_information',
334 - array(
335 - 'slug' => $plugin_slug,
336 - 'fields' => array(
337 - 'short_description' => false,
338 - 'sections' => false,
339 - 'requires' => false,
340 - 'rating' => false,
341 - 'ratings' => false,
342 - 'downloaded' => false,
343 - 'last_updated' => false,
344 - 'added' => false,
345 - 'tags' => false,
346 - 'homepage' => false,
347 - 'donate_link' => false,
348 - 'author_profile' => false,
349 - 'author' => false,
350 - ),
351 - )
352 - );
353 -
354 - if (is_wp_error($plugin_information)) {
355 - throw new \Exception($plugin_information->get_error_message());
356 - }
357 -
358 - $package = $plugin_information->download_link;
359 - $download = $upgrader->download_package($package);
360 -
361 - if (is_wp_error($download)) {
362 - throw new \Exception($download->get_error_message());
363 - }
364 -
365 - $working_dir = $upgrader->unpack_package($download, true);
366 -
367 - if (is_wp_error($working_dir)) {
368 - throw new \Exception($working_dir->get_error_message());
369 - }
370 -
371 - $result = $upgrader->install_package(
372 - array(
373 - 'source' => $working_dir,
374 - 'destination' => WP_PLUGIN_DIR,
375 - 'clear_destination' => false,
376 - 'abort_if_destination_exists' => false,
377 - 'clear_working' => true,
378 - 'hook_extra' => array(
379 - 'type' => 'plugin',
380 - 'action' => 'install',
381 - ),
382 - )
383 - );
384 -
385 - if (is_wp_error($result)) {
386 - throw new \Exception($result->get_error_message());
387 - }
388 -
389 - $activate = true;
390 -
391 - } catch (\Exception $e) {
392 - }
393 -
394 - // Discard feedback.
395 - ob_end_clean();
396 - }
397 -
398 - wp_clean_plugins_cache();
399 -
400 - // Activate this thing.
401 - if ($activate) {
402 - try {
403 - $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
404 -
405 - if (is_wp_error($result)) {
406 - throw new \Exception($result->get_error_message());
407 - }
408 - } catch (\Exception $e) {
409 - }
410 - }
411 - }
412 - }
413 -
414 - private function associate_plugin_file($plugins, $key)
415 - {
416 - $path = explode('/', $key);
417 - $filename = end($path);
418 - $plugins[$filename] = $key;
419 - return $plugins;
420 404 }
421 405 }