PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.0
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 +638 -21 1.5.22.4.0 View file →
@@ -4,27 +4,49 @@
4 4
5 5
6 6 use FluentSupport\App\Models\MailBox;
7 7 use FluentSupport\App\Models\Meta;
8 +use FluentSupport\App\Models\Product;
8 9 use FluentSupport\App\Services\EmailNotification\Settings;
9 10 use FluentSupport\App\Services\Helper;
10 -use FluentSupport\Framework\Request\Request;
11 +use FluentSupport\App\Services\Integrations\AI\AIProviderFactory;
12 +use FluentSupport\Database\Migrations\AIActivityLogsMigrator;
13 +use FluentSupport\Framework\Http\Request\Request;
14 +use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
15 +use FluentSupport\Framework\Support\Arr;
11 16
17 +/**
18 + * SettingsController class is responsible for all settings
19 + * This class is responsible for all request related to settings under global settings tab
20 + * @package FluentSupport\App\Http\Controllers
21 + *
22 + * @version 1.0.0
23 + */
12 24 class SettingsController extends Controller
13 25 {
26 + /**
27 + * getSettings method will return the settings by settings key
28 + * @param Request $request
29 + * @return array|array[]
30 + */
14 31 public function getSettings(Request $request)
15 32 {
16 - $settingsKey = $request->get('settings_key');
33 + $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
17 34
18 35 return (new Settings)->get($settingsKey);
19 36 }
20 37
38 + /**
39 + * getIntegrationSettings method will return the settings for integration
40 + * @param Request $request
41 + * @return array
42 + */
21 43 public function getIntegrationSettings(Request $request)
22 44 {
23 45 $settings = Meta::where('object_type', 'integration_settings')->get();
24 46 $integrationSettings = [];
25 47 foreach ($settings as $index => $setting) {
26 - $data = maybe_unserialize($setting->value);
48 + $data = Helper::safeUnserialize($setting->value);
27 49 if (!empty($data['status']) && $data && $data['status'] == 'yes') {
28 50 $integrationSettings[] = $setting->key;
29 51 }
30 52 }
@@ -30,13 +52,36 @@
30 52 }
31 53 return $integrationSettings;
32 54 }
33 55
56 + /**
57 + * saveSettings method will save the requested settings data by setting key
58 + * @param Request $request
59 + * @return array
60 + */
34 61 public function saveSettings(Request $request)
35 62 {
36 - $settingsKey = $request->get('settings_key');
37 - $settings = wp_unslash($request->get('settings'));
63 + $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
64 + $settings = wp_unslash($request->get('settings', null));
38 65
66 + // wp-editor fields: sanitize with wp_kses_post (same approach as Fluent Cart)
67 + $htmlFields = ['login_message'];
68 + $htmlValues = [];
69 + if (is_array($settings)) {
70 + foreach ($htmlFields as $field) {
71 + if (isset($settings[$field])) {
72 + $htmlValues[$field] = wp_kses_post($settings[$field]);
73 + }
74 + }
75 + }
76 +
77 + $settings = is_array($settings) ? map_deep($settings, 'sanitize_text_field') : [];
78 +
79 + // Restore HTML fields
80 + foreach ($htmlValues as $field => $value) {
81 + $settings[$field] = $value;
82 + }
83 +
39 84 (new Settings)->save($settingsKey, $settings);
40 85
41 86 return [
42 87 'message' => __('Settings has been updated', 'fluent-support')
@@ -42,8 +87,12 @@
42 87 'message' => __('Settings has been updated', 'fluent-support')
43 88 ];
44 89 }
45 90
91 + /**
92 + * getPages method will return the list of pages created in WP
93 + * @return array
94 + */
46 95 public function getPages()
47 96 {
48 97 return [
49 98 'pages' => Helper::getWPPages()
@@ -49,11 +98,24 @@
49 98 'pages' => Helper::getWPPages()
50 99 ];
51 100 }
52 101
102 + /**
103 + * setupPortal method will setup the support portal
104 + * @param Request $request
105 + * @return array
106 + * @throws \FluentSupport\Framework\Validator\ValidationException
107 + */
53 108 public function setupPortal(Request $request)
54 109 {
55 - $mailbox = $request->get('mailbox');
110 + $mailbox = $request->get('mailbox', null);
111 + $mailbox = is_array($mailbox) ? [
112 + 'name' => isset($mailbox['name']) ? sanitize_text_field($mailbox['name']) : '',
113 + 'email' => isset($mailbox['email']) ? sanitize_email($mailbox['email']) : '',
114 + 'box_type' => isset($mailbox['box_type']) ? sanitize_key($mailbox['box_type']) : '',
115 + 'is_default' => isset($mailbox['is_default']) ? sanitize_text_field($mailbox['is_default']) : 'yes',
116 + ] : [];
117 +
56 118 $this->validate($mailbox, [
57 119 'name' => 'required',
58 120 'email' => 'required|email',
59 121 'box_type' => 'required'
@@ -58,20 +120,22 @@
58 120 'email' => 'required|email',
59 121 'box_type' => 'required'
60 122 ]);
61 123
62 - $settings = $request->get('global_settings');
124 + $settings = $request->get('global_settings', null);
125 + $settings = is_array($settings) ? [
126 + 'create_portal_page' => isset($settings['create_portal_page']) ? sanitize_text_field($settings['create_portal_page']) : 'no',
127 + 'portal_page_id' => isset($settings['portal_page_id']) ? intval($settings['portal_page_id']) : 0,
128 + ] : [];
63 129
64 130 $createPage = $settings['create_portal_page'] == 'yes';
65 131
66 132 if (!$createPage && empty($settings['portal_page_id'])) {
67 - return $this->sendError([
68 - 'message' => __('Please select a page or enable create page', 'fluent-support')
69 - ]);
133 + Helper::getSafeErrorMessage(new \Exception(__('Please select a page or enable create page', 'fluent-support')));
70 134 }
71 135
72 136 if ($createPage) {
73 - // we have to created the page
137 + // we have to create the page
74 138 $page_id = wp_insert_post(
75 139 array(
76 140 'comment_status' => 'close',
77 141 'ping_status' => 'close',
@@ -77,9 +141,9 @@
77 141 'ping_status' => 'close',
78 142 'post_author' => get_current_user_id(),
79 143 'post_title' => __('Support Portal', 'fluent-support'),
80 144 'post_status' => 'publish',
81 - 'post_content' => '[fluent_support_portal]',
145 + 'post_content' => '<!-- wp:shortcode -->[fluent_support_portal]<!-- /wp:shortcode -->',
82 146 'post_type' => 'page'
83 147 )
84 148 );
85 149 } else {
@@ -101,9 +165,9 @@
101 165
102 166 $settingsClass->save('global_business_settings', $globalSettings);
103 167
104 168
105 - if(defined('WC_PLUGIN_FILE')) {
169 + if (defined('WC_PLUGIN_FILE')) {
106 170 // URL Flash
107 171 flush_rewrite_rules(false);
108 172 }
109 173
@@ -109,13 +173,19 @@
109 173
110 174 return [
111 175 'mailbox' => $newMailBox,
112 176 'global_settings' => $globalSettings,
113 - 'mailboxes' => MailBox::select(['id', 'name', 'settings'])->get()
177 + 'mailboxes' => MailBox::select(['id', 'name', 'settings'])->get(),
178 + 'has_fluentform' => defined('FLUENTFORM')
114 179 ];
115 180
116 181 }
117 182
183 + /**
184 + * getFluentCRMSettings method will return the settings for Fluent CRM
185 + * @param Request $request
186 + * @return array
187 + */
118 188 public function getFluentCRMSettings(Request $request)
119 189 {
120 190 if (defined('FLUENTCRM')) {
121 191 $settingDefault = [
@@ -157,9 +227,9 @@
157 227 'inline_help' => __('Select the default status for new contacts. If you select pending and it\'s a new contact then a double optin email will be sent', 'fluent-support')
158 228 ],
159 229 'assigned_list' => [
160 230 'type' => 'input-options',
161 - 'label' => __('Add to FluentCRM list (optional)', 'fluent-crm'),
231 + 'label' => __('Add to FluentCRM list (optional)', 'fluent-support'),
162 232 'options' => \FluentCrm\App\Models\Lists::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
163 233 'dependency' => [
164 234 'depends_on' => 'enabled',
165 235 'operator' => '=',
@@ -168,9 +238,9 @@
168 238 ],
169 239 'assigned_tags' => [
170 240 'type' => 'input-options',
171 241 'multiple' => true,
172 - 'label' => __('Add to Tags', 'fluent-crm'),
242 + 'label' => __('Add to Tags', 'fluent-support'),
173 243 'options' => \FluentCrm\App\Models\Tag::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
174 244 'dependency' => [
175 245 'depends_on' => 'enabled',
176 246 'operator' => '=',
@@ -182,19 +252,259 @@
182 252 return [
183 253 'is_installed' => true,
184 254 'settings' => $settings,
185 255 'settings_fields' => $settingsFields,
186 - 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL.'assets/images/fluentcrm-logo.svg'
256 + 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
187 257 ];
188 258 }
189 259
190 260 return [
191 - 'is_installed' => false,
192 - 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL.'assets/images/fluentcrm-logo.svg'
261 + 'is_installed' => false,
262 + 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
193 263 ];
194 264
195 265 }
196 266
267 + public function setupInstallation(Request $request)
268 + {
269 + $installFluentForm = $request->getSafe('install_fluentform', 'sanitize_text_field', 'no');
270 +
271 + if ($installFluentForm == 'yes' && !defined('FLUENTFORM')) {
272 + $this->installFluentForm();
273 + }
274 +
275 + $optinEmail = $request->getSafe('optin_email', 'sanitize_email', '');
276 + if ($optinEmail && is_email($optinEmail)) {
277 + $this->shareEmail($optinEmail);
278 + }
279 +
280 + $shareEssential = $request->getSafe('share_essentials', 'sanitize_text_field', 'no');
281 + if ($shareEssential == 'yes') {
282 + Helper::updateOption('_share_essential', $shareEssential);
283 + }
284 +
285 + return $this->sendSuccess([
286 + 'message' => __('Installation has been completed', 'fluent-support')
287 + ]);
288 +
289 + }
290 +
291 + public function saveReCaptchaSettings(Request $request)
292 + {
293 + $data = $request->get('reCaptcha');
294 +
295 + if (is_string($data) && 'clear-reCaptcha-settings' === sanitize_text_field($data)) {
296 + if (Meta::where('object_type', '_fs_recaptcha_settings')->delete()) {
297 + return $this->sendSuccess([
298 + 'message' => __('Your reCAPTCHA settings deleted successfully.', 'fluent-support'),
299 + ]);
300 + }
301 +
302 + return $this->sendError([
303 + 'message' => __('Unable to delete reCAPTCHA settings, try again', 'fluent-support'),
304 + ]);
305 + }
306 +
307 + if (!is_array($data)) {
308 + return $this->sendError([
309 + 'message' => __('Invalid reCAPTCHA data.', 'fluent-support'),
310 + ]);
311 + }
312 +
313 + $reCaptchaData = [
314 + 'reCaptcha_version' => sanitize_text_field($data['reCaptchaVersion'] ?? ''),
315 + 'siteKey' => sanitize_text_field($data['siteKey'] ?? ''),
316 + 'secretKey' => sanitize_text_field($data['secretKey'] ?? ''),
317 + 'formContainingReCaptcha' => array_map('sanitize_text_field', (array) ($data['formContainingReCaptcha'] ?? [])),
318 + 'is_enabled' => sanitize_text_field($data['reCaptchaEnabled'] ?? 'no'),
319 + ];
320 +
321 + $previousValue = Meta::where('object_type', '_fs_recaptcha_settings')->first();
322 +
323 + if ($previousValue === $reCaptchaData) {
324 + return $this->sendError([
325 + 'message' => __('Your recaptcha details are already saved.', 'fluent-support'),
326 + ]);
327 + }
328 +
329 + $captchaResponse = sanitize_text_field($data['captchaResponse'] ?? '');
330 +
331 + if ($captchaResponse) {
332 + $verifyReCaptcha = ReCaptchaHandler::validateRecaptcha($captchaResponse, $reCaptchaData['secretKey'], $reCaptchaData['reCaptcha_version']);
333 +
334 + if (!$verifyReCaptcha) {
335 + return $this->sendError([
336 + 'message' => __('Your reCAPTCHA settings are not valid.', 'fluent-support'),
337 + ]);
338 + }
339 + } elseif (!$previousValue) {
340 + return $this->sendError([
341 + 'message' => __('Please verify reCAPTCHA before saving.', 'fluent-support'),
342 + ]);
343 + }
344 +
345 + if ($previousValue) {
346 + Meta::where('object_type', '_fs_recaptcha_settings')->update([
347 + 'value' => maybe_serialize($reCaptchaData)
348 + ]);
349 + return $this->sendSuccess([
350 + 'message' => __('Your reCAPTCHA settings updated successfully.', 'fluent-support'),
351 + ]);
352 + } else {
353 + Meta::insert([
354 + 'object_type' => '_fs_recaptcha_settings',
355 + 'key' => '_fs_recaptcha_data',
356 + 'value' => maybe_serialize($reCaptchaData)
357 + ]);
358 + }
359 +
360 + return $this->sendSuccess([
361 + 'message' => __('Your reCAPTCHA settings added successfully.', 'fluent-support'),
362 + ]);
363 + }
364 +
365 + public function saveAIProviderSettings(Request $request)
366 + {
367 + $allowedProviders = AIProviderFactory::getAllowedProviders();
368 + $provider = $request->getSafe('provider', 'sanitize_text_field', 'openai');
369 +
370 + if (!in_array($provider, $allowedProviders, true)) {
371 + return $this->sendError([
372 + 'message' => __('Invalid AI provider selected.', 'fluent-support'),
373 + ]);
374 + }
375 +
376 + $enabled = $request->getSafe('enabled', 'sanitize_text_field', 'yes');
377 + $apiKey = $request->getSafe('api_key', 'sanitize_text_field', '');
378 + $model = $request->getSafe('model', 'sanitize_text_field', '');
379 +
380 + if (empty($apiKey) || strpos($apiKey, '****') === 0) {
381 + $existing = Helper::getAIProviderSettings();
382 + if (($existing['provider'] ?? '') === $provider) {
383 + $apiKey = $existing['api_key'] ?? '';
384 + } else {
385 + $apiKey = '';
386 + }
387 + }
388 +
389 + $data = [
390 + 'enabled' => $enabled,
391 + 'provider' => $provider,
392 + 'api_key' => $apiKey,
393 + 'model' => $model,
394 + ];
395 +
396 + try {
397 + Helper::saveAIProviderSettings($data);
398 + AIProviderFactory::clearCache();
399 + AIActivityLogsMigrator::migrate();
400 +
401 + return $this->sendSuccess([
402 + 'message' => __('AI settings have been successfully saved.', 'fluent-support'),
403 + ]);
404 + } catch (\Exception $e) {
405 + return $this->sendError([
406 + 'message' => sprintf(
407 + __('An error occurred while saving the settings: %s', 'fluent-support'),
408 + Helper::getSafeErrorMessage($e)
409 + ),
410 + ]);
411 + }
412 + }
413 +
414 + public function disconnectAIProvider()
415 + {
416 + Meta::where('object_type', '_fs_ai_provider_settings')->delete();
417 + Meta::where([
418 + 'object_type' => '_fs_openai_settings',
419 + 'key' => '_fs_openai_data',
420 + ])->delete();
421 +
422 + AIProviderFactory::clearCache();
423 +
424 + return $this->sendSuccess([
425 + 'message' => __('AI provider settings have been successfully disconnected.', 'fluent-support'),
426 + ]);
427 + }
428 +
429 + public function getAIProviderSettings()
430 + {
431 + $settings = Helper::getAIProviderSettings();
432 +
433 + $provider = $settings['provider'] ?? 'openai';
434 + $apiKey = $settings['api_key'] ?? '';
435 + $model = $settings['model'] ?? '';
436 + $defaultEnabled = !empty($apiKey) ? 'yes' : 'no';
437 + $enabled = $settings['enabled'] ?? $defaultEnabled;
438 +
439 + $availableModels = [];
440 + foreach (['openai', 'gemini', 'anthropic'] as $p) {
441 + $instance = AIProviderFactory::make($p, '', '');
442 + if (!is_wp_error($instance)) {
443 + $availableModels[$p] = $instance->getAvailableModels();
444 + }
445 + }
446 +
447 + if (empty($model) && !empty($availableModels[$provider])) {
448 + $model = $availableModels[$provider][0]['value'];
449 + }
450 +
451 + return $this->sendSuccess([
452 + 'enabled' => $enabled,
453 + 'provider' => $provider,
454 + 'api_key' => !empty($apiKey) ? '****' . substr($apiKey, -4) : '',
455 + 'model' => $model,
456 + 'available_models' => $availableModels,
457 + ]);
458 + }
459 +
460 + public function getReCaptchaSettings()
461 + {
462 + $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
463 + if ($reCaptchaSettingsData) {
464 + $settings = Helper::safeUnserialize($reCaptchaSettingsData->value);
465 + return $this->sendSuccess($settings);
466 + }
467 +
468 + return [];
469 + }
470 +
471 + private function shareEmail($optinEmail)
472 + {
473 + $user = get_user_by('ID', get_current_user_id());
474 + $data = [
475 + 'answers' => [
476 + 'website' => site_url(),
477 + 'email' => $optinEmail,
478 + 'first_name' => $user->first_name,
479 + 'last_name' => $user->last_name,
480 + 'name' => $user->display_name,
481 + 'has_fluentform' => defined('FLUENTFORM') ? 'yes' : 'no'
482 + ],
483 + 'questions' => [
484 + 'website' => 'website',
485 + 'first_name' => 'first_name',
486 + 'last_name' => 'last_name',
487 + 'email' => 'email',
488 + 'name' => 'name',
489 + 'has_fluentform' => 'has_fluentform'
490 + ],
491 + 'user' => [
492 + 'email' => $optinEmail
493 + ],
494 + 'fb_capture' => 1,
495 + 'form_id' => 77
496 + ];
497 +
498 + $url = add_query_arg($data, 'https://wpmanageninja.com/');
499 +
500 + wp_remote_post($url);
501 + }
502 +
503 + /**
504 + * installFluentCRM method will install Fluent CRM plugin
505 + * @return array
506 + */
197 507 public function installFluentCRM()
198 508 {
199 509
200 510 if (defined('FLUENTCRM')) {
@@ -205,13 +515,13 @@
205 515 }
206 516
207 517 if (!current_user_can('install_plugins')) {
208 518 return $this->sendError([
209 - 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-crm')
519 + 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
210 520 ]);
211 521 }
212 522
213 - $plugin_id = 'fluent-connect';
523 + $plugin_id = 'fluent-crm';
214 524 $plugin = [
215 525 'name' => 'Fluent CRM',
216 526 'repo-slug' => 'fluent-crm',
217 527 'file' => 'fluent-crm.php',
@@ -230,8 +540,46 @@
230 540 ]);
231 541 }
232 542 }
233 543
544 + public function installFluentForm()
545 + {
546 +
547 + if (defined('FLUENTFORM')) {
548 + return [
549 + 'is_installed' => true,
550 + 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
551 + ];
552 + }
553 +
554 + if (!current_user_can('install_plugins')) {
555 + return $this->sendError([
556 + 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
557 + ]);
558 + }
559 +
560 + $plugin_id = 'fluentform';
561 + $plugin = [
562 + 'name' => 'Fluent Forms',
563 + 'repo-slug' => 'fluentform',
564 + 'file' => 'fluentform.php',
565 + ];
566 +
567 + $this->backgroundInstaller($plugin, $plugin_id);
568 +
569 + if (defined('FLUENTFORM')) {
570 + return [
571 + 'is_installed' => true,
572 + 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
573 + ];
574 + } else {
575 + return [
576 + 'is_installed' => false,
577 + 'message' => __('Fluent Forms could not be installed', 'fluent-support')
578 + ];
579 + }
580 + }
581 +
234 582 private function backgroundInstaller($plugin_to_install, $plugin_id)
235 583 {
236 584 if (!empty($plugin_to_install['repo-slug'])) {
237 585 require_once ABSPATH . 'wp-admin/includes/file.php';
@@ -348,7 +696,276 @@
348 696 $path = explode('/', $key);
349 697 $filename = end($path);
350 698 $plugins[$filename] = $key;
351 699 return $plugins;
700 + }
701 +
702 + public function getRemoteUploadSettings(Request $request)
703 + {
704 + $dropBoxConfigured = false;
705 + $googleDriveConfigured = false;
706 + $cloudflareR2Configured = false;
707 + $amazonS3Configured = false;
708 +
709 + if (defined('FLUENTSUPPORTPRO')) {
710 + $dropBoxSettings = Helper::getIntegrationOption('dropbox_settings');
711 + $dropBoxConfigured = $dropBoxSettings && !empty($dropBoxSettings['access_token']);
712 +
713 + $googleDriveSettings = Helper::getIntegrationOption('google_drive_settings');
714 + $googleDriveConfigured = $googleDriveSettings && !empty($googleDriveSettings['access_token']);
715 +
716 + $cloudflareR2Settings = Helper::getIntegrationOption('cloudflare_r2_settings');
717 + $cloudflareR2Configured = $cloudflareR2Settings && !empty($cloudflareR2Settings['secret_access_key']) && Arr::get($cloudflareR2Settings, 'status') == 'yes';
718 +
719 + $amazonS3Settings = Helper::getIntegrationOption('amazon_s3_settings');
720 + $amazonS3Configured = $amazonS3Settings && !empty($amazonS3Settings['secret_access_key']) && Arr::get($amazonS3Settings, 'status') == 'yes';
721 + }
722 +
723 + $drivers = apply_filters('fluent_support/storage_drivers_info', [
724 + 'local' => [
725 + 'title' => 'Default WordPress Storage',
726 + 'is_disabled' => false,
727 + 'is_configured' => true,
728 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/folder.svg',
729 + 'description' => __('Upload and store the files to your WordPress File System Storage.', 'fluent-support')
730 + ],
731 + 'dropbox' => [
732 + 'meta_key' => 'dropbox_settings',
733 + 'title' => 'Dropbox',
734 + 'has_config' => true,
735 + 'is_configured' => $dropBoxConfigured,
736 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
737 + 'upgrade_url' => Helper::getUpgradeUrl('feature_lock_dropbox'),
738 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/dbox.svg',
739 + 'description' => __('Upload and store the files to your Dropbox Storage.', 'fluent-support')
740 + ],
741 + 'google_drive' => [
742 + 'meta_key' => 'google_drive_settings',
743 + 'title' => 'Google Drive',
744 + 'has_config' => true,
745 + 'is_configured' => $googleDriveConfigured,
746 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
747 + 'upgrade_url' => Helper::getUpgradeUrl('feature_lock_google_drive'),
748 + 'description' => __('Upload and store the files to your Google Drive Storage.', 'fluent-support'),
749 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/drive.svg',
750 + ],
751 + 'cloudflare_r2' => [
752 + 'meta_key' => 'cloudflare_r2_settings',
753 + 'title' => 'Cloudflare R2',
754 + 'has_config' => true,
755 + 'is_configured' => $cloudflareR2Configured,
756 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
757 + 'upgrade_url' => Helper::getUpgradeUrl('feature_lock_cloudflare_r2'),
758 + 'description' => __('Upload and store the files to Cloudflare R2 Storage with zero egress fees.', 'fluent-support'),
759 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/cloudflare-r2.svg',
760 + ],
761 + 'amazon_s3' => [
762 + 'meta_key' => 'amazon_s3_settings',
763 + 'title' => 'Amazon S3',
764 + 'has_config' => true,
765 + 'is_configured' => $amazonS3Configured,
766 + 'require_pro' => !defined('FLUENTSUPPORTPRO'),
767 + 'upgrade_url' => Helper::getUpgradeUrl('feature_lock_amazon_s3'),
768 + 'description' => __('Upload and store the files to Amazon S3 cloud storage.', 'fluent-support'),
769 + 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/amazon-s3.svg',
770 + ]
771 + ]);
772 +
773 + return [
774 + 'drivers' => $drivers,
775 + 'enabled_driver' => Helper::getUploadDriverKey()
776 + ];
777 + }
778 +
779 + public function updateRemoteUploadDriver(Request $request)
780 + {
781 + $driver = $request->getSafe('driver', 'sanitize_text_field');
782 + Helper::updateOption('file_upload_driver', $driver);
783 +
784 + return [
785 + 'message' => 'Upload driver has been updated successfully',
786 + 'driver' => $driver
787 + ];
788 + }
789 +
790 + /**
791 + * getIntegrationLogs method will return the integration logs
792 + * @return array
793 + */
794 + public function integrationStatuses()
795 + {
796 + return [
797 + 'connections' => Helper::getIntegrationStatuses()
798 + ];
799 + }
800 +
801 + public function getSettingsMenu()
802 + {
803 + return Helper::getGlobalSettingsMenu();
804 + }
805 +
806 + public function getFluentBotSettings()
807 + {
808 + $meta = Meta::where([
809 + 'object_type' => 'fluent_bot_settings',
810 + 'object_id' => 1,
811 + 'key' => '_fs_fluent_bot_config'
812 + ])->orderByDesc('id')->first();
813 +
814 + $settings = $meta ? Helper::safeUnserialize($meta->value) : [];
815 +
816 + if (!is_array($settings)) {
817 + $settings = [];
818 + }
819 +
820 + // Write-only secret: never return the raw team API key to the browser
821 + // (it authenticates the FluentBot API and would leak to logs/extensions/
822 + // XSS). Expose only whether a key is stored so the form can show a
823 + // "saved" hint; the input stays blank and only overwrites on a new key.
824 + $hasApiKey = !empty($settings['generalApiKey']);
825 + unset($settings['generalApiKey']);
826 +
827 + // Per-mapping apiKey is unused (the key is team-wide).
828 + if (!empty($settings['productMappings']) && is_array($settings['productMappings'])) {
829 + $settings['productMappings'] = array_map(function ($mapping) {
830 + if (!is_array($mapping)) {
831 + return $mapping;
832 + }
833 + unset($mapping['apiKey']);
834 + return $mapping;
835 + }, $settings['productMappings']);
836 + }
837 +
838 + $productItems = Product::orderedByTitle()->get()->map(function ($product) {
839 + return [
840 + 'id' => $product->id,
841 + 'title' => $product->title
842 + ];
843 + })->values()->all();
844 +
845 + // Default generalBotEnabled to true for backward compatibility with configs saved
846 + // before this flag existed — existing installs expect general bot to work on GET.
847 + $defaults = [
848 + 'generalBotId' => '',
849 + 'generalApiKey' => '',
850 + 'generalBotEnabled' => true,
851 + 'isEnabled' => false,
852 + 'productMappings' => [],
853 + 'products' => $productItems,
854 + ];
855 +
856 + return array_merge($defaults, $settings, [
857 + 'products' => $productItems,
858 + 'hasApiKey' => $hasApiKey,
859 + ]);
860 + }
861 +
862 + public function saveFluentBotSettings(Request $request)
863 + {
864 + $where = [
865 + 'object_type' => 'fluent_bot_settings',
866 + 'object_id' => 1,
867 + 'key' => '_fs_fluent_bot_config'
868 + ];
869 +
870 + $existing = Meta::where($where)->orderByDesc('id')->first();
871 + $existingConfig = $existing ? Helper::safeUnserialize($existing->value) : [];
872 + if (!is_array($existingConfig)) {
873 + $existingConfig = [];
874 + }
875 +
876 + // Write-only key: a blank submission means "keep the stored key" (the form
877 + // never round-trips the secret), so only overwrite when a new key is sent.
878 + $submittedApiKey = trim($request->getSafe('generalApiKey', 'sanitize_text_field'));
879 + $apiKey = $submittedApiKey !== '' ? $submittedApiKey : ($existingConfig['generalApiKey'] ?? '');
880 +
881 + $data = [
882 + 'generalBotId' => $request->getSafe('generalBotId', 'sanitize_text_field'),
883 + 'generalApiKey' => $apiKey,
884 + 'generalBotEnabled' => filter_var($request->get('generalBotEnabled', true), FILTER_VALIDATE_BOOLEAN),
885 + 'isEnabled' => $request->getSafe('isEnabled', 'rest_sanitize_boolean'),
886 + 'productMappings' => []
887 + ];
888 +
889 + $productMappings = (array) $request->get('productMappings', []);
890 + $seenProductIds = [];
891 +
892 + foreach ($productMappings as $mapping) {
893 + if (!is_array($mapping)) {
894 + continue;
895 + }
896 +
897 + $productId = intval($mapping['productId'] ?? 0);
898 + $botId = trim(sanitize_text_field($mapping['botId'] ?? ''));
899 +
900 + // Drop invalid rows: empty botId would override the general bot with nothing
901 + // at resolution time (resolveApiCredentials), producing runtime failures.
902 + if ($productId < 1 || $botId === '') {
903 + continue;
904 + }
905 +
906 + // Dedupe by productId — first valid mapping wins.
907 + if (isset($seenProductIds[$productId])) {
908 + continue;
909 + }
910 + $seenProductIds[$productId] = true;
911 +
912 + $data['productMappings'][] = [
913 + 'productId' => $productId,
914 + 'productTitle' => sanitize_text_field($mapping['productTitle'] ?? ''),
915 + 'botId' => $botId,
916 + ];
917 + }
918 +
919 + $serialized = maybe_serialize($data);
920 +
921 + if ($existing) {
922 + // Update the latest row; do not prune siblings — concurrent first-writes could
923 + // race and delete each other's inserts, leaving zero rows (data loss).
924 + // Reads use orderByDesc('id')->first() so duplicates are harmless at read time.
925 + $existing->update(['value' => $serialized]);
926 + } else {
927 + Meta::create(array_merge($where, ['value' => $serialized]));
928 + AIActivityLogsMigrator::migrate();
929 + }
930 +
931 + // Write-only: never echo the raw key back to the browser.
932 + $responseData = $data;
933 + unset($responseData['generalApiKey']);
934 + $responseData['hasApiKey'] = $apiKey !== '';
935 +
936 + return [
937 + 'success' => true,
938 + 'message' => 'Settings saved successfully',
939 + 'data' => $responseData
940 + ];
941 + }
942 +
943 + public function getFluentBotPresets()
944 + {
945 + $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
946 + $custom = $service->getCustomPresets();
947 +
948 + if (!empty($custom)) {
949 + return ['presets' => $custom];
950 + }
951 +
952 + // Return defaults without persisting — saving happens only when the user explicitly posts.
953 + $helper = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotHelper();
954 + return ['presets' => $helper->getPresetPrompts('createResponse')];
955 + }
956 +
957 + public function saveFluentBotPresets(Request $request)
958 + {
959 + $presets = (array) $request->get('presets', []);
960 +
961 + $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
962 + $saved = $service->saveCustomPresets($presets);
963 +
964 + return [
965 + 'success' => true,
966 + 'message' => __('Prompt options saved successfully', 'fluent-support'),
967 + 'presets' => $saved
968 + ];
352 969 }
353 970
354 971 }