PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.1.2
Fluent Support – Helpdesk & Customer Support Ticket System v2.1.2
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
← All changes | app/Http/Controllers/SettingsController.php +149 -32 1.10.32.1.2 View file →
@@ -8,10 +8,11 @@
8 8 use FluentSupport\App\Models\Product;
9 9 use FluentSupport\App\Services\EmailNotification\Settings;
10 10 use FluentSupport\App\Services\Helper;
11 11 use FluentSupport\Database\Migrations\AIActivityLogsMigrator;
12 -use FluentSupport\Framework\Request\Request;
12 +use FluentSupport\Framework\Http\Request\Request;
13 13 use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
14 +use FluentSupport\Framework\Support\Arr;
14 15
15 16 /**
16 17 * SettingsController class is responsible for all settings
17 18 * This class is responsible for all request related to settings under global settings tab
@@ -58,9 +59,28 @@
58 59 */
59 60 public function saveSettings(Request $request)
60 61 {
61 62 $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
62 - $settings = wp_unslash($request->getSafe('settings', null, []));
63 + $settings = wp_unslash($request->get('settings', null));
64 +
65 + // wp-editor fields: sanitize with wp_kses_post (same approach as Fluent Cart)
66 + $htmlFields = ['login_message'];
67 + $htmlValues = [];
68 + if (is_array($settings)) {
69 + foreach ($htmlFields as $field) {
70 + if (isset($settings[$field])) {
71 + $htmlValues[$field] = wp_kses_post($settings[$field]);
72 + }
73 + }
74 + }
75 +
76 + $settings = is_array($settings) ? map_deep($settings, 'sanitize_text_field') : [];
77 +
78 + // Restore HTML fields
79 + foreach ($htmlValues as $field => $value) {
80 + $settings[$field] = $value;
81 + }
82 +
63 83 (new Settings)->save($settingsKey, $settings);
64 84
65 85 return [
66 86 'message' => __('Settings has been updated', 'fluent-support')
@@ -85,9 +105,16 @@
85 105 * @throws \FluentSupport\Framework\Validator\ValidationException
86 106 */
87 107 public function setupPortal(Request $request)
88 108 {
89 - $mailbox = $request->getSafe('mailbox');
109 + $mailbox = $request->get('mailbox', null);
110 + $mailbox = is_array($mailbox) ? [
111 + 'name' => isset($mailbox['name']) ? sanitize_text_field($mailbox['name']) : '',
112 + 'email' => isset($mailbox['email']) ? sanitize_email($mailbox['email']) : '',
113 + 'box_type' => isset($mailbox['box_type']) ? sanitize_key($mailbox['box_type']) : '',
114 + 'is_default' => isset($mailbox['is_default']) ? sanitize_text_field($mailbox['is_default']) : 'yes',
115 + ] : [];
116 +
90 117 $this->validate($mailbox, [
91 118 'name' => 'required',
92 119 'email' => 'required|email',
93 120 'box_type' => 'required'
@@ -92,16 +119,18 @@
92 119 'email' => 'required|email',
93 120 'box_type' => 'required'
94 121 ]);
95 122
96 - $settings = $request->getSafe('global_settings');
123 + $settings = $request->get('global_settings', null);
124 + $settings = is_array($settings) ? [
125 + 'create_portal_page' => isset($settings['create_portal_page']) ? sanitize_text_field($settings['create_portal_page']) : 'no',
126 + 'portal_page_id' => isset($settings['portal_page_id']) ? intval($settings['portal_page_id']) : 0,
127 + ] : [];
97 128
98 129 $createPage = $settings['create_portal_page'] == 'yes';
99 130
100 131 if (!$createPage && empty($settings['portal_page_id'])) {
101 - return $this->sendError([
102 - 'message' => __('Please select a page or enable create page', 'fluent-support')
103 - ]);
132 + Helper::getSafeErrorMessage(new \Exception(__('Please select a page or enable create page', 'fluent-support')));
104 133 }
105 134
106 135 if ($createPage) {
107 136 // we have to create the page
@@ -235,15 +264,15 @@
235 264 }
236 265
237 266 public function setupInstallation(Request $request)
238 267 {
239 - $installFluentForm = $request->get('install_fluentform', 'no');
268 + $installFluentForm = $request->getSafe('install_fluentform', 'sanitize_text_field', 'no');
240 269
241 270 if ($installFluentForm == 'yes' && !defined('FLUENTFORM')) {
242 271 $this->installFluentForm();
243 272 }
244 273
245 - $optinEmail = $request->getSafe('optin_email', 'sanitize_text_field', 'no');
274 + $optinEmail = $request->getSafe('optin_email', 'sanitize_email', '');
246 275 if ($optinEmail && is_email($optinEmail)) {
247 276 $this->shareEmail($optinEmail);
248 277 }
249 278
@@ -261,9 +290,9 @@
261 290 public function saveReCaptchaSettings(Request $request)
262 291 {
263 292 $data = $request->get('reCaptcha');
264 293
265 - if ('clear-reCaptcha-settings' == $data) {
294 + if (is_string($data) && 'clear-reCaptcha-settings' === sanitize_text_field($data)) {
266 295 if (Meta::where('object_type', '_fs_recaptcha_settings')->delete()) {
267 296 return $this->sendSuccess([
268 297 'message' => __('Your reCAPTCHA settings deleted successfully.', 'fluent-support'),
269 298 ]);
@@ -273,14 +302,20 @@
273 302 'message' => __('Unable to delete reCAPTCHA settings, try again', 'fluent-support'),
274 303 ]);
275 304 }
276 305
306 + if (!is_array($data)) {
307 + return $this->sendError([
308 + 'message' => __('Invalid reCAPTCHA data.', 'fluent-support'),
309 + ]);
310 + }
311 +
277 312 $reCaptchaData = [
278 - 'reCaptcha_version' => sanitize_text_field($data['reCaptchaVersion']),
279 - 'siteKey' => sanitize_text_field($data['siteKey']),
280 - 'secretKey' => sanitize_text_field($data['secretKey']),
281 - 'formContainingReCaptcha' => array_map('sanitize_text_field', $data['formContainingReCaptcha']),
282 - 'is_enabled' => sanitize_text_field($data['reCaptchaEnabled'], 'no'),
313 + 'reCaptcha_version' => sanitize_text_field($data['reCaptchaVersion'] ?? ''),
314 + 'siteKey' => sanitize_text_field($data['siteKey'] ?? ''),
315 + 'secretKey' => sanitize_text_field($data['secretKey'] ?? ''),
316 + 'formContainingReCaptcha' => array_map('sanitize_text_field', (array) ($data['formContainingReCaptcha'] ?? [])),
317 + 'is_enabled' => sanitize_text_field($data['reCaptchaEnabled'] ?? 'no'),
283 318 ];
284 319
285 320 $previousValue = Meta::where('object_type', '_fs_recaptcha_settings')->first();
286 321
@@ -289,13 +324,21 @@
289 324 'message' => __('Your recaptcha details are already saved.', 'fluent-support'),
290 325 ]);
291 326 }
292 327
293 - $verifyReCaptcha = ReCaptchaHandler::validateRecaptcha($data['captchaResponse'], $data['secretKey'], $data['reCaptchaVersion']);
328 + $captchaResponse = sanitize_text_field($data['captchaResponse'] ?? '');
294 329
295 - if (!$verifyReCaptcha) {
330 + if ($captchaResponse) {
331 + $verifyReCaptcha = ReCaptchaHandler::validateRecaptcha($captchaResponse, $reCaptchaData['secretKey'], $reCaptchaData['reCaptcha_version']);
332 +
333 + if (!$verifyReCaptcha) {
334 + return $this->sendError([
335 + 'message' => __('Your reCAPTCHA settings are not valid.', 'fluent-support'),
336 + ]);
337 + }
338 + } elseif (!$previousValue) {
296 339 return $this->sendError([
297 - 'message' => __('Your reCAPTCHA settings are not valid.', 'fluent-support'),
340 + 'message' => __('Please verify reCAPTCHA before saving.', 'fluent-support'),
298 341 ]);
299 342 }
300 343
301 344 if ($previousValue) {
@@ -319,12 +362,11 @@
319 362 }
320 363
321 364 public function saveOpenAISettings(Request $request)
322 365 {
323 - $data = $request->get();
324 366 $data = [
325 - 'api_key' => sanitize_text_field($data['api_key']),
326 - 'model' => sanitize_text_field($data['model']),
367 + 'api_key' => $request->getSafe('api_key', 'sanitize_text_field', ''),
368 + 'model' => $request->getSafe('model', 'sanitize_text_field', ''),
327 369 ];
328 370
329 371 $response = Helper::authorizeChatGPTAPIKey($data);
330 372
@@ -352,9 +394,9 @@
352 394 ]);
353 395 } catch (\Exception $e) {
354 396 // translators: %s is the error message from the exception
355 397 $translatedMessage = __('An error occurred while saving the settings: %s', 'fluent-support');
356 - $errorMessage = sprintf($translatedMessage, $e->getMessage());
398 + $errorMessage = sprintf($translatedMessage, Helper::getSafeErrorMessage($e));
357 399
358 400 return $this->sendError([
359 401 'message' => $errorMessage,
360 402 ]);
@@ -381,17 +423,66 @@
381 423 }
382 424
383 425 public function getOpenAISettings()
384 426 {
427 + $modelOptions = $this->getOpenAIModelOptions();
428 + $supportedModels = array_column($modelOptions, 'value');
429 +
430 + $settings = [
431 + 'api_key' => '',
432 + 'model' => 'gpt-5.2',
433 + ];
434 +
385 435 $chatGPTSettingsData = Meta::where('object_type', '_fs_openai_settings')->first();
386 436 if ($chatGPTSettingsData) {
387 437 $settings = Helper::safeUnserialize($chatGPTSettingsData->value);
388 - return $this->sendSuccess($settings);
438 +
439 + if (!empty($settings['model']) && !in_array($settings['model'], $supportedModels, true)) {
440 + $previousModel = $settings['model'];
441 + $settings['model'] = 'gpt-5.2';
442 + Helper::saveOpenAIData('_fs_openai_settings', '_fs_openai_data', $settings);
443 + $settings['previous_model'] = $previousModel;
444 + $settings['model_migrated'] = true;
445 + }
389 446 }
390 447
391 - return [];
448 + $settings['model_options'] = $modelOptions;
449 +
450 + return $this->sendSuccess($settings);
392 451 }
393 452
453 + private function getOpenAIModelOptions()
454 + {
455 + $models = [
456 + ['value' => 'gpt-5.2', 'label' => 'GPT-5.2'],
457 + ['value' => 'gpt-5.2-chat-latest', 'label' => 'GPT-5.2 Chat'],
458 + ['value' => 'gpt-4.1', 'label' => 'GPT-4.1'],
459 + ['value' => 'gpt-4.1-mini', 'label' => 'GPT-4.1 Mini'],
460 + ['value' => 'gpt-4.1-nano', 'label' => 'GPT-4.1 Nano'],
461 + ['value' => 'gpt-4o', 'label' => 'GPT-4o'],
462 + ['value' => 'gpt-4o-mini', 'label' => 'GPT-4o Mini'],
463 + ['value' => 'gpt-4o-2024-08-06', 'label' => 'GPT-4o (2024-08-06)'],
464 + ['value' => 'gpt-4o-2024-05-13', 'label' => 'GPT-4o (2024-05-13)'],
465 + ['value' => 'gpt-4o-mini-2024-07-18', 'label' => 'GPT-4o Mini (2024-07-18)'],
466 + ['value' => 'gpt-4-turbo', 'label' => 'GPT-4 Turbo'],
467 + ['value' => 'gpt-4-turbo-2024-04-09', 'label' => 'GPT-4 Turbo (2024-04-09)'],
468 + ['value' => 'gpt-4-turbo-preview', 'label' => 'GPT-4 Turbo Preview'],
469 + ['value' => 'gpt-4', 'label' => 'GPT-4'],
470 + ['value' => 'gpt-4-0613', 'label' => 'GPT-4 (0613)'],
471 + ['value' => 'gpt-3.5-turbo', 'label' => 'GPT-3.5 Turbo'],
472 + ['value' => 'gpt-3.5-turbo-0125', 'label' => 'GPT-3.5 Turbo (0125)'],
473 + ['value' => 'o3', 'label' => 'o3'],
474 + ['value' => 'o3-mini', 'label' => 'o3-mini'],
475 + ['value' => 'o4-mini', 'label' => 'o4-mini'],
476 + ['value' => 'o1', 'label' => 'o1'],
477 + ['value' => 'gpt-4-0314', 'label' => 'GPT-4 (0314) - Deprecated soon'],
478 + ['value' => 'gpt-4-1106-preview', 'label' => 'GPT-4 (1106 Preview) - Deprecated soon'],
479 + ['value' => 'gpt-4-0125-preview', 'label' => 'GPT-4 (0125 Preview) - Deprecated soon'],
480 + ];
481 +
482 + return apply_filters('fluent_support/supported_openai_models', $models);
483 + }
484 +
394 485 public function getReCaptchaSettings()
395 486 {
396 487 $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
397 488 if ($reCaptchaSettingsData) {
@@ -492,18 +583,18 @@
492 583 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
493 584 ]);
494 585 }
495 586
496 - $plugin_id = 'fluent-crm';
587 + $plugin_id = 'fluentform';
497 588 $plugin = [
498 - 'name' => 'Fluent CRM',
499 - 'repo-slug' => 'fluent-crm',
500 - 'file' => 'fluent-crm.php',
589 + 'name' => 'Fluent Forms',
590 + 'repo-slug' => 'fluentform',
591 + 'file' => 'fluentform.php',
501 592 ];
502 593
503 594 $this->backgroundInstaller($plugin, $plugin_id);
504 595
505 - if (defined('FLUENTCRM')) {
596 + if (defined('FLUENTFORM')) {
506 597 return [
507 598 'is_installed' => true,
508 599 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
509 600 ];
@@ -638,8 +729,10 @@
638 729 public function getRemoteUploadSettings(Request $request)
639 730 {
640 731 $dropBoxConfigured = false;
641 732 $googleDriveConfigured = false;
733 + $cloudflareR2Configured = false;
734 + $amazonS3Configured = false;
642 735
643 736 if (defined('FLUENTSUPPORTPRO')) {
644 737 $dropBoxSettings = Helper::getIntegrationOption('dropbox_settings');
645 738 $dropBoxConfigured = $dropBoxSettings && !empty($dropBoxSettings['access_token']);
@@ -645,8 +738,14 @@
645 738 $dropBoxConfigured = $dropBoxSettings && !empty($dropBoxSettings['access_token']);
646 739
647 740 $googleDriveSettings = Helper::getIntegrationOption('google_drive_settings');
648 741 $googleDriveConfigured = $googleDriveSettings && !empty($googleDriveSettings['access_token']);
742 +
743 + $cloudflareR2Settings = Helper::getIntegrationOption('cloudflare_r2_settings');
744 + $cloudflareR2Configured = $cloudflareR2Settings && !empty($cloudflareR2Settings['secret_access_key']) && Arr::get($cloudflareR2Settings, 'status') == 'yes';
745 +
746 + $amazonS3Settings = Helper::getIntegrationOption('amazon_s3_settings');
747 + $amazonS3Configured = $amazonS3Settings && !empty($amazonS3Settings['secret_access_key']) && Arr::get($amazonS3Settings, 'status') == 'yes';
649 748 }
650 749
651 750 $drivers = apply_filters('fluent_support/storage_drivers_info', [
652 751 'local' => [
@@ -673,8 +772,28 @@
673 772 'require_pro' => !defined('FLUENTSUPPORTPRO'),
674 773 'upgrade_url' => 'https://fluentsupport.com/pricing',
675 774 'description' => __('Upload and store the files to your Google Drive Storage.', 'fluent-support'),
676 775 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/drive.svg',
776 + ],
777 + 'cloudflare_r2' => [
778 + 'meta_key' => 'cloudflare_r2_settings',
779 + 'title' => 'Cloudflare R2',
780 + 'has_config' => true,
781 + 'is_configured' => $cloudflareR2Configured,
782 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
783 + 'upgrade_url' => 'https://fluentsupport.com/pricing',
784 + 'description' => __('Upload and store the files to Cloudflare R2 Storage with zero egress fees.', 'fluent-support'),
785 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/cloudflare-r2.svg',
786 + ],
787 + 'amazon_s3' => [
788 + 'meta_key' => 'amazon_s3_settings',
789 + 'title' => 'Amazon S3',
790 + 'has_config' => true,
791 + 'is_configured' => $amazonS3Configured,
792 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
793 + 'upgrade_url' => 'https://fluentsupport.com/pricing',
794 + 'description' => __('Upload and store the files to Amazon S3 cloud storage.', 'fluent-support'),
795 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/amazon-s3.svg',
677 796 ]
678 797 ]);
679 798
680 799 return [
@@ -740,11 +859,10 @@
740 859
741 860 public function saveFluentBotSettings(Request $request)
742 861 {
743 862 $data = [
744 - 'generalApiKey' => $request->getSafe('generalApiKey', 'sanitize_text_field'),
745 863 'generalBotId' => $request->getSafe('generalBotId', 'sanitize_text_field'),
746 - 'isEnabled' => $request->getSafe('isEnabled', 'sanitize_text_field'),
864 + 'isEnabled' => $request->getSafe('isEnabled', 'rest_sanitize_boolean'),
747 865 'productMappings' => []
748 866 ];
749 867
750 868 $productMappings = (array) $request->get('productMappings', []);
@@ -756,9 +874,8 @@
756 874
757 875 $data['productMappings'][] = [
758 876 'productId' => intval($mapping['productId'] ?? 0),
759 877 'productTitle' => sanitize_text_field($mapping['productTitle'] ?? ''),
760 - 'apiKey' => sanitize_text_field($mapping['apiKey'] ?? ''),
761 878 'botId' => sanitize_text_field($mapping['botId'] ?? ''),
762 879 ];
763 880 }
764 881