PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
fluent-support / app / Services / EmailNotification / Settings.php

Settings.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.4.5, at app/Services/EmailNotification/Settings.php

761 lines 39.2 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\Services\EmailNotification;
4 use FluentSupportPro\Database\Migrations\TimeTrackMigrator;
5
6 use FluentSupport\App\Services\Helper;
7 use FluentSupport\App\Services\Notifications\NotificationSettings;
8 use FluentSupport\Framework\Support\Arr;
9
10 class Settings
11 {
12 public function getEmailSettingsKeys()
13 {
14 $key = apply_filters('fluent_support/email_setting_keys', [
15 'ticket_created_email_to_customer',
16 'ticket_replied_by_agent_email_to_customer',
17 'ticket_closed_by_agent_email_to_customer',
18 'ticket_created_email_to_admin',
19 'ticket_replied_by_customer_email_to_admin',
20 'ticket_agent_on_change',
21 'ticket_created_by_agent_email_to_customer',
22 'ticket_created_by_agent_on_behalf_email_to_customer'
23 ]);
24 return $key;
25 }
26
27 public function get($settingsKey)
28 {
29 if ($settingsKey == 'global_business_settings') {
30 return [
31 'settings' => $this->globalBusinessSettings(),
32 'fields' => $this->getGlobalBusinessSettingsFields()
33 ];
34 }
35
36 if ($settingsKey == NotificationSettings::OPTION_KEY) {
37 $notificationSettings = new NotificationSettings();
38
39 return [
40 'settings' => $notificationSettings->get(false),
41 'fields' => $notificationSettings->getFields()
42 ];
43 }
44
45 return [
46 'settings' => [],
47 'fields' => []
48 ];
49 }
50
51 /**
52 * save method will save the requested settings by settings key
53 * @param $settingsKey
54 * @param $settings
55 * @return mixed
56 */
57 public function save($settingsKey, $settings)
58 {
59 if ($settingsKey == 'global_business_settings' && is_array($settings) && array_key_exists('internal_notifications_enabled', $settings)) {
60 $notificationSettings = new NotificationSettings();
61 $savedNotificationSettings = $notificationSettings->get(false);
62 $savedNotificationSettings['enabled'] = $settings['internal_notifications_enabled'];
63 $notificationSettings->save($savedNotificationSettings);
64 }
65
66 if ($settingsKey == 'global_business_settings' && empty($settings['accepted_file_types'])) {
67 $settings['accepted_file_types'] = [];
68 }
69
70 if ($settingsKey == 'global_business_settings') {
71 $settings['ticket_link_portal_migrated'] = 'yes';
72 if (isset($settings['min_serial_number'])) {
73 $settings['min_serial_number'] = max(1, (int) $settings['min_serial_number']);
74 }
75 // The frontend only ever offers 3/7/15 — reject anything else
76 // (a tampered request, or a stale/unexpected value) back to the
77 // 15-day default rather than trusting the submitted value as-is.
78 if (isset($settings['report_snapshot_retention']) && !in_array($settings['report_snapshot_retention'], ['3', '7', '15'], true)) {
79 $settings['report_snapshot_retention'] = '15';
80 }
81 // These keys become user meta on signup, so keep only the offered fields.
82 if (isset($settings['custom_registration_form_field'])) {
83 $settings['custom_registration_form_field'] = array_values(array_intersect(
84 (array) $settings['custom_registration_form_field'],
85 array_keys(Helper::getCustomRegistrationFieldOptions())
86 ));
87 }
88 }
89
90 if ($settingsKey == 'global_business_settings' && !empty($settings['agent_time_tracking'])) {
91 if (class_exists(TimeTrackMigrator::class) && $settings['agent_time_tracking'] === 'yes') {
92 TimeTrackMigrator::migrate();
93 }
94 }
95
96 if ($settingsKey == NotificationSettings::OPTION_KEY) {
97 return (new NotificationSettings())->save($settings);
98 }
99
100 $result = Helper::updateOption($settingsKey, $settings);
101
102 return $result;
103 }
104
105 /**
106 * globalBusinessSettings method will fetch global settings from database, parse and return
107 * @param bool $cached
108 * @return array|mixed
109 */
110 public function globalBusinessSettings($cached = true)
111 {
112 static $settings;
113
114 if($cached && $settings) {
115 return $settings;
116 }
117
118 $defaults = [
119 'portal_page_id' => '',
120 'ticket_link_portal' => 'default',
121 'ticket_link_portal_migrated' => 'no',
122 // translators: %1$s is opening paragraph tag, %2$s is closing paragraph tag
123 'login_message' => sprintf(__('%1$sPlease login or create an account to access the Customer Support Portal%2$s [fluent_support_auth]', 'fluent-support'), '<p>', '</p>'),
124 'disable_public_ticket' => 'no',
125 'accepted_file_types' => ['images', 'csv', 'documents', 'zip', 'json'],
126 'max_file_size' => 2,
127 'max_file_upload' => 3,
128 'del_files_on_close' => 'no',
129 'enable_admin_bar_summary' => 'no',
130 'internal_notifications_enabled' => 'no',
131 'enable_draft_mode' => 'no',
132 'agent_feedback_rating' => 'no',
133 'keyboard_shortcuts' => 'yes',
134 'enable_min_serial_number' => 'no',
135 'ticket_prefix' => '',
136 'min_serial_number' => 1,
137 'enable_fluent_booking_integration' => 'no',
138 'report_snapshot_retention' => '15',
139 ];
140
141 //Get default/existing settings from database using the key global_business_settings
142 $existingSettings = Helper::getOption('global_business_settings', []);
143
144 if (!$existingSettings) {
145 $settings = $defaults;
146 return $settings;
147 }
148
149 $settings = wp_parse_args($existingSettings, $defaults);
150 $settings['ticket_link_portal'] = $settings['ticket_link_portal'] ?: 'default';
151
152 if (Arr::get($settings, 'ticket_link_portal_migrated') !== 'yes') {
153 $settings['ticket_link_portal'] = $this->resolveLegacyTicketLinkPortal($settings);
154 $settings['ticket_link_portal_migrated'] = 'yes';
155 Helper::updateOption('global_business_settings', $settings);
156 }
157
158 return $settings;
159 }
160
161 private function resolveLegacyTicketLinkPortal($settings)
162 {
163 $selectedPortal = Arr::get($settings, 'ticket_link_portal', 'default');
164
165 if ($selectedPortal && $selectedPortal !== 'default') {
166 return $selectedPortal;
167 }
168
169 $ticketFormSettings = Helper::getOption('_ticket_form_settings', []);
170
171 if (Arr::get($settings, 'enable_fc_menu') === 'yes') {
172 return 'fluent_cart';
173 }
174
175 // The legacy pro plugin defaulted enable_woo_menu to 'yes', so treat an
176 // absent value the same as 'yes' — only skip migration if explicitly 'no'.
177 // WC portal was always Pro-only, so require Pro to be active as well.
178 if (defined('FLUENTSUPPORTPRO_PLUGIN_VERSION') && defined('WC_PLUGIN_FILE') && Arr::get($ticketFormSettings, 'enable_woo_menu', 'yes') !== 'no') {
179 return 'woocommerce';
180 }
181
182 return 'default';
183 }
184
185
186 /**
187 * getGlobalBusinessSettingsFields method will prepare the list of field, and it's property that will be used in global settings form
188 * @return array
189 */
190 private function getGlobalBusinessSettingsFields()
191 {
192 $nextSerialNumber = \FluentSupport\App\Models\Ticket::getNextSerialNumber();
193
194 $mimeGroups = Helper::getMimeGroups();
195
196 $formattedMimeGroups = [];
197
198 foreach ($mimeGroups as $mimeGroup => $mime) {
199 $formattedMimeGroups[$mimeGroup] = $mime['title'];
200 }
201
202 $customRegistrationFormOptions = Helper::getCustomRegistrationFieldOptions();
203
204 $fields = [
205 'ticket_link_portal_description' => [
206 'type' => 'html-viewer',
207 'label' => __('Customer Portal', 'fluent-support'),
208 'wrapper_class' => 'fs_portal_destination_description',
209 ],
210 'ticket_link_portal' => [
211 'type' => 'input-radio',
212 'wrapper_class' => 'fs_portal_destination_options',
213 'options' => $this->getTicketLinkPortalOptions(),
214 ],
215 'fluent_community_portal_link' => [
216 'type' => 'html-viewer',
217 'wrapper_class' => 'fs_fluent_community_portal_link',
218 'html' => $this->getFluentCommunityPortalLinkHtml(),
219 'dependency' => [
220 'depends_on' => 'ticket_link_portal',
221 'operator' => '=',
222 'value' => 'fluent_community'
223 ],
224 ],
225 'portal_page_id' => [
226 'type' => 'input-options',
227 'label' => __('Select a Page', 'fluent-support'),
228 'wrapper_class' => 'fs_portal_page_selector',
229 'show_id' => true,
230 'placeholder' => __('Select Portal Page', 'fluent-support'),
231 'options' => Helper::getWPPages(),//Get list of published pages
232 'inline_help' => __('Please provide the page id where you want to show the tickets for your customers. Use shortcode <code>[fluent_support_portal]</code> in that page', 'fluent-support'),
233 'admin_url' => admin_url(),
234 'home_url' => home_url('/'),
235 'dependency' => [
236 'depends_on' => 'ticket_link_portal',
237 'operator' => '=',
238 'value' => 'default'
239 ],
240 ],
241 'login_message' => [
242 'type' => 'wp-editor',
243 'label' => __('Message for non logged in users', 'fluent-support'),
244 'inline_help' => __('Please provide message for not logged in users. You can place login shortcode too. Use shortcode <code>[fluent_support_login]</code> to show built-in login form. For the user registration use this shortcode <code>[fluent_support_signup]</code> and for both form please use <code>[fluent_support_auth]</code>. Registration forms only show when "Anyone can register" is enabled in Settings > General.', 'fluent-support')
245 ],
246 'disable_public_ticket' => [
247 'wrapper_class' => '',
248 'type' => 'inline-checkbox',
249 'label' => __('Public Ticket Interaction', 'fluent-support'),
250 'true_label' => 'yes',
251 'false-label' => 'no',
252 'checkbox_label' => __('Disable Public Ticket interaction', 'fluent-support'),
253 'inline_help' => __('If you enable this then only logged in user can reply the tickets. Otherwise, url will be signed and intended user can reply without logging in', 'fluent-support')
254 ],
255 'accepted_file_types' => [
256 'wrapper_class' => '',
257 'type' => 'checkbox-group',
258 'label' => __('Accepted File Types', 'fluent-support'),
259 'options' => $formattedMimeGroups
260 ],
261 'max_file_size' => [
262 'wrapper_class' => 'fs_settings_half_field',
263 'type' => 'input-text',
264 'data_type' => 'number',
265 'label' => __('Max File Size (in MegaByte)', 'fluent-support'),
266 ],
267 'max_file_upload' => [
268 'wrapper_class' => 'fs_settings_half_field',
269 'type' => 'input-text',
270 'data_type' => 'number',
271 'label' => __('Maximum File Upload', 'fluent-support'),
272 ],
273 'del_files_on_close' => [
274 'wrapper_class' => '',
275 'type' => 'inline-checkbox',
276 'true_label' => 'yes',
277 'false-label' => 'no',
278 'checkbox_label' => __('Delete all attachments on ticket close', 'fluent-support'),
279 'inline_help' => __('If you enable this feature, all attachments associated with a ticket will be deleted when the ticket is closed.', 'fluent-support')
280 ],
281 'enable_admin_bar_summary' => [
282 'wrapper_class' => '',
283 'type' => 'inline-checkbox',
284 'true_label' => 'yes',
285 'false-label' => 'no',
286 'checkbox_label' => __('Enable Fluent Summary In Admin Bar', 'fluent-support'),
287 'inline_help' => __('If you enable this, logged in user can see the ticket summary from top nav bar.', 'fluent-support')
288 ],
289 'internal_notifications_enabled' => [
290 'wrapper_class' => '',
291 'type' => 'inline-checkbox',
292 'true_label' => 'yes',
293 'false-label' => 'no',
294 'checkbox_label' => __('Enable Internal Notifications', 'fluent-support'),
295 'inline_help' => sprintf(
296 '<ul><li>%1$s</li><li>%2$s</li></ul>',
297 __('Enable the in-app notification bell, unread counts, and notification event storage for agents.', 'fluent-support'),
298 __('Notification data older than 15 days will be removed automatically.', 'fluent-support')
299 )
300 ],
301 'enable_draft_mode' => [
302 'wrapper_class' => '',
303 'type' => 'inline-checkbox',
304 'true_label' => 'yes',
305 'false-label' => 'no',
306 'checkbox_label' => __('Enable Draft Mode', 'fluent-support'),
307 'inline_help' => __('If you enable this setting, any written response will be saved as a draft if an agent accidentally closes a ticket.', 'fluent-support')
308 ],
309 'custom_registration_form_field' => [
310 'wrapper_class' => '',
311 'type' => 'checkbox-group',
312 'label' => __('Custom Registration Form Field', 'fluent-support'),
313 'options' => $customRegistrationFormOptions
314 ],
315 'enable_two_fa' => [
316 'wrapper_class' => '',
317 'type' => 'inline-checkbox',
318 'true_label' => 'yes',
319 'false-label' => 'no',
320 'checkbox_label' => __('Enable Two-Factor Authentication', 'fluent-support'),
321 'inline_help' => __('If you enable this setting, users will be required to submit a second form of authentication, such as a code sent to their email, to login.', 'fluent-support')
322 ],
323 'keyboard_shortcuts' => [
324 'wrapper_class' => '',
325 'type' => 'inline-checkbox',
326 'true_label' => 'yes',
327 'false-label' => 'no',
328 'checkbox_label' => __('Enable Keyboard Shortcuts', 'fluent-support'),
329 'inline_help' => __("If you enable this, agents can use keyboard shortcuts for faster actions.", 'fluent-support'),
330 'shortcut_modal' => $this->getKeyboardShortcutModalData()
331 ],
332 'enable_min_serial_number' => [
333 'wrapper_class' => '',
334 'type' => 'inline-checkbox',
335 'true_label' => 'yes',
336 'false-label' => 'no',
337 'checkbox_label' => __('Enable Minimum Ticket Number', 'fluent-support'),
338 'inline_help' => __('Enable this to start public ticket numbers from a custom minimum number.', 'fluent-support')
339 ],
340 'min_serial_number' => [
341 'wrapper_class' => 'fs_settings_half_field',
342 'type' => 'input-text',
343 'data_type' => 'number',
344 'label' => __('Minimum Ticket Number', 'fluent-support'),
345 'help' => __('Once a ticket is created, you cannot lower this value below the current highest ticket number. Set this carefully — raising it is easy, but lowering it below an existing ticket number is not allowed.', 'fluent-support'),
346 'inline_help' => sprintf(
347 __('Next Ticket Number: %d', 'fluent-support'),
348 $nextSerialNumber
349 ),
350 'next_serial_number' => $nextSerialNumber,
351 'dependency' => [
352 'depends_on' => 'enable_min_serial_number',
353 'operator' => '=',
354 'value' => 'yes'
355 ]
356 ],
357 'ticket_prefix' => [
358 'wrapper_class' => 'fs_settings_half_field',
359 'type' => 'input-text',
360 'data_type' => 'text',
361 'label' => __('Ticket Prefix', 'fluent-support'),
362 'inline_help' => __('Optional prefix for newly created public ticket numbers.', 'fluent-support'),
363 'dependency' => [
364 'depends_on' => 'enable_min_serial_number',
365 'operator' => '=',
366 'value' => 'yes'
367 ]
368 ],
369 'report_snapshot_retention' => [
370 'wrapper_class' => '',
371 'type' => 'input-radio',
372 'label' => __('Snapshot Retention', 'fluent-support'),
373 'options' => [
374 ['id' => '3', 'label' => __('3 days', 'fluent-support')],
375 ['id' => '7', 'label' => __('7 days', 'fluent-support')],
376 ['id' => '15', 'label' => __('15 days', 'fluent-support')],
377 ],
378 'inline_help' => __('Advanced Reports saves a snapshot of each report every 6 hours. Snapshots older than this many days are automatically deleted.', 'fluent-support'),
379 ]
380 ];
381 if (defined('FLUENT_BOOKING_VERSION')) {
382 $fields['enable_fluent_booking_integration'] = [
383 'wrapper_class' => '',
384 'type' => 'inline-checkbox',
385 'true_label' => 'yes',
386 'false-label' => 'no',
387 'checkbox_label' => __('Enable Fluent Booking Integration', 'fluent-support'),
388 'inline_help' => __('If you enable this, agents can create booking links and view meeting details inside tickets.', 'fluent-support')
389 ];
390 }
391
392 if (defined('FLUENTSUPPORTPRO_PLUGIN_VERSION')) {
393 $fields['agent_feedback_rating'] = [
394 'wrapper_class' => '',
395 'type' => 'inline-checkbox',
396 'true_label' => 'yes',
397 'false-label' => 'no',
398 'checkbox_label' => __('Agent Feedback Rating', 'fluent-support'),
399 'inline_help' => __("If you enable this setting, users will have the option to provide feedback on an agent's response.", 'fluent-support')
400 ];
401
402 $fields['agent_time_tracking'] = [
403 'wrapper_class' => '',
404 'type' => 'inline-checkbox',
405 'true_label' => 'yes',
406 'false-label' => 'no',
407 'checkbox_label' => __('Agent Time Tracking', 'fluent-support'),
408 'inline_help' => __("If you enable this setting, the agent can specify the amount of time needed to complete a ticket.", 'fluent-support')
409 ];
410
411 }
412
413 if (Helper::getAuthProvider() === 'fluent_auth') {
414 $fields = $this->replaceTwoFaWithFluentAuthNotice($fields);
415 }
416
417 return $fields;
418 }
419
420 /**
421 * With FluentAuth handling portal sign-in, our 2FA toggle no longer applies, so a note
422 * pointing to FluentAuth's 2FA settings takes its place. The stored value is kept.
423 *
424 * @param array $fields
425 * @return array
426 */
427 private function replaceTwoFaWithFluentAuthNotice($fields)
428 {
429 $notice = [
430 'type' => 'html-viewer',
431 'label' => __('Two-Factor Authentication', 'fluent-support'),
432 'wrapper_class' => 'fs_warn_alert_wrapper',
433 'html' => sprintf(
434 /* translators: %s: URL of the FluentAuth settings page */
435 __('Portal sign-in is handled by the FluentAuth plugin, so two-factor authentication for your customers and agents is set up there. FluentAuth applies it to the roles you choose, so include your customers\' role. <a href="%s">Open FluentAuth settings</a>', 'fluent-support'),
436 esc_url(admin_url('admin.php?page=fluent-auth#/settings'))
437 ),
438 ];
439
440 $replaced = [];
441
442 foreach ($fields as $key => $field) {
443 if ($key === 'enable_two_fa') {
444 $replaced['fluent_auth_two_fa_notice'] = $notice;
445 continue;
446 }
447
448 $replaced[$key] = $field;
449 }
450
451 return $replaced;
452 }
453
454 private function getTicketLinkPortalOptions()
455 {
456 $options = [
457 [
458 'id' => 'default',
459 'label' => __('Default Portal Page', 'fluent-support'),
460 ],
461 ];
462
463 if (Helper::isPortalActive('fluent_cart')) {
464 $options[] = [
465 'id' => 'fluent_cart',
466 'label' => __('FluentCart Account Navigation', 'fluent-support'),
467 ];
468 }
469
470 if (Helper::isPortalActive('woocommerce')) {
471 $options[] = [
472 'id' => 'woocommerce',
473 'label' => __('WooCommerce Account Navigation', 'fluent-support'),
474 ];
475 }
476
477 if (Helper::isPortalActive('fluent_community')) {
478 $options[] = [
479 'id' => 'fluent_community',
480 'label' => __('FluentCommunity Portal', 'fluent-support'),
481 ];
482 }
483
484 return apply_filters('fluent_support/ticket_link_portal_options', $options);
485 }
486
487 private function getFluentCommunityPortalLinkHtml()
488 {
489 if (!defined('FLUENT_COMMUNITY_PLUGIN_VERSION') || !class_exists('\FluentCommunity\App\Services\Helper')) {
490 return '';
491 }
492
493 $portalLink = rtrim(\FluentCommunity\App\Services\Helper::baseUrl('support/'), '/\\') . '/';
494
495 return '<div class="fs_portal_link_content">'
496 . '<span class="fs_portal_link_label">' . esc_html__('FluentCommunity Portal URL', 'fluent-support') . '</span>'
497 . '<button type="button" class="fs_portal_link_token" data-portal-link="' . esc_attr($portalLink) . '">'
498 . '<span class="fs_portal_link_text">' . esc_html($portalLink) . '</span>'
499 . '<span class="fs_portal_link_action">' . esc_html__('Copy', 'fluent-support') . '</span>'
500 . '</button>'
501 . '<p class="fc_inline_help">' . esc_html__('Copy this URL and add it to your FluentCommunity menu.', 'fluent-support') . '</p>'
502 . '</div>';
503 }
504
505 /**
506 * Returns translatable keyboard shortcut modal data for the settings form.
507 *
508 * @return array{title: string, mac_label: string, win_label: string, action_label: string, shortcut_label: string, mac_shortcuts: array, win_shortcuts: array}
509 */
510 public function getKeyboardShortcutModalData()
511 {
512 $actionLabel = __('Action', 'fluent-support');
513 $shortcutLabel = __('Shortcut', 'fluent-support');
514
515 // Windows and Linux use Ctrl where macOS uses ⌘ (see resources/admin/Bits/shortcuts.js).
516 $macShortcuts = [
517 ['action' => __('Tickets: Advanced filter toggle', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + f'],
518 ['action' => __('Tickets: Refresh', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + q'],
519 ['action' => __('Tickets: Create Ticket', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + n'],
520 ['action' => __('Tickets: Reset filter', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + r'],
521 ['action' => __('Tickets: All Tickets', 'fluent-support'), 'shortcut' => '⌘ + ⇧ + a'],
522 ['action' => __('Tickets: My Tickets', 'fluent-support'), 'shortcut' => '⌘ + ⇧ + m'],
523 ['action' => __('Tickets: Unassigned', 'fluent-support'), 'shortcut' => '⌘ + ⇧ + u'],
524 ['action' => __('Tickets: Waiting for reply', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + w'],
525 ['action' => __('Tickets: Bookmarks', 'fluent-support'), 'shortcut' => '⌘ + ⇧ + b'],
526 ['action' => __('Tickets: Toggle (Open, active, close, all)', 'fluent-support'), 'shortcut' => '⌘ + ⇧ + → / ←'],
527 ['action' => __('Ticket Reply: Reply', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + r'],
528 ['action' => __('Ticket Reply: Personal note', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + n'],
529 ['action' => __('Ticket Reply: Merge', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + m'],
530 ['action' => __('Ticket Reply: Bookmarks', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + b'],
531 ['action' => __('Ticket Reply: Refresh', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + q'],
532 ['action' => __('Ticket Reply: Next ticket', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + ↓'],
533 ['action' => __('Ticket Reply: Previous ticket', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + ↑'],
534 ['action' => __('Ticket Reply: Close ticket', 'fluent-support'), 'shortcut' => '⌘ + ⌥ + x'],
535 ];
536
537 $winShortcuts = [
538 ['action' => __('Tickets: Advanced filter toggle', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + f'],
539 ['action' => __('Tickets: Refresh', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + q'],
540 ['action' => __('Tickets: Create Ticket', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + n'],
541 ['action' => __('Tickets: Reset filter', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + r'],
542 ['action' => __('Tickets: All Tickets', 'fluent-support'), 'shortcut' => 'Ctrl + Shift + a'],
543 ['action' => __('Tickets: My Tickets', 'fluent-support'), 'shortcut' => 'Ctrl + Shift + m'],
544 ['action' => __('Tickets: Unassigned', 'fluent-support'), 'shortcut' => 'Ctrl + Shift + u'],
545 ['action' => __('Tickets: Waiting for reply', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + w'],
546 ['action' => __('Tickets: Bookmarks', 'fluent-support'), 'shortcut' => 'Ctrl + Shift + b'],
547 ['action' => __('Tickets: Toggle (Open, active, close, all)', 'fluent-support'), 'shortcut' => 'Ctrl + Shift + → / ←'],
548 ['action' => __('Ticket Reply: Reply', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + r'],
549 ['action' => __('Ticket Reply: Personal note', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + n'],
550 ['action' => __('Ticket Reply: Merge', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + m'],
551 ['action' => __('Ticket Reply: Bookmarks', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + b'],
552 ['action' => __('Ticket Reply: Refresh', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + q'],
553 ['action' => __('Ticket Reply: Next ticket', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + ↓'],
554 ['action' => __('Ticket Reply: Previous ticket', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + ↑'],
555 ['action' => __('Ticket Reply: Close ticket', 'fluent-support'), 'shortcut' => 'Ctrl + Alt + x'],
556 ];
557
558 return [
559 'title' => __('Keyboard Shortcuts', 'fluent-support'),
560 'mac_label' => __('macOS', 'fluent-support'),
561 'win_label' => __('Windows', 'fluent-support'),
562 'action_label' => $actionLabel,
563 'shortcut_label'=> $shortcutLabel,
564 'mac_shortcuts' => $macShortcuts,
565 'win_shortcuts' => $winShortcuts,
566 ];
567 }
568
569 public function saveBoxEmailSettings($box, $emailKey, $settings)
570 {
571 return $box->saveMeta('_email_' . $emailKey, $settings);
572 }
573
574 /**
575 * getBoxEmailSettings method will reply the email settings
576 * @param $box
577 * @param $emailKey
578 * @return array|false
579 */
580 public function getBoxEmailSettings($box, $emailKey)
581 {
582 if (!$box) {
583 return false;
584 }
585
586 #ticket_closed_by_agent_email_to_customer 2 times, is it wrong or right!!!
587 $strictSubjectKeys = apply_filters('fluent_support/strict_subjects', [
588 'ticket_replied_by_agent_email_to_customer',
589 'ticket_closed_by_agent_email_to_customer',
590 'ticket_created_email_to_customer'
591 ]);
592
593 $settingsDefaults = [
594 'ticket_created_email_to_customer' => [
595 'key' => 'ticket_created_email_to_customer',
596 'title' => __('Ticket Created (To Customer)', 'fluent-support'),
597 'description' => __('This email will be sent when a customer submit a support ticket', 'fluent-support'),
598 'email_subject' => 'Re: {{ticket.title}} #{{ticket.public_id}}',
599 'default_status' => 'no',
600 'send_attachments'=> 'no'
601 ],
602 'ticket_replied_by_agent_email_to_customer' => [
603 'key' => 'ticket_replied_by_agent_email_to_customer',
604 'title' => __('Replied by Agent (To Customer)', 'fluent-support'),
605 'description' => __('This email will be sent when an agent reply to a ticket', 'fluent-support'),
606 'email_subject' => 'Re: {{ticket.title}} #{{ticket.public_id}}',
607 'default_status' => 'yes',
608 'send_attachments'=> 'no'
609 ],
610 'ticket_closed_by_agent_email_to_customer' => [
611 'key' => 'ticket_closed_by_agent_email_to_customer',
612 'title' => __('Ticket Closed by Agent (To Customer)', 'fluent-support'),
613 'description' => __('This email will be sent when an agent close a ticket', 'fluent-support'),
614 'email_subject' => 'Re: {{ticket.title}} #{{ticket.public_id}}',
615 'default_status' => 'no',
616 'send_attachments'=> 'no'
617 ],
618 'ticket_created_email_to_admin' => [
619 'key' => 'ticket_created_email_to_admin',
620 'title' => __('Ticket Created (To Admin)', 'fluent-support'),
621 'description' => __('This email will be sent when the business when a new ticket has been submitted', 'fluent-support'),
622 'email_subject' => 'New Ticket: {{ticket.title}} #{{ticket.public_id}}',
623 'default_status' => 'yes',
624 'send_attachments'=> 'no'
625 ],
626 'ticket_replied_by_customer_email_to_admin' => [
627 'key' => 'ticket_replied_by_customer_email_to_admin',
628 'title' => __('Replied by Customer (To Agent/Admin)', 'fluent-support'),
629 'description' => __('This email will be sent to Assigned Agent or Admin when a customer reply to a ticket', 'fluent-support'),
630 'email_subject' => 'New Response: {{ticket.title}} #{{ticket.public_id}}',
631 'default_status' => 'yes',
632 'send_attachments'=> 'no'
633 ],
634 'ticket_agent_on_change' => [
635 'key' => 'ticket_agent_on_change',
636 'title' => __('Ticket Agent Change (To Agent)', 'fluent-support'),
637 'description' => __('This email will be sent to newly assigned agent', 'fluent-support'),
638 'email_subject' => 'Ticket Agent Change: {{ticket.title}} #{{ticket.public_id}}',
639 'default_status' => 'yes',
640 'send_attachments'=> 'no'
641 ],
642 'ticket_created_by_agent_email_to_customer' => [
643 'key' => 'ticket_created_by_agent_email_to_customer',
644 'title' => __('Ticket Logged by Agent (To Customer)', 'fluent-support'),
645 'description' => __('This email will be sent when an agent logs a ticket on behalf of a customer (e.g. from a phone call or email received outside the system)', 'fluent-support'),
646 'email_subject' => 'Re: {{ticket.title}} #{{ticket.public_id}}',
647 'default_status' => 'no',
648 'send_attachments'=> 'no'
649 ],
650 'ticket_created_by_agent_on_behalf_email_to_customer' => [
651 'key' => 'ticket_created_by_agent_on_behalf_email_to_customer',
652 'title' => __('Agent Outreach Ticket (To Customer)', 'fluent-support'),
653 'description' => __('This email will be sent when an agent creates a ticket using the "Agent Initiated" option to proactively reach out to a customer — includes the agent\'s message content', 'fluent-support'),
654 // Outreach reads like an email the agent wrote: their subject, their message.
655 'email_subject' => '{{ticket.title}}',
656 'default_status' => 'yes',
657 'send_attachments'=> 'no'
658 ]
659 ];
660
661 if (!isset($settingsDefaults[$emailKey])) {
662 return false;
663 }
664
665 $savedSettings = (array)$box->getMeta('_email_' . $emailKey, []);
666
667
668 if (!$savedSettings) {
669 $savedSettings = [
670 'key' => $settingsDefaults[$emailKey]['key'],
671 'title' => $settingsDefaults[$emailKey]['title'],
672 'email_subject' => $settingsDefaults[$emailKey]['email_subject'],
673 'email_body' => $this->getDefaultEmailBody($emailKey, $box->box_type),
674 'status' => $settingsDefaults[$emailKey]['default_status'],
675 'can_edit_subject' => (in_array($emailKey, $strictSubjectKeys) && $box->box_type == 'email') ? 'no' : 'yes',
676 'send_attachments' => $settingsDefaults[$emailKey]['send_attachments']
677 ];
678
679 if ($box->box_type == 'email' && in_array($emailKey, $strictSubjectKeys)) {
680 $savedSettings['email_subject'] = 'Re: {{ticket.title}}';
681 $savedSettings['can_edit_subject'] = 'no';
682 }
683
684 return $savedSettings;
685 }
686
687 $savedSettings['key'] = $settingsDefaults[$emailKey]['key'];
688 $savedSettings['title'] = $settingsDefaults[$emailKey]['title'];
689 $savedSettings['description'] = $settingsDefaults[$emailKey]['description'];
690
691 if ($box->box_type == 'email' && in_array($emailKey, $strictSubjectKeys)) {
692 $savedSettings['email_subject'] = 'Re: {{ticket.title}}';
693 $savedSettings['can_edit_subject'] = 'no';
694 }
695
696 if (empty($savedSettings['email_subject'])) {
697 $savedSettings['email_subject'] = $settingsDefaults[$emailKey]['email_subject'];
698 }
699
700 if (empty($savedSettings['status'])) {
701 $savedSettings['status'] = $settingsDefaults[$emailKey]['default_status'];
702 }
703
704 if (empty($savedSettings['email_body'])) {
705 $savedSettings['email_body'] = $this->getDefaultEmailBody($emailKey, $box->box_type);
706 }
707
708 if (empty($savedSettings['send_attachments'])) {
709 $savedSettings['send_attachments'] = $settingsDefaults[$emailKey]['send_attachments'];
710 }
711
712 return $savedSettings;
713 }
714
715 /**
716 * getDefaultEmailBody method will return html for email body
717 * @param $emailKey
718 * @param string $type
719 * @return string
720 */
721 private function getDefaultEmailBody($emailKey, $type = 'web')
722 {
723 if ($emailKey == 'ticket_created_email_to_customer') {
724 if ($type == 'web') {
725 return '<p>Hi <strong><em>{{customer.full_name}}</em>,</strong></p><p>Your request (<a href="{{ticket.public_url}}">#{{ticket.public_id}}</a>) has been received, and is being reviewed by our support staff.</p><p>To add additional comments, follow the link below:</p><h4><a href="{{ticket.public_url}}">View Ticket</a></h4><p>&nbsp;</p><p>or follow this link: {{ticket.public_url}}</p><hr /><p>{{business.name}}</p>';
726 } else {
727 return '<p>Hi <strong><em>{{customer.full_name}}</em>,</strong></p><p>Your request has been received, and is being reviewed by our support staff.</p><p>Our support staff will reply back to you soon</p>';
728 }
729 } else if ($emailKey == 'ticket_replied_by_agent_email_to_customer') {
730 if ($type == 'web') {
731 return '<p>Hi <strong><em>{{customer.full_name}}</em>,</strong></p><p>An agent just replied to your ticket "<strong>{{ticket.title}}</strong>" (<a href="{{ticket.public_url}}">#{{ticket.public_id}}</a>). To view the reply or add additional comments, click the button below:</p><h4><a href="{{ticket.public_url}}">View Ticket</a></h4><p>or follow this link: {{ticket.public_url}}</p><hr /><p>Regards,<br />{{business.name}}</p>';
732 } else {
733 // {{agent.signature}} is empty when the agent has none.
734 return '{{response.full_content}}{{agent.signature}}';
735 }
736 } else if ($emailKey == 'ticket_closed_by_agent_email_to_customer') {
737 if ($type == 'web') {
738 return '<p>Hi <strong><em>{{customer.full_name}},</strong></p><p>Your ticket - {{ticket.title}}</p><p>We hope that the ticket was resolved to your satisfaction. If you feel that the ticket should not be closed or if the ticket has not been resolved, please reopen the ticket (<a href="{{ticket.public_url}}">#{{ticket.public_id}}</a>)</p><p>Regards,<br />{{business.name}}</p>';
739 } else {
740 return '<p>Hi <strong><em>{{customer.full_name}},</strong></p><p>Your ticket - {{ticket.title}}</p><p>We hope that the ticket was resolved to your satisfaction. If you feel that the ticket should not be closed or if the ticket has not been resolved, please feel free to reply back.<p>Regards,<br />{{business.name}}</p>';
741 }
742 } else if ($emailKey == 'ticket_created_email_to_admin') {
743 return '<p>A new ticket (<a href="{{ticket.admin_url}}">{{ticket.title}}</a>) has been submitted by {{customer.full_name}}</p><h4>Ticket Body</h4><p>{{ticket.content}}</p><p><b><a href="{{ticket.admin_url}}">View Ticket</a></b></p>';
744 } else if ($emailKey == 'ticket_replied_by_customer_email_to_admin') {
745 return '<p>A new response has been added to "<a href="{{ticket.admin_url}}">{{ticket.title}}</a>" by {{customer.full_name}}</p><h4>Response Body</h4><p>{{response.content}}</p><p><b><a href="{{ticket.admin_url}}">View Ticket</a></b></p>';
746 } else if($emailKey == 'ticket_agent_on_change') {
747 return '<p>Hi <strong><em>{{agent.full_name}}</em>,</strong></p><p>Ticket "<a href="{{ticket.admin_url}}">#{{ticket.public_id}}</a>" assigned to you.</p>';
748 } else if($emailKey == 'ticket_created_by_agent_email_to_customer') {
749 return '<p>Hi <strong><em>{{customer.full_name}}</em>,</strong></p><p>{{agent.full_name}} created a ticket on behalf of you, you can check it <a href="{{ticket.public_url}}">here</a></p>.';
750 } else if($emailKey == 'ticket_created_by_agent_on_behalf_email_to_customer') {
751 if ($type == 'web') {
752 // Web mailboxes take replies in the portal, not by email.
753 return '{{response.full_content}}{{agent.signature}}<p><a href="{{ticket.public_url}}">Reply to this conversation</a></p>';
754 }
755 return '{{response.full_content}}{{agent.signature}}';
756 }
757
758 return '';
759 }
760 }
761