PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.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
← All changes | app/Services/TicketHelper.php +335 -8 1.5.52.4.0 View file →
@@ -2,11 +2,17 @@
2 2
3 3 namespace FluentSupport\App\Services;
4 4
5 5 use FluentSupport\App\Models\Agent;
6 +use FluentSupport\App\Models\Conversation;
7 +use FluentSupport\App\Models\MailBox;
6 8 use FluentSupport\App\Models\Meta;
9 +use FluentSupport\App\Models\Product;
10 +use FluentSupport\App\Models\TagPivot;
7 11 use FluentSupport\App\Models\Ticket;
12 +use FluentSupport\App\Models\TicketTag;
8 13 use FluentSupport\App\Modules\PermissionManager;
14 +use FluentSupport\App\Services\Helper;
9 15
10 16 class TicketHelper
11 17 {
12 18 /**
@@ -24,9 +30,9 @@
24 30 ->first();
25 31
26 32 $activities = [];
27 33 if ($meta) {
28 - $activities = maybe_unserialize($meta->value);
34 + $activities = Helper::safeUnserialize($meta->value);
29 35 }
30 36
31 37 foreach ($activities as $index => $activity) {
32 38 if ((time() - $activity) > 60) {
@@ -54,20 +60,85 @@
54 60
55 61 return self::getAgentsInfoFromActivities($activities);
56 62 }
57 63
58 - public static function getAgentsInfoFromActivities($activities)
64 + private static function getAgentsInfoFromActivities($activities)
59 65 {
60 66 if (!$activities) {
61 67 return [];
62 68 }
63 69
64 - return Agent::select(['email', 'first_name', 'last_name'])
70 + return Agent::select(['id', 'email', 'first_name', 'last_name'])
65 71 ->whereIn('id', array_keys($activities))
66 72 ->get();
67 73 }
68 74
69 75 /**
76 + * Batch-load live activity for a collection of tickets.
77 + * Uses 2 queries total: one for activity metas, one for all agents.
78 + *
79 + * @param \Iterable $tickets Collection of Ticket models
80 + * @return void
81 + */
82 + public static function loadBatchLiveActivities($tickets)
83 + {
84 + $activeTicketIds = $tickets->where('status', '!=', 'closed')->pluck('id')->toArray();
85 +
86 + if (!$activeTicketIds) {
87 + foreach ($tickets as $ticket) {
88 + $ticket->live_activity = [];
89 + }
90 + return;
91 + }
92 +
93 + $activityMetas = Meta::where('object_type', 'ticket_meta')
94 + ->where('key', '_live_activity')
95 + ->whereIn('object_id', $activeTicketIds)
96 + ->get()
97 + ->keyBy('object_id');
98 +
99 + $allAgentIds = [];
100 + $ticketActivities = [];
101 +
102 + foreach ($activityMetas as $ticketId => $meta) {
103 + $activities = Helper::safeUnserialize($meta->value);
104 + if (!is_array($activities)) {
105 + continue;
106 + }
107 +
108 + $activities = array_filter($activities, function ($time) {
109 + return (time() - $time) <= 60;
110 + });
111 +
112 + $ticketActivities[$ticketId] = $activities;
113 + $allAgentIds = array_merge($allAgentIds, array_keys($activities));
114 + }
115 +
116 + $allAgentIds = array_unique(array_filter($allAgentIds));
117 + $agents = $allAgentIds
118 + ? Agent::select(['id', 'email', 'first_name', 'last_name'])
119 + ->whereIn('id', $allAgentIds)
120 + ->get()
121 + ->keyBy('id')
122 + : [];
123 +
124 + $activitiesByTicket = [];
125 + foreach ($ticketActivities as $ticketId => $activities) {
126 + $ticketAgents = [];
127 + foreach (array_keys($activities) as $agentId) {
128 + if (isset($agents[$agentId])) {
129 + $ticketAgents[] = $agents[$agentId];
130 + }
131 + }
132 + $activitiesByTicket[$ticketId] = $ticketAgents;
133 + }
134 +
135 + foreach ($tickets as $ticket) {
136 + $ticket->live_activity = $activitiesByTicket[$ticket->id] ?? [];
137 + }
138 + }
139 +
140 + /**
70 141 * removeFromActivities method will remove the old live activity by agent and ticket id
71 142 * @param $ticketId
72 143 * @param $agentId
73 144 * @return bool
@@ -80,9 +151,9 @@
80 151 ->first();
81 152
82 153 $activities = [];
83 154 if ($meta) {
84 - $activities = maybe_unserialize($meta->value);
155 + $activities = Helper::safeUnserialize($meta->value);
85 156 }
86 157
87 158 if (!$activities) {
88 159 return false;
@@ -109,17 +180,29 @@
109 180 * @return mixed
110 181 */
111 182 public static function getSuggestedTickets($agentId, $limit = 5)
112 183 {
184 + $restrictedBusinessBoxes = PermissionManager::getRestrictedMailboxIds();
185 +
113 186 //Get lis of tickets which are waiting for reply
114 187 $tickets = Ticket::where('agent_id', $agentId)
188 + ->where(function ($q) use ($restrictedBusinessBoxes) {
189 + $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes);
190 + $q->orWhereNull('mailbox_id');
191 + })
115 192 ->where('status', '!=', 'closed')
116 193 ->applyFilters([
117 194 'waiting_for_reply' => 'yes'
118 195 ])
119 - ->orderBy('last_customer_response', 'ASC')
196 + ->oldest('last_customer_response')
120 197 ->limit($limit)
121 - ->with('customer')
198 + ->with(
199 + [
200 + 'customer',
201 + 'mailbox',
202 + 'tags',
203 + 'agent'
204 + ])
122 205 ->get();
123 206
124 207 //If no ticket is available for reply and logged-in user has permission to manage unassigned tickets
125 208 if($tickets->isEmpty() && PermissionManager::currentUserCan('fst_manage_unassigned_tickets')) {
@@ -124,14 +207,23 @@
124 207 //If no ticket is available for reply and logged-in user has permission to manage unassigned tickets
125 208 if($tickets->isEmpty() && PermissionManager::currentUserCan('fst_manage_unassigned_tickets')) {
126 209 //Get the ticket list which status is not closed and agent id is null or 0
127 210 $tickets = Ticket::where('status', '!=', 'closed')
128 - ->orderBy('id', 'ASC')
211 + ->oldest('id')
212 + ->where(function ($q) use ($restrictedBusinessBoxes) {
213 + $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes);
214 + $q->orWhereNull('mailbox_id');
215 + })
129 216 ->where(function ($q) {
130 217 $q->whereNull('agent_id');
131 218 $q->orWhere('agent_id', '0');
132 219 })
133 - ->with('customer')
220 + ->with(
221 + [
222 + 'customer',
223 + 'mailbox',
224 + 'tags',
225 + ])
134 226 ->limit($limit)
135 227 ->get();
136 228 }
137 229
@@ -137,6 +229,241 @@
137 229
138 230
139 231 return $tickets;
140 232
233 + }
234 +
235 + // This method will return all tagged/mentioned/watcher ticket's ids for filtering
236 + public static function getWatcherTicketIds($agentId)
237 + {
238 + $mentioned = TagPivot::where('source_type', 'ticket_watcher')
239 + ->where('tag_id', $agentId)
240 + ->latest('id')
241 + ->get(['source_id']);
242 +
243 + $ticketIds = array_column($mentioned->toArray(), 'source_id');
244 + return $ticketIds;
245 + }
246 +
247 + // This method will return all tagged/mentioned/watcher tickets of logged in agent
248 + public static function getTicketsToWatch()
249 + {
250 + $agent = Helper::getCurrentAgent();
251 + $restrictedBusinessBoxes = PermissionManager::getRestrictedMailboxIds();
252 +
253 + $tickets = Ticket::with(
254 + [
255 + 'customer',
256 + 'mailbox',
257 + 'tags',
258 + 'agent'
259 + ])
260 + ->limit(5)
261 + ->where(function ($q) use ($restrictedBusinessBoxes) {
262 + $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes);
263 + $q->orWhereNull('mailbox_id');
264 + })
265 + ->join('fs_tag_pivot', 'fs_tag_pivot.source_id', '=', 'fs_tickets.id')
266 + ->where('fs_tag_pivot.source_type', '=', 'ticket_watcher')
267 + ->where('fs_tag_pivot.tag_id', '=', $agent->id)
268 + ->select(['fs_tickets.*'])
269 + ->get();
270 +
271 + return $tickets;
272 + }
273 +
274 + // This method will return all ticket watcher inside a ticket
275 + public static function getWatchers($watchers)
276 + {
277 + $watcherAgents = [];
278 +
279 + foreach ($watchers as $watcher) {
280 + $watcherAgents[] = Agent::where('id', absint($watcher->tag_id))->select(['id', 'first_name', 'last_name'])->first();
281 + }
282 +
283 + return $watcherAgents;
284 + }
285 +
286 + /** @internal Not called from core controllers — available for Pro/hook usage. */
287 + public static function getCarbonCopyCustomerInfo($ticketId){
288 + $existing = Meta::where('object_type', 'beginning_cc_info')->where('object_id', $ticketId)->first();
289 + if($existing){
290 + return Helper::safeUnserialize($existing->value, []);
291 + }
292 +
293 + return [];
294 + }
295 +
296 + // This method will count total tickets
297 + public static function countAllTickets()
298 + {
299 + return (new Ticket())->count();
300 + }
301 + // This method will count all un-assigned tickets
302 + public static function countUnassignedTickets()
303 + {
304 + return Ticket::whereNull('agent_id')->count();
305 + }
306 + // This method will count all closed tickets
307 + public static function countClosedTickets()
308 + {
309 + return Ticket::where('status', 'closed')->count();
310 + }
311 +
312 + // This method will count all New tickets
313 + public static function countNewTickets()
314 + {
315 + return Ticket::where('status', 'new')->count();
316 + }
317 +
318 + // This method will count all Active tickets
319 + public static function countActiveTickets()
320 + {
321 + return Ticket::where('status', 'active')->count();
322 + }
323 +
324 + public static function getTicketEssentials($type = '')
325 + {
326 + $agents = Agent::select(['id', 'first_name', 'last_name'])
327 + ->where('person_type', 'agent')
328 + ->get()->toArray();
329 +
330 + $data = [
331 + 'client_priorities' => Helper::customerTicketPriorities(),
332 + 'ticket_statuses_group' => Helper::ticketStatusGroups(),
333 + 'agents' => $agents,
334 + 'changeable_ticket_statuses' => Helper::changeableTicketStatuses(),
335 + 'admin_priorities' => Helper::adminTicketPriorities(),
336 + 'enable_draft_mode' => Helper::getBusinessSettings('enable_draft_mode', 'no'),
337 + 'max_file_upload' => Helper::getBusinessSettings('max_file_upload', 3),
338 + 'support_products' => Product::select(['id', 'title'])->orderedByTitle()->get(),
339 + 'ticket_tags' => TicketTag::select(['id', 'title'])->get()->toArray(),
340 + 'mailboxes' => MailBox::getAccessibleBoxes(),
341 + 'ticket_statuses' => Helper::ticketStatuses(),
342 + ];
343 +
344 + return $type ? $data[$type] : $data;
345 + }
346 +
347 + public static function getLabelSearch($agentId)
348 + {
349 + $lists = Meta::where('object_id', $agentId)
350 + ->where('object_type', 'search_meta')
351 + ->where('key', 'label_search')
352 + ->first();
353 + $unserialize = [];
354 + if ($lists) {
355 + $unserialize = Helper::safeUnserialize($lists->value);
356 + }
357 +
358 + return $unserialize;
359 + }
360 +
361 + public static function saveSearchLabel($agent_id, $searchData, $filterType) {
362 +
363 + $agent_id = get_current_user_id();
364 + $existingRecord = Meta::where('object_id', $agent_id)
365 + ->where('object_type', 'search_meta')
366 + ->where('key', 'label_search')
367 + ->first();
368 +
369 + // If record exists, unserialize the data or initialize an empty array
370 + $existingData = $existingRecord ? Helper::safeUnserialize($existingRecord->value) ?: [] : [];
371 +
372 + // Check if it's an update or new entry
373 + $isUpdate = isset($searchData['id']) && array_filter($existingData, function ($item) use ($searchData) {
374 + return isset($item['id']) && $item['id'] == $searchData['id'];
375 + });
376 +
377 + // Handle adding or updating
378 + $updatedData = self::addOrUpdateSearchData($existingData, $searchData);
379 +
380 + // Save the updated data back to the database
381 + if ($existingRecord) {
382 + $existingRecord->update([
383 + 'value' => maybe_serialize($updatedData)
384 + ]);
385 + } else {
386 + Meta::insert([
387 + 'object_type' => 'search_meta',
388 + 'key' => 'label_search',
389 + 'object_id' => $agent_id,
390 + 'value' => maybe_serialize($updatedData)
391 + ]);
392 + }
393 +
394 + $message = $isUpdate
395 + ? __('Your saved search has been updated successfully!', 'fluent-support')
396 + : __('Your search has been saved successfully!', 'fluent-support');
397 +
398 + return [
399 + 'message' => $message,
400 + ];
401 + }
402 +
403 + private static function addOrUpdateSearchData(array $existingData, array $searchData): array
404 + {
405 + if (isset($searchData['id'])) {
406 + $updated = false;
407 +
408 + // Update the existing record with the matching ID
409 + foreach ($existingData as &$record) {
410 + if (isset($record['id']) && $record['id'] == $searchData['id']) {
411 + $record = array_merge($record, $searchData);
412 + $updated = true;
413 + break;
414 + }
415 + }
416 +
417 + // If no matching record found, append the new data
418 + if (!$updated) {
419 + $existingData[] = $searchData;
420 + }
421 + } else {
422 + // If no ID provided, generate a new one and add the data
423 + $newId = self::generateNewId($existingData);
424 + $searchData['id'] = $newId;
425 + $existingData[] = $searchData;
426 + }
427 +
428 + return $existingData;
429 + }
430 +
431 + public static function deleteSavedSearch($search_id) {
432 + $agent_id = get_current_user_id();
433 + $existingRecord = Meta::where('object_id', $agent_id)
434 + ->where('object_type', 'search_meta')
435 + ->where('key', 'label_search')
436 + ->first();
437 +
438 + if ($existingRecord) {
439 + $existingData = Helper::safeUnserialize($existingRecord->value) ?: [];
440 +
441 + $updatedData = array_filter($existingData, function ($item) use ($search_id) {
442 + return isset($item['id']) && $item['id'] != $search_id;
443 + });
444 +
445 + if (count($existingData) !== count($updatedData)) {
446 + $existingRecord->update([
447 + 'value' => maybe_serialize(array_values($updatedData))
448 + ]);
449 + return [
450 + 'message' => __('Search deleted successfully.', 'fluent-support'),
451 + ];
452 + }
453 +
454 + return [
455 + 'message' => __('Search ID not found.', 'fluent-support'),
456 + ];
457 + }
458 + }
459 +
460 + private static function generateNewId(array $existingData): int
461 + {
462 + if (empty($existingData)) {
463 + return 1;
464 + }
465 +
466 + $maxId = max(array_column($existingData, 'id'));
467 + return $maxId + 1;
141 468 }
142 469 }