PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.2.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.2.0
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
fluent-support / app / Hooks / Handlers / InternalNotificationHandler.php

InternalNotificationHandler.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.2.0, at app/Hooks/Handlers/InternalNotificationHandler.php

312 lines 11.5 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\Hooks\Handlers;
4
5 use FluentSupport\App\Models\Meta;
6 use FluentSupport\App\Services\Helper;
7 use FluentSupport\App\Services\Notifications\MentionParser;
8 use FluentSupport\App\Services\Notifications\NotificationEventMap;
9 use FluentSupport\App\Services\Notifications\NotificationService;
10 use FluentSupport\App\Services\Notifications\RecipientResolver;
11 use FluentSupport\App\Services\Tickets\AgentTicketAccess;
12
13 class InternalNotificationHandler
14 {
15 public function init()
16 {
17 add_action('fluent_support/response_added_by_agent', [$this, 'handleAgentReply'], 20, 3);
18 add_action('fluent_support/response_added_by_agent', [$this, 'handleAgentMention'], 20, 3);
19 add_action('fluent_support/note_added_by_agent', [$this, 'handleAgentMention'], 20, 3);
20 add_action('fluent_support/agent_assigned_to_ticket', [$this, 'handleAgentAssignment'], 15, 4);
21 add_action('fluent_support/response_added_by_customer', [$this, 'handleCustomerReply'], 20, 3);
22 add_action('fluent_support/ticket_closed', [$this, 'handleTicketClosed'], 15, 2);
23 add_action('fluent_support/ticket_reopen', [$this, 'handleTicketReopened'], 15, 2);
24 add_action('fluent_support/workflow_triggered', [$this, 'handleWorkflowTriggered'], 15, 4);
25 }
26
27 public function handleAgentReply($response, $ticket, $person)
28 {
29 $recipientResolver = new RecipientResolver();
30 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
31
32 if (!$assignedAgent) {
33 return;
34 }
35
36 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
37
38 if (!$recipientIds) {
39 return;
40 }
41
42 (new NotificationService())->createNotification([
43 'actor_id' => $person->id,
44 'ticket_id' => $ticket->id,
45 'conversation_id' => $response->id,
46 'event_type' => NotificationEventMap::AGENT_REPLIED,
47 'channel' => 'web',
48 'payload' => [
49 'ticket_title' => $ticket->title,
50 'ticket_priority' => $ticket->priority,
51 'agent' => $this->formatPerson($person),
52 'conversation_type' => $response->conversation_type,
53 'content_preview' => $this->makeContentPreview($response->content)
54 ]
55 ], $recipientIds);
56 }
57
58 public function handleAgentMention($response, $ticket, $person)
59 {
60 $agentIds = (new MentionParser())->extractMentionIds($response->content);
61
62 if (!$agentIds) {
63 return;
64 }
65
66 $recipientResolver = new RecipientResolver();
67 $recipients = $recipientResolver->resolveMentionedAgents($agentIds);
68 $recipients = $this->filterTicketAccessibleAgents($recipients, $ticket);
69 $recipientIds = $recipientResolver->extractRecipientPersonIds($recipients);
70
71 if (!$recipientIds) {
72 return;
73 }
74
75 $mentionedNames = array_values(array_map(
76 fn($a) => trim("{$a->first_name} {$a->last_name}"),
77 $recipients
78 ));
79
80 (new NotificationService())->createNotification([
81 'actor_id' => $person->id,
82 'ticket_id' => $ticket->id,
83 'conversation_id' => $response->id,
84 'event_type' => NotificationEventMap::AGENT_MENTIONED,
85 'channel' => 'web',
86 'payload' => [
87 'ticket_title' => $ticket->title,
88 'conversation_type' => $response->conversation_type,
89 'mentioned_handles' => $mentionedNames,
90 'content_preview' => $this->makeContentPreview($response->content)
91 ]
92 ], $recipientIds);
93 }
94
95 public function handleAgentAssignment($assignedAgent, $ticket, $assigner, $previousAgentId = null)
96 {
97 if (!$assignedAgent || empty($assignedAgent->id)) {
98 return;
99 }
100
101 $assignerId = (!empty($assigner) && !empty($assigner->id)) ? (int) $assigner->id : null;
102 $recipientIds = (new RecipientResolver())->extractRecipientPersonIds([$assignedAgent]);
103
104 if (!$recipientIds) {
105 return;
106 }
107
108 $eventType = $this->hasPreviousAssignment($previousAgentId)
109 ? NotificationEventMap::TICKET_REASSIGNED
110 : NotificationEventMap::TICKET_ASSIGNED;
111
112 (new NotificationService())->createNotification([
113 'actor_id' => $assignerId,
114 'ticket_id' => $ticket->id,
115 'event_type' => $eventType,
116 'channel' => 'web',
117 'payload' => [
118 'ticket_title' => $ticket->title,
119 'ticket_priority' => $ticket->priority,
120 'assignment_type' => $eventType === NotificationEventMap::TICKET_REASSIGNED ? 'reassignment' : 'assignment',
121 'assigned_agent' => $this->formatPerson($assignedAgent),
122 'assigner' => $this->formatPerson($assigner)
123 ]
124 ], $recipientIds);
125 }
126
127 public function handleCustomerReply($response, $ticket, $person)
128 {
129 $recipientResolver = new RecipientResolver();
130 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
131
132 if (!$assignedAgent) {
133 return;
134 }
135
136 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
137
138 if (!$recipientIds) {
139 return;
140 }
141
142 (new NotificationService())->createNotification([
143 'actor_id' => $person->id,
144 'ticket_id' => $ticket->id,
145 'conversation_id' => $response->id,
146 'event_type' => NotificationEventMap::CUSTOMER_REPLIED,
147 'channel' => 'web',
148 'payload' => [
149 'ticket_title' => $ticket->title,
150 'ticket_priority' => $ticket->priority,
151 'customer' => $this->formatPerson($person),
152 'conversation_type' => $response->conversation_type,
153 'content_preview' => $this->makeContentPreview($response->content)
154 ]
155 ], $recipientIds);
156 }
157
158 public function handleTicketClosed($ticket, $person)
159 {
160 $this->createStatusNotification($ticket, $person, NotificationEventMap::TICKET_CLOSED);
161 }
162
163 public function handleTicketReopened($ticket, $person)
164 {
165 $this->createStatusNotification($ticket, $person, NotificationEventMap::TICKET_REOPENED);
166 }
167
168 public function handleWorkflowTriggered($workflow, $ticket, $person = null, $context = [])
169 {
170 if (!$workflow || !$ticket || empty($ticket->id)) {
171 return;
172 }
173
174 $actorId = (!empty($person) && !empty($person->id)) ? (int) $person->id : null;
175 $recipientResolver = new RecipientResolver();
176 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
177
178 if (!$assignedAgent) {
179 return;
180 }
181
182 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
183
184 if (!$recipientIds) {
185 return;
186 }
187
188 $context = is_array($context) ? $context : [];
189
190 (new NotificationService())->createNotification([
191 'actor_id' => $actorId,
192 'ticket_id' => $ticket->id,
193 'event_type' => NotificationEventMap::WORKFLOW_TRIGGERED,
194 'channel' => 'web',
195 'payload' => [
196 'ticket_title' => $ticket->title,
197 'ticket_priority'=> $ticket->priority,
198 'workflow_id' => !empty($workflow->id) ? (int) $workflow->id : null,
199 'workflow_name' => !empty($workflow->title) ? $workflow->title : __('Workflow', 'fluent-support'),
200 'trigger_type' => !empty($workflow->trigger_type) ? $workflow->trigger_type : null,
201 'trigger_key' => !empty($workflow->trigger_key) ? $workflow->trigger_key : null,
202 'summary' => $this->makeWorkflowSummary($workflow, $context),
203 'actor' => $this->formatPerson($person),
204 'context' => [
205 'source' => !empty($context['source']) ? sanitize_text_field($context['source']) : null,
206 'event' => !empty($context['event']) ? sanitize_text_field($context['event']) : null
207 ]
208 ]
209 ], $recipientIds);
210 }
211
212 protected function createStatusNotification($ticket, $person, $eventType)
213 {
214 $actorId = (!empty($person) && !empty($person->id)) ? (int) $person->id : null;
215 $recipientResolver = new RecipientResolver();
216 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
217
218 if (!$assignedAgent) {
219 return;
220 }
221
222 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
223
224 if (!$recipientIds) {
225 return;
226 }
227
228 (new NotificationService())->createNotification([
229 'actor_id' => $actorId,
230 'ticket_id' => $ticket->id,
231 'event_type' => $eventType,
232 'channel' => 'web',
233 'payload' => [
234 'ticket_title' => $ticket->title,
235 'ticket_priority' => $ticket->priority,
236 'status' => $eventType === NotificationEventMap::TICKET_CLOSED ? 'closed' : 'reopened',
237 'actor' => $this->formatPerson($person)
238 ]
239 ], $recipientIds);
240 }
241
242 protected function hasPreviousAssignment($previousAgentId)
243 {
244 return (int) $previousAgentId > 0;
245 }
246
247 protected function filterTicketAccessibleAgents($agents, $ticket)
248 {
249 if (empty($agents)) {
250 return [];
251 }
252
253 $agentIds = [];
254 foreach ($agents as $agent) {
255 $agentIds[] = $agent->id;
256 }
257
258 $metas = Meta::where('object_type', 'person_meta')
259 ->where('key', 'agent_restrictions')
260 ->whereIn('object_id', $agentIds)
261 ->get();
262
263 $restrictionsMap = [];
264 foreach ($metas as $meta) {
265 $restrictionsMap[$meta->object_id] = Helper::safeUnserialize($meta->value) ?: [];
266 }
267
268 $ticketAccess = new AgentTicketAccess();
269 $accessibleAgents = [];
270
271 foreach ($agents as $agent) {
272 $restrictions = $restrictionsMap[$agent->id] ?? [];
273 if ($ticketAccess->canAccess($agent, $ticket, $restrictions)) {
274 $accessibleAgents[] = $agent;
275 }
276 }
277
278 return $accessibleAgents;
279 }
280
281 protected function makeContentPreview($content)
282 {
283 return wp_html_excerpt(wp_strip_all_tags((string) $content), 180, '...');
284 }
285
286 protected function makeWorkflowSummary($workflow, array $context = [])
287 {
288 $workflowName = !empty($workflow->title) ? $workflow->title : __('Workflow', 'fluent-support');
289 $source = !empty($context['source']) ? sanitize_text_field($context['source']) : 'workflow';
290
291 if ($source === 'manual') {
292 return sprintf(__('Manual workflow "%s" was run', 'fluent-support'), $workflowName);
293 }
294
295 return sprintf(__('Automation workflow "%s" was triggered', 'fluent-support'), $workflowName);
296 }
297
298 protected function formatPerson($person)
299 {
300 if (!$person) {
301 return null;
302 }
303
304 return [
305 'id' => (int) $person->id,
306 'person_type' => $person->person_type,
307 'name' => $person->full_name ?: $person->email,
308 'email' => $person->email
309 ];
310 }
311 }
312