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 +118 -133 1.5.02 → 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,14 +205,13 @@
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')
204 - ->select(['ID', 'post_title'])
213 + ->select(['ID', 'post_title', 'post_name'])
205 214 ->orderBy('post_title', 'ASC')
206 215 ->get();
207 216
208 217 $pages = [];
@@ -208,9 +217,10 @@
208 217 $pages = [];
209 218 foreach ($allPages as $page) {
210 219 $pages[] = [
211 220 'id' => $page->ID,
212 - 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards')
221 + 'name' => $page->post_name,
222 + 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-booking')
213 223 ];
214 224 }
215 225
216 226 return [
@@ -227,9 +237,9 @@
227 237 }
228 238
229 239 $settings = $request->get('settings', []);
230 240
231 - $prefSettings = Helper::getPrefSettins(false);
241 + $prefSettings = Helper::getPrefSettings(false);
232 242
233 243 $settings = wp_parse_args($settings, $prefSettings);
234 244
235 245 $settings = Arr::only($settings, array_keys($prefSettings));
@@ -252,10 +262,100 @@
252 262 'featureModules' => $settings
253 263 ]);
254 264 }
255 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 +
256 350 public function installPlugin(Request $request)
257 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 +
258 358 $pluginId = $request->get('plugin_id');
259 359
260 360 $allowedPlugins = [
261 361 'fluent-smtp' => [
@@ -282,8 +382,13 @@
282 382 'name' => 'FluentBoards',
283 383 'repo-slug' => 'fluent-boards',
284 384 'file' => 'fluent-boards.php',
285 385 ],
386 + 'fluent-cart' => [
387 + 'name' => 'FluentCart',
388 + 'repo-slug' => 'fluent-cart',
389 + 'file' => 'fluent-cart.php',
390 + ]
286 391 ];
287 392
288 393 if (!isset($allowedPlugins[$pluginId])) {
289 394 return $this->sendError([
@@ -290,131 +395,11 @@
290 395 'message' => __('This action is not allowed', 'fluent-booking')
291 396 ]);
292 397 }
293 398
294 - $this->backgroundInstaller($allowedPlugins[$pluginId], $pluginId);
399 + OnboardingService::backgroundInstaller($allowedPlugins[$pluginId]);
295 400
296 401 return $this->sendSuccess([
297 - 'message' => __('Plugin is being installed in the background', 'fluent-booking')
402 + 'message' => __('Plugin has been installed Successfully', 'fluent-booking')
298 403 ]);
299 - }
300 -
301 - private function backgroundInstaller($plugin_to_install, $plugin_id)
302 - {
303 - if (!empty($plugin_to_install['repo-slug'])) {
304 - require_once ABSPATH . 'wp-admin/includes/file.php';
305 - require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
306 - require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
307 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
308 -
309 - WP_Filesystem();
310 -
311 - $skin = new \Automatic_Upgrader_Skin();
312 - $upgrader = new \WP_Upgrader($skin);
313 - $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
314 - $plugin_slug = $plugin_to_install['repo-slug'];
315 - $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
316 - $installed = false;
317 - $activate = false;
318 -
319 - // See if the plugin is installed already.
320 - if (isset($installed_plugins[$plugin_file])) {
321 - $installed = true;
322 - $activate = !is_plugin_active($installed_plugins[$plugin_file]);
323 - }
324 -
325 - // Install this thing!
326 - if (!$installed) {
327 - // Suppress feedback.
328 - ob_start();
329 -
330 - try {
331 - $plugin_information = plugins_api(
332 - 'plugin_information',
333 - array(
334 - 'slug' => $plugin_slug,
335 - 'fields' => array(
336 - 'short_description' => false,
337 - 'sections' => false,
338 - 'requires' => false,
339 - 'rating' => false,
340 - 'ratings' => false,
341 - 'downloaded' => false,
342 - 'last_updated' => false,
343 - 'added' => false,
344 - 'tags' => false,
345 - 'homepage' => false,
346 - 'donate_link' => false,
347 - 'author_profile' => false,
348 - 'author' => false,
349 - ),
350 - )
351 - );
352 -
353 - if (is_wp_error($plugin_information)) {
354 - throw new \Exception($plugin_information->get_error_message());
355 - }
356 -
357 - $package = $plugin_information->download_link;
358 - $download = $upgrader->download_package($package);
359 -
360 - if (is_wp_error($download)) {
361 - throw new \Exception($download->get_error_message());
362 - }
363 -
364 - $working_dir = $upgrader->unpack_package($download, true);
365 -
366 - if (is_wp_error($working_dir)) {
367 - throw new \Exception($working_dir->get_error_message());
368 - }
369 -
370 - $result = $upgrader->install_package(
371 - array(
372 - 'source' => $working_dir,
373 - 'destination' => WP_PLUGIN_DIR,
374 - 'clear_destination' => false,
375 - 'abort_if_destination_exists' => false,
376 - 'clear_working' => true,
377 - 'hook_extra' => array(
378 - 'type' => 'plugin',
379 - 'action' => 'install',
380 - ),
381 - )
382 - );
383 -
384 - if (is_wp_error($result)) {
385 - throw new \Exception($result->get_error_message());
386 - }
387 -
388 - $activate = true;
389 -
390 - } catch (\Exception $e) {
391 - }
392 -
393 - // Discard feedback.
394 - ob_end_clean();
395 - }
396 -
397 - wp_clean_plugins_cache();
398 -
399 - // Activate this thing.
400 - if ($activate) {
401 - try {
402 - $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
403 -
404 - if (is_wp_error($result)) {
405 - throw new \Exception($result->get_error_message());
406 - }
407 - } catch (\Exception $e) {
408 - }
409 - }
410 - }
411 - }
412 -
413 - private function associate_plugin_file($plugins, $key)
414 - {
415 - $path = explode('/', $key);
416 - $filename = end($path);
417 - $plugins[$filename] = $key;
418 - return $plugins;
419 404 }
420 405 }