PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.3.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.3.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 1.5.6 All 67 releases
fluent-support / app / Http / Controllers / SettingsController.php

SettingsController.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.3.0, at app/Http/Controllers/SettingsController.php

973 lines 36.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Http\Controllers;
4
5
6 use FluentSupport\App\Models\MailBox;
7 use FluentSupport\App\Models\Meta;
8 use FluentSupport\App\Models\Product;
9 use FluentSupport\App\Services\EmailNotification\Settings;
10 use FluentSupport\App\Services\Helper;
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;
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 */
24 class SettingsController extends Controller
25 {
26 /**
27 * getSettings method will return the settings by settings key
28 * @param Request $request
29 * @return array|array[]
30 */
31 public function getSettings(Request $request)
32 {
33 $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
34
35 return (new Settings)->get($settingsKey);
36 }
37
38 /**
39 * getIntegrationSettings method will return the settings for integration
40 * @param Request $request
41 * @return array
42 */
43 public function getIntegrationSettings(Request $request)
44 {
45 $settings = Meta::where('object_type', 'integration_settings')->get();
46 $integrationSettings = [];
47 foreach ($settings as $index => $setting) {
48 $data = Helper::safeUnserialize($setting->value);
49 if (!empty($data['status']) && $data && $data['status'] == 'yes') {
50 $integrationSettings[] = $setting->key;
51 }
52 }
53 return $integrationSettings;
54 }
55
56 /**
57 * saveSettings method will save the requested settings data by setting key
58 * @param Request $request
59 * @return array
60 */
61 public function saveSettings(Request $request)
62 {
63 $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
64 $settings = wp_unslash($request->get('settings', null));
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
84 (new Settings)->save($settingsKey, $settings);
85
86 return [
87 'message' => __('Settings has been updated', 'fluent-support')
88 ];
89 }
90
91 /**
92 * getPages method will return the list of pages created in WP
93 * @return array
94 */
95 public function getPages()
96 {
97 return [
98 'pages' => Helper::getWPPages()
99 ];
100 }
101
102 /**
103 * setupPortal method will setup the support portal
104 * @param Request $request
105 * @return array
106 * @throws \FluentSupport\Framework\Validator\ValidationException
107 */
108 public function setupPortal(Request $request)
109 {
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
118 $this->validate($mailbox, [
119 'name' => 'required',
120 'email' => 'required|email',
121 'box_type' => 'required'
122 ]);
123
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 ] : [];
129
130 $createPage = $settings['create_portal_page'] == 'yes';
131
132 if (!$createPage && empty($settings['portal_page_id'])) {
133 Helper::getSafeErrorMessage(new \Exception(__('Please select a page or enable create page', 'fluent-support')));
134 }
135
136 if ($createPage) {
137 // we have to create the page
138 $page_id = wp_insert_post(
139 array(
140 'comment_status' => 'close',
141 'ping_status' => 'close',
142 'post_author' => get_current_user_id(),
143 'post_title' => __('Support Portal', 'fluent-support'),
144 'post_status' => 'publish',
145 'post_content' => '<!-- wp:shortcode -->[fluent_support_portal]<!-- /wp:shortcode -->',
146 'post_type' => 'page'
147 )
148 );
149 } else {
150 $page_id = intval($settings['portal_page_id']);
151 }
152
153 $newMailBox = MailBox::first();
154 if (!$newMailBox) {
155 $mailbox['is_default'] = 'yes';
156 $mailbox['created_by'] = get_current_user_id();
157 $mailbox['settings']['admin_email_address'] = $mailbox['email'];
158 $newMailBox = MailBox::create($mailbox);
159 }
160
161 $settingsClass = new Settings();
162 $globalSettings = $settingsClass->globalBusinessSettings();
163
164 $globalSettings['portal_page_id'] = $page_id;
165
166 $settingsClass->save('global_business_settings', $globalSettings);
167
168
169 if (defined('WC_PLUGIN_FILE')) {
170 // URL Flash
171 flush_rewrite_rules(false);
172 }
173
174 return [
175 'mailbox' => $newMailBox,
176 'global_settings' => $globalSettings,
177 'mailboxes' => MailBox::select(['id', 'name', 'settings'])->get(),
178 'has_fluentform' => defined('FLUENTFORM')
179 ];
180
181 }
182
183 /**
184 * getFluentCRMSettings method will return the settings for Fluent CRM
185 * @param Request $request
186 * @return array
187 */
188 public function getFluentCRMSettings(Request $request)
189 {
190 if (defined('FLUENTCRM')) {
191 $settingDefault = [
192 'enabled' => 'no',
193 'default_status' => 'subscribed',
194 'assigned_list' => '',
195 'assigned_tags' => []
196 ];
197
198 $settings = Helper::getOption('_fluentcrm_intergration_settings');
199
200 $settings = wp_parse_args($settings, $settingDefault);
201
202 $settingsFields = [
203 'enabled' => [
204 'type' => 'inline-checkbox',
205 'true_label' => 'yes',
206 'false_label' => 'no',
207 'checkbox_label' => __('Enable FluentCRM Integration', 'fluent-support')
208 ],
209 'default_status' => [
210 'type' => 'input-radio',
211 'label' => __('Default status for new contacts', 'fluent-support'),
212 'options' => [
213 [
214 'id' => 'subscribed',
215 'label' => __('Subscribed', 'fluent-support')
216 ],
217 [
218 'id' => 'pending',
219 'label' => __('Pending', 'fluent-support')
220 ]
221 ],
222 'dependency' => [
223 'depends_on' => 'enabled',
224 'operator' => '=',
225 'value' => 'yes'
226 ],
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')
228 ],
229 'assigned_list' => [
230 'type' => 'input-options',
231 'label' => __('Add to FluentCRM list (optional)', 'fluent-support'),
232 'options' => \FluentCrm\App\Models\Lists::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
233 'dependency' => [
234 'depends_on' => 'enabled',
235 'operator' => '=',
236 'value' => 'yes'
237 ],
238 ],
239 'assigned_tags' => [
240 'type' => 'input-options',
241 'multiple' => true,
242 'label' => __('Add to Tags', 'fluent-support'),
243 'options' => \FluentCrm\App\Models\Tag::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
244 'dependency' => [
245 'depends_on' => 'enabled',
246 'operator' => '=',
247 'value' => 'yes'
248 ]
249 ]
250 ];
251
252 return [
253 'is_installed' => true,
254 'settings' => $settings,
255 'settings_fields' => $settingsFields,
256 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
257 ];
258 }
259
260 return [
261 'is_installed' => false,
262 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
263 ];
264
265 }
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 'sslverify' => false
502 ]);
503 }
504
505 /**
506 * installFluentCRM method will install Fluent CRM plugin
507 * @return array
508 */
509 public function installFluentCRM()
510 {
511
512 if (defined('FLUENTCRM')) {
513 return [
514 'is_installed' => true,
515 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
516 ];
517 }
518
519 if (!current_user_can('install_plugins')) {
520 return $this->sendError([
521 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
522 ]);
523 }
524
525 $plugin_id = 'fluent-crm';
526 $plugin = [
527 'name' => 'Fluent CRM',
528 'repo-slug' => 'fluent-crm',
529 'file' => 'fluent-crm.php',
530 ];
531
532 $this->backgroundInstaller($plugin, $plugin_id);
533
534 if (defined('FLUENTCRM')) {
535 return [
536 'is_installed' => true,
537 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
538 ];
539 } else {
540 return $this->sendError([
541 'message' => __('Sorry! FluentCRM could not be installed. Please install manually', 'fluent-support')
542 ]);
543 }
544 }
545
546 public function installFluentForm()
547 {
548
549 if (defined('FLUENTFORM')) {
550 return [
551 'is_installed' => true,
552 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
553 ];
554 }
555
556 if (!current_user_can('install_plugins')) {
557 return $this->sendError([
558 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
559 ]);
560 }
561
562 $plugin_id = 'fluentform';
563 $plugin = [
564 'name' => 'Fluent Forms',
565 'repo-slug' => 'fluentform',
566 'file' => 'fluentform.php',
567 ];
568
569 $this->backgroundInstaller($plugin, $plugin_id);
570
571 if (defined('FLUENTFORM')) {
572 return [
573 'is_installed' => true,
574 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
575 ];
576 } else {
577 return [
578 'is_installed' => false,
579 'message' => __('Fluent Forms could not be installed', 'fluent-support')
580 ];
581 }
582 }
583
584 private function backgroundInstaller($plugin_to_install, $plugin_id)
585 {
586 if (!empty($plugin_to_install['repo-slug'])) {
587 require_once ABSPATH . 'wp-admin/includes/file.php';
588 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
589 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
590 require_once ABSPATH . 'wp-admin/includes/plugin.php';
591
592 WP_Filesystem();
593
594 $skin = new \Automatic_Upgrader_Skin();
595 $upgrader = new \WP_Upgrader($skin);
596 $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
597 $plugin_slug = $plugin_to_install['repo-slug'];
598 $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
599 $installed = false;
600 $activate = false;
601
602 // See if the plugin is installed already.
603 if (isset($installed_plugins[$plugin_file])) {
604 $installed = true;
605 $activate = !is_plugin_active($installed_plugins[$plugin_file]);
606 }
607
608 // Install this thing!
609 if (!$installed) {
610 // Suppress feedback.
611 ob_start();
612
613 try {
614 $plugin_information = plugins_api(
615 'plugin_information',
616 array(
617 'slug' => $plugin_slug,
618 'fields' => array(
619 'short_description' => false,
620 'sections' => false,
621 'requires' => false,
622 'rating' => false,
623 'ratings' => false,
624 'downloaded' => false,
625 'last_updated' => false,
626 'added' => false,
627 'tags' => false,
628 'homepage' => false,
629 'donate_link' => false,
630 'author_profile' => false,
631 'author' => false,
632 ),
633 )
634 );
635
636 if (is_wp_error($plugin_information)) {
637 throw new \Exception($plugin_information->get_error_message());
638 }
639
640 $package = $plugin_information->download_link;
641 $download = $upgrader->download_package($package);
642
643 if (is_wp_error($download)) {
644 throw new \Exception($download->get_error_message());
645 }
646
647 $working_dir = $upgrader->unpack_package($download, true);
648
649 if (is_wp_error($working_dir)) {
650 throw new \Exception($working_dir->get_error_message());
651 }
652
653 $result = $upgrader->install_package(
654 array(
655 'source' => $working_dir,
656 'destination' => WP_PLUGIN_DIR,
657 'clear_destination' => false,
658 'abort_if_destination_exists' => false,
659 'clear_working' => true,
660 'hook_extra' => array(
661 'type' => 'plugin',
662 'action' => 'install',
663 ),
664 )
665 );
666
667 if (is_wp_error($result)) {
668 throw new \Exception($result->get_error_message());
669 }
670
671 $activate = true;
672
673 } catch (\Exception $e) {
674 }
675
676 // Discard feedback.
677 ob_end_clean();
678 }
679
680 wp_clean_plugins_cache();
681
682 // Activate this thing.
683 if ($activate) {
684 try {
685 $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
686
687 if (is_wp_error($result)) {
688 throw new \Exception($result->get_error_message());
689 }
690 } catch (\Exception $e) {
691 }
692 }
693 }
694 }
695
696 private function associate_plugin_file($plugins, $key)
697 {
698 $path = explode('/', $key);
699 $filename = end($path);
700 $plugins[$filename] = $key;
701 return $plugins;
702 }
703
704 public function getRemoteUploadSettings(Request $request)
705 {
706 $dropBoxConfigured = false;
707 $googleDriveConfigured = false;
708 $cloudflareR2Configured = false;
709 $amazonS3Configured = false;
710
711 if (defined('FLUENTSUPPORTPRO')) {
712 $dropBoxSettings = Helper::getIntegrationOption('dropbox_settings');
713 $dropBoxConfigured = $dropBoxSettings && !empty($dropBoxSettings['access_token']);
714
715 $googleDriveSettings = Helper::getIntegrationOption('google_drive_settings');
716 $googleDriveConfigured = $googleDriveSettings && !empty($googleDriveSettings['access_token']);
717
718 $cloudflareR2Settings = Helper::getIntegrationOption('cloudflare_r2_settings');
719 $cloudflareR2Configured = $cloudflareR2Settings && !empty($cloudflareR2Settings['secret_access_key']) && Arr::get($cloudflareR2Settings, 'status') == 'yes';
720
721 $amazonS3Settings = Helper::getIntegrationOption('amazon_s3_settings');
722 $amazonS3Configured = $amazonS3Settings && !empty($amazonS3Settings['secret_access_key']) && Arr::get($amazonS3Settings, 'status') == 'yes';
723 }
724
725 $drivers = apply_filters('fluent_support/storage_drivers_info', [
726 'local' => [
727 'title' => 'Default WordPress Storage',
728 'is_disabled' => false,
729 'is_configured' => true,
730 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/folder.svg',
731 'description' => __('Upload and store the files to your WordPress File System Storage.', 'fluent-support')
732 ],
733 'dropbox' => [
734 'meta_key' => 'dropbox_settings',
735 'title' => 'Dropbox',
736 'has_config' => true,
737 'is_configured' => $dropBoxConfigured,
738 'require_pro' => !defined('FLUENTSUPPORTPRO'),
739 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/dbox.svg',
740 'description' => __('Upload and store the files to your Dropbox Storage.', 'fluent-support')
741 ],
742 'google_drive' => [
743 'meta_key' => 'google_drive_settings',
744 'title' => 'Google Drive',
745 'has_config' => true,
746 'is_configured' => $googleDriveConfigured,
747 'require_pro' => !defined('FLUENTSUPPORTPRO'),
748 'upgrade_url' => 'https://fluentsupport.com/pricing',
749 'description' => __('Upload and store the files to your Google Drive Storage.', 'fluent-support'),
750 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/drive.svg',
751 ],
752 'cloudflare_r2' => [
753 'meta_key' => 'cloudflare_r2_settings',
754 'title' => 'Cloudflare R2',
755 'has_config' => true,
756 'is_configured' => $cloudflareR2Configured,
757 'require_pro' => !defined('FLUENTSUPPORTPRO'),
758 'upgrade_url' => 'https://fluentsupport.com/pricing',
759 'description' => __('Upload and store the files to Cloudflare R2 Storage with zero egress fees.', 'fluent-support'),
760 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/cloudflare-r2.svg',
761 ],
762 'amazon_s3' => [
763 'meta_key' => 'amazon_s3_settings',
764 'title' => 'Amazon S3',
765 'has_config' => true,
766 'is_configured' => $amazonS3Configured,
767 'require_pro' => !defined('FLUENTSUPPORTPRO'),
768 'upgrade_url' => 'https://fluentsupport.com/pricing',
769 'description' => __('Upload and store the files to Amazon S3 cloud storage.', 'fluent-support'),
770 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/amazon-s3.svg',
771 ]
772 ]);
773
774 return [
775 'drivers' => $drivers,
776 'enabled_driver' => Helper::getUploadDriverKey()
777 ];
778 }
779
780 public function updateRemoteUploadDriver(Request $request)
781 {
782 $driver = $request->getSafe('driver', 'sanitize_text_field');
783 Helper::updateOption('file_upload_driver', $driver);
784
785 return [
786 'message' => 'Upload driver has been updated successfully',
787 'driver' => $driver
788 ];
789 }
790
791 /**
792 * getIntegrationLogs method will return the integration logs
793 * @return array
794 */
795 public function integrationStatuses()
796 {
797 return [
798 'connections' => Helper::getIntegrationStatuses()
799 ];
800 }
801
802 public function getSettingsMenu()
803 {
804 return Helper::getGlobalSettingsMenu();
805 }
806
807 public function getFluentBotSettings()
808 {
809 $meta = Meta::where([
810 'object_type' => 'fluent_bot_settings',
811 'object_id' => 1,
812 'key' => '_fs_fluent_bot_config'
813 ])->orderByDesc('id')->first();
814
815 $settings = $meta ? Helper::safeUnserialize($meta->value) : [];
816
817 if (!is_array($settings)) {
818 $settings = [];
819 }
820
821 // Write-only secret: never return the raw team API key to the browser
822 // (it authenticates the FluentBot API and would leak to logs/extensions/
823 // XSS). Expose only whether a key is stored so the form can show a
824 // "saved" hint; the input stays blank and only overwrites on a new key.
825 $hasApiKey = !empty($settings['generalApiKey']);
826 unset($settings['generalApiKey']);
827
828 // Per-mapping apiKey is unused (the key is team-wide).
829 if (!empty($settings['productMappings']) && is_array($settings['productMappings'])) {
830 $settings['productMappings'] = array_map(function ($mapping) {
831 if (!is_array($mapping)) {
832 return $mapping;
833 }
834 unset($mapping['apiKey']);
835 return $mapping;
836 }, $settings['productMappings']);
837 }
838
839 $productItems = Product::all()->map(function ($product) {
840 return [
841 'id' => $product->id,
842 'title' => $product->title
843 ];
844 })->values()->all();
845
846 // Default generalBotEnabled to true for backward compatibility with configs saved
847 // before this flag existed — existing installs expect general bot to work on GET.
848 $defaults = [
849 'generalBotId' => '',
850 'generalApiKey' => '',
851 'generalBotEnabled' => true,
852 'isEnabled' => false,
853 'productMappings' => [],
854 'products' => $productItems,
855 ];
856
857 return array_merge($defaults, $settings, [
858 'products' => $productItems,
859 'hasApiKey' => $hasApiKey,
860 ]);
861 }
862
863 public function saveFluentBotSettings(Request $request)
864 {
865 $where = [
866 'object_type' => 'fluent_bot_settings',
867 'object_id' => 1,
868 'key' => '_fs_fluent_bot_config'
869 ];
870
871 $existing = Meta::where($where)->orderByDesc('id')->first();
872 $existingConfig = $existing ? Helper::safeUnserialize($existing->value) : [];
873 if (!is_array($existingConfig)) {
874 $existingConfig = [];
875 }
876
877 // Write-only key: a blank submission means "keep the stored key" (the form
878 // never round-trips the secret), so only overwrite when a new key is sent.
879 $submittedApiKey = trim($request->getSafe('generalApiKey', 'sanitize_text_field'));
880 $apiKey = $submittedApiKey !== '' ? $submittedApiKey : ($existingConfig['generalApiKey'] ?? '');
881
882 $data = [
883 'generalBotId' => $request->getSafe('generalBotId', 'sanitize_text_field'),
884 'generalApiKey' => $apiKey,
885 'generalBotEnabled' => filter_var($request->get('generalBotEnabled', true), FILTER_VALIDATE_BOOLEAN),
886 'isEnabled' => $request->getSafe('isEnabled', 'rest_sanitize_boolean'),
887 'productMappings' => []
888 ];
889
890 $productMappings = (array) $request->get('productMappings', []);
891 $seenProductIds = [];
892
893 foreach ($productMappings as $mapping) {
894 if (!is_array($mapping)) {
895 continue;
896 }
897
898 $productId = intval($mapping['productId'] ?? 0);
899 $botId = trim(sanitize_text_field($mapping['botId'] ?? ''));
900
901 // Drop invalid rows: empty botId would override the general bot with nothing
902 // at resolution time (resolveApiCredentials), producing runtime failures.
903 if ($productId < 1 || $botId === '') {
904 continue;
905 }
906
907 // Dedupe by productId — first valid mapping wins.
908 if (isset($seenProductIds[$productId])) {
909 continue;
910 }
911 $seenProductIds[$productId] = true;
912
913 $data['productMappings'][] = [
914 'productId' => $productId,
915 'productTitle' => sanitize_text_field($mapping['productTitle'] ?? ''),
916 'botId' => $botId,
917 ];
918 }
919
920 $serialized = maybe_serialize($data);
921
922 if ($existing) {
923 // Update the latest row; do not prune siblings — concurrent first-writes could
924 // race and delete each other's inserts, leaving zero rows (data loss).
925 // Reads use orderByDesc('id')->first() so duplicates are harmless at read time.
926 $existing->update(['value' => $serialized]);
927 } else {
928 Meta::create(array_merge($where, ['value' => $serialized]));
929 AIActivityLogsMigrator::migrate();
930 }
931
932 // Write-only: never echo the raw key back to the browser.
933 $responseData = $data;
934 unset($responseData['generalApiKey']);
935 $responseData['hasApiKey'] = $apiKey !== '';
936
937 return [
938 'success' => true,
939 'message' => 'Settings saved successfully',
940 'data' => $responseData
941 ];
942 }
943
944 public function getFluentBotPresets()
945 {
946 $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
947 $custom = $service->getCustomPresets();
948
949 if (!empty($custom)) {
950 return ['presets' => $custom];
951 }
952
953 // Return defaults without persisting — saving happens only when the user explicitly posts.
954 $helper = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotHelper();
955 return ['presets' => $helper->getPresetPrompts('createResponse')];
956 }
957
958 public function saveFluentBotPresets(Request $request)
959 {
960 $presets = (array) $request->get('presets', []);
961
962 $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
963 $saved = $service->saveCustomPresets($presets);
964
965 return [
966 'success' => true,
967 'message' => __('Prompt options saved successfully', 'fluent-support'),
968 'presets' => $saved
969 ];
970 }
971
972 }
973