PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.3.2
Fluent Support – Helpdesk & Customer Support Ticket System v2.3.2
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.2, at app/Http/Controllers/SettingsController.php

972 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 }
502
503 /**
504 * installFluentCRM method will install Fluent CRM plugin
505 * @return array
506 */
507 public function installFluentCRM()
508 {
509
510 if (defined('FLUENTCRM')) {
511 return [
512 'is_installed' => true,
513 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
514 ];
515 }
516
517 if (!current_user_can('install_plugins')) {
518 return $this->sendError([
519 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
520 ]);
521 }
522
523 $plugin_id = 'fluent-crm';
524 $plugin = [
525 'name' => 'Fluent CRM',
526 'repo-slug' => 'fluent-crm',
527 'file' => 'fluent-crm.php',
528 ];
529
530 $this->backgroundInstaller($plugin, $plugin_id);
531
532 if (defined('FLUENTCRM')) {
533 return [
534 'is_installed' => true,
535 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
536 ];
537 } else {
538 return $this->sendError([
539 'message' => __('Sorry! FluentCRM could not be installed. Please install manually', 'fluent-support')
540 ]);
541 }
542 }
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
582 private function backgroundInstaller($plugin_to_install, $plugin_id)
583 {
584 if (!empty($plugin_to_install['repo-slug'])) {
585 require_once ABSPATH . 'wp-admin/includes/file.php';
586 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
587 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
588 require_once ABSPATH . 'wp-admin/includes/plugin.php';
589
590 WP_Filesystem();
591
592 $skin = new \Automatic_Upgrader_Skin();
593 $upgrader = new \WP_Upgrader($skin);
594 $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
595 $plugin_slug = $plugin_to_install['repo-slug'];
596 $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
597 $installed = false;
598 $activate = false;
599
600 // See if the plugin is installed already.
601 if (isset($installed_plugins[$plugin_file])) {
602 $installed = true;
603 $activate = !is_plugin_active($installed_plugins[$plugin_file]);
604 }
605
606 // Install this thing!
607 if (!$installed) {
608 // Suppress feedback.
609 ob_start();
610
611 try {
612 $plugin_information = plugins_api(
613 'plugin_information',
614 array(
615 'slug' => $plugin_slug,
616 'fields' => array(
617 'short_description' => false,
618 'sections' => false,
619 'requires' => false,
620 'rating' => false,
621 'ratings' => false,
622 'downloaded' => false,
623 'last_updated' => false,
624 'added' => false,
625 'tags' => false,
626 'homepage' => false,
627 'donate_link' => false,
628 'author_profile' => false,
629 'author' => false,
630 ),
631 )
632 );
633
634 if (is_wp_error($plugin_information)) {
635 throw new \Exception($plugin_information->get_error_message());
636 }
637
638 $package = $plugin_information->download_link;
639 $download = $upgrader->download_package($package);
640
641 if (is_wp_error($download)) {
642 throw new \Exception($download->get_error_message());
643 }
644
645 $working_dir = $upgrader->unpack_package($download, true);
646
647 if (is_wp_error($working_dir)) {
648 throw new \Exception($working_dir->get_error_message());
649 }
650
651 $result = $upgrader->install_package(
652 array(
653 'source' => $working_dir,
654 'destination' => WP_PLUGIN_DIR,
655 'clear_destination' => false,
656 'abort_if_destination_exists' => false,
657 'clear_working' => true,
658 'hook_extra' => array(
659 'type' => 'plugin',
660 'action' => 'install',
661 ),
662 )
663 );
664
665 if (is_wp_error($result)) {
666 throw new \Exception($result->get_error_message());
667 }
668
669 $activate = true;
670
671 } catch (\Exception $e) {
672 }
673
674 // Discard feedback.
675 ob_end_clean();
676 }
677
678 wp_clean_plugins_cache();
679
680 // Activate this thing.
681 if ($activate) {
682 try {
683 $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
684
685 if (is_wp_error($result)) {
686 throw new \Exception($result->get_error_message());
687 }
688 } catch (\Exception $e) {
689 }
690 }
691 }
692 }
693
694 private function associate_plugin_file($plugins, $key)
695 {
696 $path = explode('/', $key);
697 $filename = end($path);
698 $plugins[$filename] = $key;
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::all()->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 ];
969 }
970
971 }
972