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

975 lines 36.7 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\Database\Migrations\AIActivityLogsMigrator;
12 use FluentSupport\Framework\Http\Request\Request;
13 use FluentSupport\App\Hooks\Handlers\ReCaptchaHandler;
14 use FluentSupport\Framework\Support\Arr;
15
16 /**
17 * SettingsController class is responsible for all settings
18 * This class is responsible for all request related to settings under global settings tab
19 * @package FluentSupport\App\Http\Controllers
20 *
21 * @version 1.0.0
22 */
23 class SettingsController extends Controller
24 {
25 /**
26 * getSettings method will return the settings by settings key
27 * @param Request $request
28 * @return array|array[]
29 */
30 public function getSettings(Request $request)
31 {
32 $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
33
34 return (new Settings)->get($settingsKey);
35 }
36
37 /**
38 * getIntegrationSettings method will return the settings for integration
39 * @param Request $request
40 * @return array
41 */
42 public function getIntegrationSettings(Request $request)
43 {
44 $settings = Meta::where('object_type', 'integration_settings')->get();
45 $integrationSettings = [];
46 foreach ($settings as $index => $setting) {
47 $data = Helper::safeUnserialize($setting->value);
48 if (!empty($data['status']) && $data && $data['status'] == 'yes') {
49 $integrationSettings[] = $setting->key;
50 }
51 }
52 return $integrationSettings;
53 }
54
55 /**
56 * saveSettings method will save the requested settings data by setting key
57 * @param Request $request
58 * @return array
59 */
60 public function saveSettings(Request $request)
61 {
62 $settingsKey = $request->getSafe('settings_key', 'sanitize_text_field');
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
83 (new Settings)->save($settingsKey, $settings);
84
85 return [
86 'message' => __('Settings has been updated', 'fluent-support')
87 ];
88 }
89
90 /**
91 * getPages method will return the list of pages created in WP
92 * @return array
93 */
94 public function getPages()
95 {
96 return [
97 'pages' => Helper::getWPPages()
98 ];
99 }
100
101 /**
102 * setupPortal method will setup the support portal
103 * @param Request $request
104 * @return array
105 * @throws \FluentSupport\Framework\Validator\ValidationException
106 */
107 public function setupPortal(Request $request)
108 {
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
117 $this->validate($mailbox, [
118 'name' => 'required',
119 'email' => 'required|email',
120 'box_type' => 'required'
121 ]);
122
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 ] : [];
128
129 $createPage = $settings['create_portal_page'] == 'yes';
130
131 if (!$createPage && empty($settings['portal_page_id'])) {
132 Helper::getSafeErrorMessage(new \Exception(__('Please select a page or enable create page', 'fluent-support')));
133 }
134
135 if ($createPage) {
136 // we have to create the page
137 $page_id = wp_insert_post(
138 array(
139 'comment_status' => 'close',
140 'ping_status' => 'close',
141 'post_author' => get_current_user_id(),
142 'post_title' => __('Support Portal', 'fluent-support'),
143 'post_status' => 'publish',
144 'post_content' => '<!-- wp:shortcode -->[fluent_support_portal]<!-- /wp:shortcode -->',
145 'post_type' => 'page'
146 )
147 );
148 } else {
149 $page_id = intval($settings['portal_page_id']);
150 }
151
152 $newMailBox = MailBox::first();
153 if (!$newMailBox) {
154 $mailbox['is_default'] = 'yes';
155 $mailbox['created_by'] = get_current_user_id();
156 $mailbox['settings']['admin_email_address'] = $mailbox['email'];
157 $newMailBox = MailBox::create($mailbox);
158 }
159
160 $settingsClass = new Settings();
161 $globalSettings = $settingsClass->globalBusinessSettings();
162
163 $globalSettings['portal_page_id'] = $page_id;
164
165 $settingsClass->save('global_business_settings', $globalSettings);
166
167
168 if (defined('WC_PLUGIN_FILE')) {
169 // URL Flash
170 flush_rewrite_rules(false);
171 }
172
173 return [
174 'mailbox' => $newMailBox,
175 'global_settings' => $globalSettings,
176 'mailboxes' => MailBox::select(['id', 'name', 'settings'])->get(),
177 'has_fluentform' => defined('FLUENTFORM')
178 ];
179
180 }
181
182 /**
183 * getFluentCRMSettings method will return the settings for Fluent CRM
184 * @param Request $request
185 * @return array
186 */
187 public function getFluentCRMSettings(Request $request)
188 {
189 if (defined('FLUENTCRM')) {
190 $settingDefault = [
191 'enabled' => 'no',
192 'default_status' => 'subscribed',
193 'assigned_list' => '',
194 'assigned_tags' => []
195 ];
196
197 $settings = Helper::getOption('_fluentcrm_intergration_settings');
198
199 $settings = wp_parse_args($settings, $settingDefault);
200
201 $settingsFields = [
202 'enabled' => [
203 'type' => 'inline-checkbox',
204 'true_label' => 'yes',
205 'false_label' => 'no',
206 'checkbox_label' => __('Enable FluentCRM Integration', 'fluent-support')
207 ],
208 'default_status' => [
209 'type' => 'input-radio',
210 'label' => __('Default status for new contacts', 'fluent-support'),
211 'options' => [
212 [
213 'id' => 'subscribed',
214 'label' => __('Subscribed', 'fluent-support')
215 ],
216 [
217 'id' => 'pending',
218 'label' => __('Pending', 'fluent-support')
219 ]
220 ],
221 'dependency' => [
222 'depends_on' => 'enabled',
223 'operator' => '=',
224 'value' => 'yes'
225 ],
226 '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')
227 ],
228 'assigned_list' => [
229 'type' => 'input-options',
230 'label' => __('Add to FluentCRM list (optional)', 'fluent-support'),
231 'options' => \FluentCrm\App\Models\Lists::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
232 'dependency' => [
233 'depends_on' => 'enabled',
234 'operator' => '=',
235 'value' => 'yes'
236 ],
237 ],
238 'assigned_tags' => [
239 'type' => 'input-options',
240 'multiple' => true,
241 'label' => __('Add to Tags', 'fluent-support'),
242 'options' => \FluentCrm\App\Models\Tag::select(['id', 'title'])->orderBy('title', 'ASC')->get(),
243 'dependency' => [
244 'depends_on' => 'enabled',
245 'operator' => '=',
246 'value' => 'yes'
247 ]
248 ]
249 ];
250
251 return [
252 'is_installed' => true,
253 'settings' => $settings,
254 'settings_fields' => $settingsFields,
255 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
256 ];
257 }
258
259 return [
260 'is_installed' => false,
261 'fluentcrm_logo' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/fluentcrm-logo.svg'
262 ];
263
264 }
265
266 public function setupInstallation(Request $request)
267 {
268 $installFluentForm = $request->getSafe('install_fluentform', 'sanitize_text_field', 'no');
269
270 if ($installFluentForm == 'yes' && !defined('FLUENTFORM')) {
271 $this->installFluentForm();
272 }
273
274 $optinEmail = $request->getSafe('optin_email', 'sanitize_email', '');
275 if ($optinEmail && is_email($optinEmail)) {
276 $this->shareEmail($optinEmail);
277 }
278
279 $shareEssential = $request->getSafe('share_essentials', 'sanitize_text_field', 'no');
280 if ($shareEssential == 'yes') {
281 Helper::updateOption('_share_essential', $shareEssential);
282 }
283
284 return $this->sendSuccess([
285 'message' => __('Installation has been completed', 'fluent-support')
286 ]);
287
288 }
289
290 public function saveReCaptchaSettings(Request $request)
291 {
292 $data = $request->get('reCaptcha');
293
294 if (is_string($data) && 'clear-reCaptcha-settings' === sanitize_text_field($data)) {
295 if (Meta::where('object_type', '_fs_recaptcha_settings')->delete()) {
296 return $this->sendSuccess([
297 'message' => __('Your reCAPTCHA settings deleted successfully.', 'fluent-support'),
298 ]);
299 }
300
301 return $this->sendError([
302 'message' => __('Unable to delete reCAPTCHA settings, try again', 'fluent-support'),
303 ]);
304 }
305
306 if (!is_array($data)) {
307 return $this->sendError([
308 'message' => __('Invalid reCAPTCHA data.', 'fluent-support'),
309 ]);
310 }
311
312 $reCaptchaData = [
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'),
318 ];
319
320 $previousValue = Meta::where('object_type', '_fs_recaptcha_settings')->first();
321
322 if ($previousValue === $reCaptchaData) {
323 return $this->sendError([
324 'message' => __('Your recaptcha details are already saved.', 'fluent-support'),
325 ]);
326 }
327
328 $captchaResponse = sanitize_text_field($data['captchaResponse'] ?? '');
329
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) {
339 return $this->sendError([
340 'message' => __('Please verify reCAPTCHA before saving.', 'fluent-support'),
341 ]);
342 }
343
344 if ($previousValue) {
345 Meta::where('object_type', '_fs_recaptcha_settings')->update([
346 'value' => maybe_serialize($reCaptchaData)
347 ]);
348 return $this->sendSuccess([
349 'message' => __('Your reCAPTCHA settings updated successfully.', 'fluent-support'),
350 ]);
351 } else {
352 Meta::insert([
353 'object_type' => '_fs_recaptcha_settings',
354 'key' => '_fs_recaptcha_data',
355 'value' => maybe_serialize($reCaptchaData)
356 ]);
357 }
358
359 return $this->sendSuccess([
360 'message' => __('Your reCAPTCHA settings added successfully.', 'fluent-support'),
361 ]);
362 }
363
364 public function saveOpenAISettings(Request $request)
365 {
366 $data = [
367 'api_key' => $request->getSafe('api_key', 'sanitize_text_field', ''),
368 'model' => $request->getSafe('model', 'sanitize_text_field', ''),
369 ];
370
371 $response = Helper::authorizeChatGPTAPIKey($data);
372
373 if (is_wp_error($response)) {
374 return $this->sendError([
375 'message' => __('There was an error verifying the API key.', 'fluent-support'),
376 ]);
377 }
378
379 $body = json_decode(wp_remote_retrieve_body($response), true);
380
381 if (isset($body['error'])) {
382 return $this->sendError([
383 'message' => __('Invalid API key. Please provide a valid ChatGPT API key.', 'fluent-support'),
384 ]);
385 }
386
387 try {
388 $isDataSaved = Helper::saveOpenAIData('_fs_openai_settings', '_fs_openai_data', $data);
389 if ($isDataSaved) {
390 AIActivityLogsMigrator::migrate();
391 }
392 return $this->sendSuccess([
393 'message' => __('OpenAI settings have been successfully saved.', 'fluent-support'),
394 ]);
395 } catch (\Exception $e) {
396 // translators: %s is the error message from the exception
397 $translatedMessage = __('An error occurred while saving the settings: %s', 'fluent-support');
398 $errorMessage = sprintf($translatedMessage, Helper::getSafeErrorMessage($e));
399
400 return $this->sendError([
401 'message' => $errorMessage,
402 ]);
403 }
404 }
405
406
407 public function disconnectOpenAI()
408 {
409 $deletedRecords = Meta::where([
410 'object_type' => '_fs_openai_settings',
411 'key' => '_fs_openai_data',
412 ])->delete();
413
414 if ($deletedRecords) {
415 return $this->sendSuccess([
416 'message' => __('OpenAI settings have been successfully disconnected.', 'fluent-support'),
417 ]);
418 } else {
419 return $this->sendError([
420 'message' => __('Failed to disconnect OpenAI settings. No matching records found or an error occurred.', 'fluent-support'),
421 ]);
422 }
423 }
424
425 public function getOpenAISettings()
426 {
427 $modelOptions = $this->getOpenAIModelOptions();
428 $supportedModels = array_column($modelOptions, 'value');
429
430 $settings = [
431 'api_key' => '',
432 'model' => 'gpt-5.2',
433 ];
434
435 $chatGPTSettingsData = Meta::where('object_type', '_fs_openai_settings')->first();
436 if ($chatGPTSettingsData) {
437 $settings = Helper::safeUnserialize($chatGPTSettingsData->value);
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 }
446 }
447
448 $settings['model_options'] = $modelOptions;
449
450 return $this->sendSuccess($settings);
451 }
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
485 public function getReCaptchaSettings()
486 {
487 $reCaptchaSettingsData = Meta::where('object_type', '_fs_recaptcha_settings')->first();
488 if ($reCaptchaSettingsData) {
489 $settings = Helper::safeUnserialize($reCaptchaSettingsData->value);
490 return $this->sendSuccess($settings);
491 }
492
493 return [];
494 }
495
496 private function shareEmail($optinEmail)
497 {
498 $user = get_user_by('ID', get_current_user_id());
499 $data = [
500 'answers' => [
501 'website' => site_url(),
502 'email' => $optinEmail,
503 'first_name' => $user->first_name,
504 'last_name' => $user->last_name,
505 'name' => $user->display_name,
506 'has_fluentform' => defined('FLUENTFORM') ? 'yes' : 'no'
507 ],
508 'questions' => [
509 'website' => 'website',
510 'first_name' => 'first_name',
511 'last_name' => 'last_name',
512 'email' => 'email',
513 'name' => 'name',
514 'has_fluentform' => 'has_fluentform'
515 ],
516 'user' => [
517 'email' => $optinEmail
518 ],
519 'fb_capture' => 1,
520 'form_id' => 77
521 ];
522
523 $url = add_query_arg($data, 'https://wpmanageninja.com/');
524
525 wp_remote_post($url, [
526 'sslverify' => false
527 ]);
528 }
529
530 /**
531 * installFluentCRM method will install Fluent CRM plugin
532 * @return array
533 */
534 public function installFluentCRM()
535 {
536
537 if (defined('FLUENTCRM')) {
538 return [
539 'is_installed' => true,
540 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
541 ];
542 }
543
544 if (!current_user_can('install_plugins')) {
545 return $this->sendError([
546 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
547 ]);
548 }
549
550 $plugin_id = 'fluent-crm';
551 $plugin = [
552 'name' => 'Fluent CRM',
553 'repo-slug' => 'fluent-crm',
554 'file' => 'fluent-crm.php',
555 ];
556
557 $this->backgroundInstaller($plugin, $plugin_id);
558
559 if (defined('FLUENTCRM')) {
560 return [
561 'is_installed' => true,
562 'message' => __('FluentCRM plugin has been installed and activated successfully', 'fluent-support')
563 ];
564 } else {
565 return $this->sendError([
566 'message' => __('Sorry! FluentCRM could not be installed. Please install manually', 'fluent-support')
567 ]);
568 }
569 }
570
571 public function installFluentForm()
572 {
573
574 if (defined('FLUENTFORM')) {
575 return [
576 'is_installed' => true,
577 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
578 ];
579 }
580
581 if (!current_user_can('install_plugins')) {
582 return $this->sendError([
583 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-support')
584 ]);
585 }
586
587 $plugin_id = 'fluentform';
588 $plugin = [
589 'name' => 'Fluent Forms',
590 'repo-slug' => 'fluentform',
591 'file' => 'fluentform.php',
592 ];
593
594 $this->backgroundInstaller($plugin, $plugin_id);
595
596 if (defined('FLUENTFORM')) {
597 return [
598 'is_installed' => true,
599 'message' => __('Fluent Forms plugin has been installed and activated successfully', 'fluent-support')
600 ];
601 } else {
602 return [
603 'is_installed' => false,
604 'message' => __('Fluent Forms could not be installed', 'fluent-support')
605 ];
606 }
607 }
608
609 private function backgroundInstaller($plugin_to_install, $plugin_id)
610 {
611 if (!empty($plugin_to_install['repo-slug'])) {
612 require_once ABSPATH . 'wp-admin/includes/file.php';
613 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
614 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
615 require_once ABSPATH . 'wp-admin/includes/plugin.php';
616
617 WP_Filesystem();
618
619 $skin = new \Automatic_Upgrader_Skin();
620 $upgrader = new \WP_Upgrader($skin);
621 $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
622 $plugin_slug = $plugin_to_install['repo-slug'];
623 $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
624 $installed = false;
625 $activate = false;
626
627 // See if the plugin is installed already.
628 if (isset($installed_plugins[$plugin_file])) {
629 $installed = true;
630 $activate = !is_plugin_active($installed_plugins[$plugin_file]);
631 }
632
633 // Install this thing!
634 if (!$installed) {
635 // Suppress feedback.
636 ob_start();
637
638 try {
639 $plugin_information = plugins_api(
640 'plugin_information',
641 array(
642 'slug' => $plugin_slug,
643 'fields' => array(
644 'short_description' => false,
645 'sections' => false,
646 'requires' => false,
647 'rating' => false,
648 'ratings' => false,
649 'downloaded' => false,
650 'last_updated' => false,
651 'added' => false,
652 'tags' => false,
653 'homepage' => false,
654 'donate_link' => false,
655 'author_profile' => false,
656 'author' => false,
657 ),
658 )
659 );
660
661 if (is_wp_error($plugin_information)) {
662 throw new \Exception($plugin_information->get_error_message());
663 }
664
665 $package = $plugin_information->download_link;
666 $download = $upgrader->download_package($package);
667
668 if (is_wp_error($download)) {
669 throw new \Exception($download->get_error_message());
670 }
671
672 $working_dir = $upgrader->unpack_package($download, true);
673
674 if (is_wp_error($working_dir)) {
675 throw new \Exception($working_dir->get_error_message());
676 }
677
678 $result = $upgrader->install_package(
679 array(
680 'source' => $working_dir,
681 'destination' => WP_PLUGIN_DIR,
682 'clear_destination' => false,
683 'abort_if_destination_exists' => false,
684 'clear_working' => true,
685 'hook_extra' => array(
686 'type' => 'plugin',
687 'action' => 'install',
688 ),
689 )
690 );
691
692 if (is_wp_error($result)) {
693 throw new \Exception($result->get_error_message());
694 }
695
696 $activate = true;
697
698 } catch (\Exception $e) {
699 }
700
701 // Discard feedback.
702 ob_end_clean();
703 }
704
705 wp_clean_plugins_cache();
706
707 // Activate this thing.
708 if ($activate) {
709 try {
710 $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
711
712 if (is_wp_error($result)) {
713 throw new \Exception($result->get_error_message());
714 }
715 } catch (\Exception $e) {
716 }
717 }
718 }
719 }
720
721 private function associate_plugin_file($plugins, $key)
722 {
723 $path = explode('/', $key);
724 $filename = end($path);
725 $plugins[$filename] = $key;
726 return $plugins;
727 }
728
729 public function getRemoteUploadSettings(Request $request)
730 {
731 $dropBoxConfigured = false;
732 $googleDriveConfigured = false;
733 $cloudflareR2Configured = false;
734 $amazonS3Configured = false;
735
736 if (defined('FLUENTSUPPORTPRO')) {
737 $dropBoxSettings = Helper::getIntegrationOption('dropbox_settings');
738 $dropBoxConfigured = $dropBoxSettings && !empty($dropBoxSettings['access_token']);
739
740 $googleDriveSettings = Helper::getIntegrationOption('google_drive_settings');
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';
748 }
749
750 $drivers = apply_filters('fluent_support/storage_drivers_info', [
751 'local' => [
752 'title' => 'Default WordPress Storage',
753 'is_disabled' => false,
754 'is_configured' => true,
755 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/folder.svg',
756 'description' => __('Upload and store the files to your WordPress File System Storage.', 'fluent-support')
757 ],
758 'dropbox' => [
759 'meta_key' => 'dropbox_settings',
760 'title' => 'Dropbox',
761 'has_config' => true,
762 'is_configured' => $dropBoxConfigured,
763 'require_pro' => !defined('FLUENTSUPPORTPRO'),
764 'icon' => FLUENT_SUPPORT_PLUGIN_URL . 'assets/images/icons/dbox.svg',
765 'description' => __('Upload and store the files to your Dropbox Storage.', 'fluent-support')
766 ],
767 'google_drive' => [
768 'meta_key' => 'google_drive_settings',
769 'title' => 'Google Drive',
770 'has_config' => true,
771 'is_configured' => $googleDriveConfigured,
772 'require_pro' => !defined('FLUENTSUPPORTPRO'),
773 'upgrade_url' => 'https://fluentsupport.com/pricing',
774 'description' => __('Upload and store the files to your Google Drive Storage.', 'fluent-support'),
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',
796 ]
797 ]);
798
799 return [
800 'drivers' => $drivers,
801 'enabled_driver' => Helper::getUploadDriverKey()
802 ];
803 }
804
805 public function updateRemoteUploadDriver(Request $request)
806 {
807 $driver = $request->getSafe('driver', 'sanitize_text_field');
808 Helper::updateOption('file_upload_driver', $driver);
809
810 return [
811 'message' => 'Upload driver has been updated successfully',
812 'driver' => $driver
813 ];
814 }
815
816 /**
817 * getIntegrationLogs method will return the integration logs
818 * @return array
819 */
820 public function integrationStatuses()
821 {
822 return [
823 'connections' => Helper::getIntegrationStatuses()
824 ];
825 }
826
827 public function getSettingsMenu()
828 {
829 return Helper::getGlobalSettingsMenu();
830 }
831
832 public function getFluentBotSettings()
833 {
834 $meta = Meta::where([
835 'object_type' => 'fluent_bot_settings',
836 'object_id' => 1,
837 'key' => '_fs_fluent_bot_config'
838 ])->orderByDesc('id')->first();
839
840 $settings = $meta ? Helper::safeUnserialize($meta->value) : [];
841
842 if (!is_array($settings)) {
843 $settings = [];
844 }
845
846 unset($settings['generalApiKey']);
847
848 if (!empty($settings['productMappings']) && is_array($settings['productMappings'])) {
849 $settings['productMappings'] = array_map(function ($mapping) {
850 if (!is_array($mapping)) {
851 return $mapping;
852 }
853 unset($mapping['apiKey']);
854 return $mapping;
855 }, $settings['productMappings']);
856 }
857
858 $productItems = Product::all()->map(function ($product) {
859 return [
860 'id' => $product->id,
861 'title' => $product->title
862 ];
863 })->values()->all();
864
865 // Default generalBotEnabled to true for backward compatibility with configs saved
866 // before this flag existed — existing installs expect general bot to work on GET.
867 $defaults = [
868 'generalBotId' => '',
869 'generalBotEnabled' => true,
870 'isEnabled' => false,
871 'productMappings' => [],
872 'products' => $productItems,
873 ];
874
875 return array_merge($defaults, $settings, [
876 'products' => $productItems
877 ]);
878 }
879
880 public function saveFluentBotSettings(Request $request)
881 {
882 $data = [
883 'generalBotId' => $request->getSafe('generalBotId', 'sanitize_text_field'),
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 $where = [
922 'object_type' => 'fluent_bot_settings',
923 'object_id' => 1,
924 'key' => '_fs_fluent_bot_config'
925 ];
926
927 $existing = Meta::where($where)->orderByDesc('id')->first();
928
929 if ($existing) {
930 // Update the latest row; do not prune siblings — concurrent first-writes could
931 // race and delete each other's inserts, leaving zero rows (data loss).
932 // Reads use orderByDesc('id')->first() so duplicates are harmless at read time.
933 $existing->update(['value' => $serialized]);
934 } else {
935 Meta::create(array_merge($where, ['value' => $serialized]));
936 AIActivityLogsMigrator::migrate();
937 }
938
939 return [
940 'success' => true,
941 'message' => 'Settings saved successfully',
942 'data' => $data
943 ];
944 }
945
946 public function getFluentBotPresets()
947 {
948 $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
949 $custom = $service->getCustomPresets();
950
951 if (!empty($custom)) {
952 return ['presets' => $custom];
953 }
954
955 // Return defaults without persisting — saving happens only when the user explicitly posts.
956 $helper = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotHelper();
957 return ['presets' => $helper->getPresetPrompts('createResponse')];
958 }
959
960 public function saveFluentBotPresets(Request $request)
961 {
962 $presets = (array) $request->get('presets', []);
963
964 $service = new \FluentSupport\App\Services\Integrations\FluentBot\FluentBotService();
965 $saved = $service->saveCustomPresets($presets);
966
967 return [
968 'success' => true,
969 'message' => __('Prompt options saved successfully', 'fluent-support'),
970 'presets' => $saved
971 ];
972 }
973
974 }
975