PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.3.1
Fluent Support – Helpdesk & Customer Support Ticket System v2.3.1
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 / Hooks / Handlers / InternalNotificationHandler.php

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

322 lines 11.8 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 if ((int) $ticket->agent_id !== (int) $assignedAgent->id) {
102 return;
103 }
104
105 $accessibleAgents = $this->filterTicketAccessibleAgents([$assignedAgent], $ticket);
106
107 if (!$accessibleAgents) {
108 return;
109 }
110
111 $assignerId = (!empty($assigner) && !empty($assigner->id)) ? (int) $assigner->id : null;
112 $recipientIds = (new RecipientResolver())->extractRecipientPersonIds($accessibleAgents);
113
114 if (!$recipientIds) {
115 return;
116 }
117
118 $eventType = $this->hasPreviousAssignment($previousAgentId)
119 ? NotificationEventMap::TICKET_REASSIGNED
120 : NotificationEventMap::TICKET_ASSIGNED;
121
122 (new NotificationService())->createNotification([
123 'actor_id' => $assignerId,
124 'ticket_id' => $ticket->id,
125 'event_type' => $eventType,
126 'channel' => 'web',
127 'payload' => [
128 'ticket_title' => $ticket->title,
129 'ticket_priority' => $ticket->priority,
130 'assignment_type' => $eventType === NotificationEventMap::TICKET_REASSIGNED ? 'reassignment' : 'assignment',
131 'assigned_agent' => $this->formatPerson($assignedAgent),
132 'assigner' => $this->formatPerson($assigner)
133 ]
134 ], $recipientIds);
135 }
136
137 public function handleCustomerReply($response, $ticket, $person)
138 {
139 $recipientResolver = new RecipientResolver();
140 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
141
142 if (!$assignedAgent) {
143 return;
144 }
145
146 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
147
148 if (!$recipientIds) {
149 return;
150 }
151
152 (new NotificationService())->createNotification([
153 'actor_id' => $person->id,
154 'ticket_id' => $ticket->id,
155 'conversation_id' => $response->id,
156 'event_type' => NotificationEventMap::CUSTOMER_REPLIED,
157 'channel' => 'web',
158 'payload' => [
159 'ticket_title' => $ticket->title,
160 'ticket_priority' => $ticket->priority,
161 'customer' => $this->formatPerson($person),
162 'conversation_type' => $response->conversation_type,
163 'content_preview' => $this->makeContentPreview($response->content)
164 ]
165 ], $recipientIds);
166 }
167
168 public function handleTicketClosed($ticket, $person)
169 {
170 $this->createStatusNotification($ticket, $person, NotificationEventMap::TICKET_CLOSED);
171 }
172
173 public function handleTicketReopened($ticket, $person)
174 {
175 $this->createStatusNotification($ticket, $person, NotificationEventMap::TICKET_REOPENED);
176 }
177
178 public function handleWorkflowTriggered($workflow, $ticket, $person = null, $context = [])
179 {
180 if (!$workflow || !$ticket || empty($ticket->id)) {
181 return;
182 }
183
184 $actorId = (!empty($person) && !empty($person->id)) ? (int) $person->id : null;
185 $recipientResolver = new RecipientResolver();
186 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
187
188 if (!$assignedAgent) {
189 return;
190 }
191
192 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
193
194 if (!$recipientIds) {
195 return;
196 }
197
198 $context = is_array($context) ? $context : [];
199
200 (new NotificationService())->createNotification([
201 'actor_id' => $actorId,
202 'ticket_id' => $ticket->id,
203 'event_type' => NotificationEventMap::WORKFLOW_TRIGGERED,
204 'channel' => 'web',
205 'payload' => [
206 'ticket_title' => $ticket->title,
207 'ticket_priority'=> $ticket->priority,
208 'workflow_id' => !empty($workflow->id) ? (int) $workflow->id : null,
209 'workflow_name' => !empty($workflow->title) ? $workflow->title : __('Workflow', 'fluent-support'),
210 'trigger_type' => !empty($workflow->trigger_type) ? $workflow->trigger_type : null,
211 'trigger_key' => !empty($workflow->trigger_key) ? $workflow->trigger_key : null,
212 'summary' => $this->makeWorkflowSummary($workflow, $context),
213 'actor' => $this->formatPerson($person),
214 'context' => [
215 'source' => !empty($context['source']) ? sanitize_text_field($context['source']) : null,
216 'event' => !empty($context['event']) ? sanitize_text_field($context['event']) : null
217 ]
218 ]
219 ], $recipientIds);
220 }
221
222 protected function createStatusNotification($ticket, $person, $eventType)
223 {
224 $actorId = (!empty($person) && !empty($person->id)) ? (int) $person->id : null;
225 $recipientResolver = new RecipientResolver();
226 $assignedAgent = $recipientResolver->resolveAssignedAgent($ticket);
227
228 if (!$assignedAgent) {
229 return;
230 }
231
232 $recipientIds = $recipientResolver->extractRecipientPersonIds([$assignedAgent]);
233
234 if (!$recipientIds) {
235 return;
236 }
237
238 (new NotificationService())->createNotification([
239 'actor_id' => $actorId,
240 'ticket_id' => $ticket->id,
241 'event_type' => $eventType,
242 'channel' => 'web',
243 'payload' => [
244 'ticket_title' => $ticket->title,
245 'ticket_priority' => $ticket->priority,
246 'status' => $eventType === NotificationEventMap::TICKET_CLOSED ? 'closed' : 'reopened',
247 'actor' => $this->formatPerson($person)
248 ]
249 ], $recipientIds);
250 }
251
252 protected function hasPreviousAssignment($previousAgentId)
253 {
254 return (int) $previousAgentId > 0;
255 }
256
257 protected function filterTicketAccessibleAgents($agents, $ticket)
258 {
259 if (empty($agents)) {
260 return [];
261 }
262
263 $agentIds = [];
264 foreach ($agents as $agent) {
265 $agentIds[] = $agent->id;
266 }
267
268 $metas = Meta::where('object_type', 'person_meta')
269 ->where('key', 'agent_restrictions')
270 ->whereIn('object_id', $agentIds)
271 ->get();
272
273 $restrictionsMap = [];
274 foreach ($metas as $meta) {
275 $restrictionsMap[$meta->object_id] = Helper::safeUnserialize($meta->value) ?: [];
276 }
277
278 $ticketAccess = new AgentTicketAccess();
279 $accessibleAgents = [];
280
281 foreach ($agents as $agent) {
282 $restrictions = $restrictionsMap[$agent->id] ?? [];
283 if ($ticketAccess->canAccess($agent, $ticket, $restrictions)) {
284 $accessibleAgents[] = $agent;
285 }
286 }
287
288 return $accessibleAgents;
289 }
290
291 protected function makeContentPreview($content)
292 {
293 return wp_html_excerpt(wp_strip_all_tags((string) $content), 180, '...');
294 }
295
296 protected function makeWorkflowSummary($workflow, array $context = [])
297 {
298 $workflowName = !empty($workflow->title) ? $workflow->title : __('Workflow', 'fluent-support');
299 $source = !empty($context['source']) ? sanitize_text_field($context['source']) : 'workflow';
300
301 if ($source === 'manual') {
302 return sprintf(__('Manual workflow "%s" was run', 'fluent-support'), $workflowName);
303 }
304
305 return sprintf(__('Automation workflow "%s" was triggered', 'fluent-support'), $workflowName);
306 }
307
308 protected function formatPerson($person)
309 {
310 if (!$person) {
311 return null;
312 }
313
314 return [
315 'id' => (int) $person->id,
316 'person_type' => $person->person_type,
317 'name' => $person->full_name ?: $person->email,
318 'email' => $person->email
319 ];
320 }
321 }
322