PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.1.2
Fluent Support – Helpdesk & Customer Support Ticket System v2.1.2
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
← All changes | app/Http/Controllers/TicketController.php +43 -303 2.2.12.1.2 View file →
@@ -17,13 +17,11 @@
17 17 use FluentSupport\App\Services\ProfileInfoService;
18 18 use FluentSupport\App\Services\TicketHelper;
19 19 use FluentSupport\App\Services\TicketQueryService;
20 20 use FluentSupport\App\Modules\PermissionManager;
21 -use FluentSupport\App\Services\Tickets\AgentTicketAccess;
22 21 use FluentSupport\App\Services\Tickets\ResponseService;
23 22 use FluentSupport\App\Models\AgentGroup;
24 23 use FluentSupport\App\Services\Tickets\TicketService;
25 -use FluentSupport\App\Services\Integrations\FluentBooking\FluentBookingService;
26 24
27 25 /**
28 26 * TicketController class for REST API related to ticket
29 27 * This class is responsible for getting / inserting/ modifying data for all request related to ticket
@@ -247,9 +245,9 @@
247 245 $ticketWith = is_array($ticketWith) ? map_deep($ticketWith, 'sanitize_text_field') : null;
248 246
249 247 if (!$ticketWith) {
250 248 $ticketWith = ['customer', 'agent', 'product', 'mailbox', 'tags', 'attachments' => function ($q) {
251 - $q->where('status', 'active');
249 + $q->whereIn('status', ['active', 'inline']);
252 250 }];
253 251 }
254 252
255 253 //Get ticket by id
@@ -256,15 +254,9 @@
256 254 $ticket = Ticket::with($ticketWith)->findOrFail($ticket_id);
257 255
258 256 //Eager load responses with their nested relations to avoid N+1 queries
259 257 $ticket->load(['responses' => function ($q) {
260 - $q->with([
261 - 'person',
262 - 'ccinfo',
263 - 'attachments' => function ($q) {
264 - $q->where('status', 'active');
265 - }
266 - ]);
258 + $q->with('person', 'attachments', 'ccinfo');
267 259 }]);
268 260
269 261 //Check if ticket is in a restricted mailbox
270 262 $restrictedBusinessBoxes = PermissionManager::getRestrictedMailboxIds();
@@ -315,40 +307,11 @@
315 307 }
316 308 }
317 309 }
318 310
319 - $contents = ['ticket' => $ticket->content];
320 - foreach ($ticket->responses as $response) {
321 - $contents['response_' . $response->id] = $response->content;
322 - }
323 -
324 - $contents = Helper::refreshSignedAttachmentUrlsInContents($contents, $ticket->id);
325 - $ticket->content = $contents['ticket'];
326 -
327 311 //Format response content
328 312 foreach ($ticket->responses as $response) {
329 - $responseKey = 'response_' . $response->id;
330 - if (isset($contents[$responseKey])) {
331 - $response->content = $contents[$responseKey];
332 - }
333 -
334 - $responseContent = apply_filters(
335 - 'fluent_support/response_content_before_render',
336 - $response->content,
337 - $response,
338 - $ticket
339 - );
340 -
341 - $responseContent = links_add_target(make_clickable(wpautop($responseContent, false)));
342 -
343 -
344 - $response->content = apply_filters(
345 - 'fluent_support/response_content_after_render',
346 - $responseContent,
347 - $response,
348 - $ticket
349 - );
350 -
313 + $response->content = links_add_target(make_clickable(wpautop($response->content, false)));
351 314 if (!empty($response->ccinfo)) {
352 315 $val = Helper::safeUnserialize($response->ccinfo->value);
353 316 if (isset($val['cc_email']) && !empty($val['cc_email'])) {
354 317 $response->cc_info = $val['cc_email'];
@@ -359,22 +322,10 @@
359 322 $response->cc_info = '';
360 323 }
361 324 }
362 325
363 - $ticketContent = apply_filters(
364 - 'fluent_support/ticket_content_before_render',
365 - $ticket->content,
366 - $ticket
367 - );
326 + $ticket->content = links_add_target(make_clickable(wpautop($ticket->content, false)));
368 327
369 - $ticketContent = links_add_target(make_clickable(wpautop($ticketContent, false)));
370 -
371 - $ticket->content = apply_filters(
372 - 'fluent_support/ticket_content_after_render',
373 - $ticketContent,
374 - $ticket
375 - );
376 -
377 328 //Get last activity by agent
378 329 $ticket->live_activity = TicketHelper::getActivity($ticket->id, $agent->id);
379 330
380 331 //Get all carbon copy customer
@@ -422,87 +373,8 @@
422 373 ]);
423 374 }
424 375 }
425 376
426 - public function getMentionableAgents(Request $request, $ticket_id)
427 - {
428 - try {
429 - $ticket = Ticket::findOrFail($ticket_id);
430 -
431 - if (in_array($ticket->mailbox_id, PermissionManager::getRestrictedMailboxIds())) {
432 - throw new \Exception(esc_html__('Ticket cannot be fetched due to restricted mailbox', 'fluent-support'));
433 - }
434 -
435 - $this->ensureCanAccessTicket($ticket);
436 -
437 - $search = trim($request->getSafe('search', 'sanitize_text_field', ''));
438 - $limit = min(max(absint($request->getSafe('limit', 'intval', 20)), 1), 50);
439 -
440 - return [
441 - 'agents' => $this->getMentionableAgentList($ticket, $search, $limit)
442 - ];
443 - } catch (\Exception $e) {
444 - return $this->sendError([
445 - 'message' => Helper::getSafeErrorMessage($e)
446 - ]);
447 - }
448 - }
449 -
450 - protected function getMentionableAgentList($ticket, $search, $limit)
451 - {
452 - $allAgents = Agent::select(['id', 'first_name', 'last_name', 'email', 'user_id'])
453 - ->mentionBy($search)
454 - ->orderBy('first_name')
455 - ->orderBy('last_name')
456 - ->get();
457 -
458 - if ($allAgents->isEmpty()) {
459 - return [];
460 - }
461 -
462 - $restrictions = $this->getAgentRestrictionsMap($allAgents->pluck('id')->all());
463 - $ticketAccess = new AgentTicketAccess();
464 - $results = [];
465 -
466 - foreach ($allAgents as $agent) {
467 - if (!$ticketAccess->canAccess($agent, $ticket, $restrictions[$agent->id] ?? [])) {
468 - continue;
469 - }
470 -
471 - $results[] = [
472 - 'id' => strval($agent->id),
473 - 'first_name' => $agent->first_name,
474 - 'last_name' => $agent->last_name,
475 - 'email' => $agent->email,
476 - ];
477 -
478 - if (count($results) >= $limit) {
479 - break;
480 - }
481 - }
482 -
483 - return $results;
484 - }
485 -
486 - protected function getAgentRestrictionsMap(array $agentIds)
487 - {
488 - if (!$agentIds) {
489 - return [];
490 - }
491 -
492 - $metas = Meta::where('object_type', 'person_meta')
493 - ->where('key', 'agent_restrictions')
494 - ->whereIn('object_id', $agentIds)
495 - ->get();
496 -
497 - $restrictions = [];
498 - foreach ($metas as $meta) {
499 - $restrictions[$meta->object_id] = Helper::safeUnserialize($meta->value) ?: [];
500 - }
501 -
502 - return $restrictions;
503 - }
504 -
505 377 /**
506 378 * createResponse method will create response by agent for the ticket
507 379 * @param Request $request
508 380 * @param Ticket $ticket
@@ -536,14 +408,8 @@
536 408 $this->ensureCanAccessTicket($ticket);
537 409
538 410 $responseData = (new ResponseService())->createResponse($data, $agent, $ticket);
539 411
540 - $responseData['response']->content = Helper::refreshSignedAttachmentUrls($responseData['response']->content, $ticket->id);
541 - $responseData['response']->load([
542 - 'attachments' => function ($q) {
543 - $q->where('status', 'active');
544 - }
545 - ]);
546 412 $responseData['response']->content = wp_specialchars_decode(wpautop($responseData['response']->content, false));
547 413
548 414 return [
549 415 'message' => __('Response has been added', 'fluent-support'),
@@ -557,115 +423,8 @@
557 423 ]);
558 424 }
559 425 }
560 426
561 - public function getFluentBookingEventTypes()
562 - {
563 - try {
564 - // All FluentBooking endpoints require manage permission; view-only agents cannot call a meeting.
565 - $this->ensureCanManageTickets();
566 -
567 - $service = new FluentBookingService();
568 - $eventTypes = $service->getEventTypes();
569 -
570 - return [
571 - 'status' => $service->getStatus($eventTypes),
572 - 'event_types' => $eventTypes
573 - ];
574 - } catch (\Exception $e) {
575 - return $this->sendError([
576 - 'message' => Helper::getSafeErrorMessage($e)
577 - ]);
578 - }
579 - }
580 -
581 - public function createFluentBookingLink(Request $request, $ticket_id)
582 - {
583 - try {
584 - // All FluentBooking endpoints require manage permission; view-only agents cannot call a meeting.
585 - $this->ensureCanManageTickets();
586 -
587 - $ticket = Ticket::with('customer')->findOrFail($ticket_id);
588 -
589 - // Enforces per-ticket visibility (e.g. own-tickets-only agents cannot access unassigned tickets).
590 - $this->ensureCanAccessTicket($ticket);
591 -
592 - $eventId = $request->getSafe('event_type_id', 'intval');
593 -
594 - if (!$eventId) {
595 - throw new \Exception(esc_html__('Please select a FluentBooking event type.', 'fluent-support'));
596 - }
597 -
598 - return (new FluentBookingService())->createBookingLink(
599 - $ticket,
600 - $eventId,
601 - $request->getSafe('message', 'wp_kses_post'),
602 - $request->get('selected_slots', []),
603 - $request->getSafe('timezone', 'sanitize_text_field', '')
604 - );
605 - } catch (\Exception $e) {
606 - return $this->sendError([
607 - 'message' => Helper::getSafeErrorMessage($e)
608 - ]);
609 - }
610 - }
611 -
612 - public function getFluentBookingAvailability(Request $request, $ticket_id)
613 - {
614 - try {
615 - // All FluentBooking endpoints require manage permission; view-only agents cannot call a meeting.
616 - $this->ensureCanManageTickets();
617 -
618 - $ticket = Ticket::with('customer')->findOrFail($ticket_id);
619 -
620 - // Enforces per-ticket visibility (e.g. own-tickets-only agents cannot access unassigned tickets).
621 - $this->ensureCanAccessTicket($ticket);
622 -
623 - $eventId = $request->getSafe('event_type_id', 'intval');
624 -
625 - if (!$eventId) {
626 - throw new \Exception(esc_html__('Please select a FluentBooking event type.', 'fluent-support'));
627 - }
628 -
629 - return [
630 - 'availability' => (new FluentBookingService())->getAvailabilitySlots(
631 - $eventId,
632 - $request->getSafe('range', 'sanitize_key', 'next_3_days'),
633 - $request->getSafe('timezone', 'sanitize_text_field'),
634 - $request->getSafe('duration', 'intval'),
635 - $ticket,
636 - $request->get('selected_dates', []),
637 - $request->getSafe('calendar_month', 'sanitize_text_field', '')
638 - )
639 - ];
640 - } catch (\Exception $e) {
641 - return $this->sendError([
642 - 'message' => Helper::getSafeErrorMessage($e)
643 - ]);
644 - }
645 - }
646 -
647 - public function getFluentBookingMeetings($ticket_id)
648 - {
649 - try {
650 - // All FluentBooking endpoints require manage permission; view-only agents cannot call a meeting.
651 - $this->ensureCanManageTickets();
652 -
653 - $ticket = Ticket::with('customer')->findOrFail($ticket_id);
654 -
655 - // Enforces per-ticket visibility (e.g. own-tickets-only agents cannot access unassigned tickets).
656 - $this->ensureCanAccessTicket($ticket);
657 -
658 - return [
659 - 'meetings' => (new FluentBookingService())->getTicketMeetings($ticket)
660 - ];
661 - } catch (\Exception $e) {
662 - return $this->sendError([
663 - 'message' => Helper::getSafeErrorMessage($e)
664 - ]);
665 - }
666 - }
667 -
668 427 /**
669 428 * createDraft method will create draft by agent for the ticket
670 429 * @param Request $request
671 430 * @param Ticket $ticket
@@ -885,14 +644,13 @@
885 644 if ($propName == 'product_id') {
886 645 $ticket->load('product');
887 646 $updateData['product'] = $ticket->product;
888 647 } else if ($propName == 'agent_id') {
889 - $previousAgentId = (int) $prevValue;
890 648 $ticket->load('agent');
891 649 $updateData['agent'] = $ticket->agent;
892 650 $updateData['assigner'] = (new TicketService())->onAgentChange($ticket, $assigner);
893 651 if ($prevValue != $ticket->{$propName}) {
894 - do_action('fluent_support/agent_assigned_to_ticket', $ticket->agent, $ticket, $assigner, $previousAgentId);
652 + do_action('fluent_support/agent_assigned_to_ticket', $ticket->agent, $ticket, $assigner);
895 653 }
896 654 }
897 655
898 656 $message = sprintf(
@@ -1032,9 +790,8 @@
1032 790 $assignedCount = 0;
1033 791 $skippedCount = 0;
1034 792
1035 793 $tickets->each(function ($ticket) use ($assignAgent, $agent, &$assignedCount, &$skippedCount) {
1036 - $previousAgentId = (int) $ticket->agent_id;
1037 794 $restrictions = $assignAgent->getMeta('agent_restrictions', []);
1038 795
1039 796 //Skip ticket if mailbox is restricted for the agent
1040 797 if (!empty($restrictions) && in_array($ticket->mailbox_id, $restrictions['restrictedBusinessBoxes'])) {
@@ -1045,9 +802,9 @@
1045 802 $ticket->agent_id = $assignAgent->id;
1046 803 $ticket->save();
1047 804 $assignedCount++;
1048 805
1049 - do_action('fluent_support/agent_assigned_to_ticket', $assignAgent, $ticket, $agent, $previousAgentId);
806 + do_action('fluent_support/agent_assigned_to_ticket', $assignAgent, $ticket, $agent);
1050 807 });
1051 808
1052 809 $assignedMessage = sprintf(
1053 810 /* translators: %1$d is the number of tickets assigned, %2$s is the agent's name. */
@@ -1084,9 +841,8 @@
1084 841 $skippedCount = 0;
1085 842 $currentCounts = [];
1086 843
1087 844 foreach ($tickets as $ticket) {
1088 - $previousAgentId = (int) $ticket->agent_id;
1089 845 $selectedAgent = $group->getLeastLoadedAgent(
1090 846 $ticket->mailbox_id, $currentCounts
1091 847 );
1092 848
@@ -1100,9 +856,9 @@
1100 856 $assignedCount++;
1101 857 $currentCounts[$selectedAgent->id]++;
1102 858
1103 859 as_enqueue_async_action('fluent_support/async_agent_assigned_to_ticket', [
1104 - $selectedAgent->id, $ticket->id, $agent->id, $previousAgentId
860 + $selectedAgent->id, $ticket->id, $agent->id
1105 861 ], 'fluent-support');
1106 862 }
1107 863
1108 864 return [
@@ -1275,11 +1031,9 @@
1275 1031 public function deleteResponse($ticket_id, $response_id)
1276 1032 {
1277 1033 try {
1278 1034 $ticket = Ticket::findOrFail($ticket_id);
1279 - $response = Conversation::where('id', $response_id)
1280 - ->where('ticket_id', $ticket_id)
1281 - ->firstOrFail();
1035 + $response = Conversation::findOrFail($response_id);
1282 1036 $agent = Helper::getAgentByUserId();
1283 1037
1284 1038 if (!PermissionManager::currentUserCan('fst_delete_tickets') && $ticket->agent_id !== $agent->id) {
1285 1039 throw new \Exception(
@@ -1311,11 +1065,9 @@
1311 1065 public function updateResponse(TicketResponseRequest $request, $ticket_id, $response_id)
1312 1066 {
1313 1067 try {
1314 1068 $ticket = Ticket::findOrFail($ticket_id);
1315 - $response = Conversation::where('id', $response_id)
1316 - ->where('ticket_id', $ticket_id)
1317 - ->firstOrFail();
1069 + $response = Conversation::findOrFail($response_id);
1318 1070 $agent = Helper::getAgentByUserId();
1319 1071
1320 1072 if (!PermissionManager::currentUserCan('fst_manage_other_tickets') && $ticket->agent_id !== $agent->id) {
1321 1073 throw new \Exception(
@@ -1322,23 +1074,22 @@
1322 1074 esc_html__('Sorry, you do not have permission to update this response.', 'fluent-support')
1323 1075 );
1324 1076 }
1325 1077
1326 - $content = wp_unslash(wp_kses_post($request->getSafe('content', 'wp_kses_post')));
1327 - $response->content = $content;
1078 + $response->content = wp_unslash(wp_kses_post($request->getSafe('content', 'wp_kses_post')));
1328 1079
1329 - if ($response->conversation_type == 'draft_response' && $response->person_id != $agent->id && PermissionManager::currentUserCan('fst_approve_draft_reply')) {
1330 - $response = $this->approveDraftConversation($ticket, $response, $agent, $content);
1331 - } else if ($response->conversation_type == 'draft_response' && $response->person_id != $agent->id) {
1080 + //If updating a draft response by someone other than the author, check approval permission
1081 + if ($response->conversation_type == 'draft_response' && $response->person_id != $agent->id) {
1332 1082 if (!PermissionManager::currentUserCan('fst_approve_draft_reply')) {
1333 1083 throw new \Exception(
1334 1084 esc_html__('Sorry, You do not have permission to approve this draft response', 'fluent-support')
1335 1085 );
1336 1086 }
1337 - } else {
1338 - $response->save();
1087 + $response->conversation_type = 'response';
1339 1088 }
1340 1089
1090 + $response->save();
1091 +
1341 1092 return [
1342 1093 'message' => __('Selected response has been updated', 'fluent-support'),
1343 1094 'response' => $response
1344 1095 ];
@@ -1366,15 +1117,34 @@
1366 1117 ->firstOrFail();
1367 1118
1368 1119 $person = Helper::getAgentByUserId();
1369 1120
1370 - $response = $this->approveDraftConversation(
1371 - $ticket,
1372 - $response,
1373 - $person,
1374 - wp_unslash(wp_kses_post($request->getSafe('content', 'wp_kses_post')))
1375 - );
1121 + $content = wp_unslash(wp_kses_post($request->getSafe('content', 'wp_kses_post')));
1122 + $resetWaitingSince = apply_filters('fluent_support/reset_waiting_since', true, $content);
1376 1123
1124 + $response->conversation_type = 'response';
1125 + $response->created_at = current_time('mysql');
1126 + $response->save();
1127 +
1128 + if ($person->person_type == 'agent' && $ticket->status == 'new') {
1129 + $ticket->status = 'active';
1130 + if ($ticket->created_at) {
1131 + $ticket->first_response_time = strtotime(current_time('mysql')) - strtotime($ticket->created_at);
1132 + } else {
1133 + $ticket->first_response_time = 300;
1134 + }
1135 + }
1136 +
1137 + if ($resetWaitingSince) {
1138 + $ticket->last_agent_response = current_time('mysql');
1139 + $ticket->waiting_since = current_time('mysql');
1140 + }
1141 +
1142 + $ticket->response_count += 1;
1143 + $ticket->save();
1144 +
1145 + do_action('fluent_support/response_added_by_' . $person->person_type, $response, $ticket, $person);
1146 +
1377 1147 return [
1378 1148 'message' => __('Draft response has been successfully approved.', 'fluent-support'),
1379 1149 'response' => $response,
1380 1150 ];
@@ -1384,39 +1154,8 @@
1384 1154 ]);
1385 1155 }
1386 1156 }
1387 1157
1388 - protected function approveDraftConversation($ticket, $response, $person, $content)
1389 - {
1390 - $resetWaitingSince = apply_filters('fluent_support/reset_waiting_since', true, $content);
1391 -
1392 - $response->content = $content;
1393 - $response->conversation_type = 'response';
1394 - $response->created_at = current_time('mysql');
1395 - $response->save();
1396 -
1397 - if ($person->person_type == 'agent' && $ticket->status == 'new') {
1398 - $ticket->status = 'active';
1399 - if ($ticket->created_at) {
1400 - $ticket->first_response_time = strtotime(current_time('mysql')) - strtotime($ticket->created_at);
1401 - } else {
1402 - $ticket->first_response_time = 300;
1403 - }
1404 - }
1405 -
1406 - if ($resetWaitingSince) {
1407 - $ticket->last_agent_response = current_time('mysql');
1408 - $ticket->waiting_since = current_time('mysql');
1409 - }
1410 -
1411 - $ticket->response_count += 1;
1412 - $ticket->save();
1413 -
1414 - do_action('fluent_support/response_added_by_' . $person->person_type, $response, $ticket, $person);
1415 -
1416 - return $response;
1417 - }
1418 -
1419 1158 /**
1420 1159 * getLiveActivity method will return the activity in a ticket by agents
1421 1160 * @param Request $request
1422 1161 * @param $ticket_id
@@ -1498,11 +1237,11 @@
1498 1237 * This method will get ticket id and customer id as parameter, it will replace existing customer id with new
1499 1238 * @param Request $request
1500 1239 * @return array
1501 1240 */
1502 - public function changeTicketCustomer(Request $request, $ticket_id)
1241 + public function changeTicketCustomer(Request $request)
1503 1242 {
1504 - $ticketId = (int) $ticket_id;
1243 + $ticketId = $request->getSafe('ticket_id', 'intval');
1505 1244 $newCustomerId = $request->getSafe('customer', 'intval');
1506 1245
1507 1246 if (!$newCustomerId) {
1508 1247 return $this->sendError(__('Invalid customer selected.', 'fluent-support'));
@@ -1664,4 +1403,5 @@
1664 1403 ]);
1665 1404 }
1666 1405 }
1667 1406 }
1407 +