where('key', '_live_activity') ->where('object_id', $ticketId) ->first(); $activities = []; if ($meta) { $activities = Helper::safeUnserialize($meta->value); } foreach ($activities as $index => $activity) { if ((time() - $activity) > 60) { unset($activities[$index]); } } if (!$currentAgentId) { return self::getAgentsInfoFromActivities($activities); } $activities[$currentAgentId] = time(); if ($meta) { $meta->value = maybe_serialize($activities); $meta->save(); } else { Meta::insert([ 'object_type' => 'ticket_meta', 'key' => '_live_activity', 'object_id' => $ticketId, 'value' => maybe_serialize($activities) ]); } return self::getAgentsInfoFromActivities($activities); } private static function getAgentsInfoFromActivities($activities) { if (!$activities) { return []; } return Agent::select(['id', 'email', 'first_name', 'last_name']) ->whereIn('id', array_keys($activities)) ->get(); } /** * Batch-load live activity for a collection of tickets. * Uses 2 queries total: one for activity metas, one for all agents. * * @param \Iterable $tickets Collection of Ticket models * @return void */ public static function loadBatchLiveActivities($tickets) { $activeTicketIds = $tickets->where('status', '!=', 'closed')->pluck('id')->toArray(); if (!$activeTicketIds) { foreach ($tickets as $ticket) { $ticket->live_activity = []; } return; } $activityMetas = Meta::where('object_type', 'ticket_meta') ->where('key', '_live_activity') ->whereIn('object_id', $activeTicketIds) ->get() ->keyBy('object_id'); $allAgentIds = []; $ticketActivities = []; foreach ($activityMetas as $ticketId => $meta) { $activities = Helper::safeUnserialize($meta->value); if (!is_array($activities)) { continue; } $activities = array_filter($activities, function ($time) { return (time() - $time) <= 60; }); $ticketActivities[$ticketId] = $activities; $allAgentIds = array_merge($allAgentIds, array_keys($activities)); } $allAgentIds = array_unique(array_filter($allAgentIds)); $agents = $allAgentIds ? Agent::select(['id', 'email', 'first_name', 'last_name']) ->whereIn('id', $allAgentIds) ->get() ->keyBy('id') : []; $activitiesByTicket = []; foreach ($ticketActivities as $ticketId => $activities) { $ticketAgents = []; foreach (array_keys($activities) as $agentId) { if (isset($agents[$agentId])) { $ticketAgents[] = $agents[$agentId]; } } $activitiesByTicket[$ticketId] = $ticketAgents; } foreach ($tickets as $ticket) { $ticket->live_activity = $activitiesByTicket[$ticket->id] ?? []; } } /** * removeFromActivities method will remove the old live activity by agent and ticket id * @param $ticketId * @param $agentId * @return bool */ public static function removeFromActivities($ticketId, $agentId) { $meta = Meta::where('object_type', 'ticket_meta') ->where('key', '_live_activity') ->where('object_id', $ticketId) ->first(); $activities = []; if ($meta) { $activities = Helper::safeUnserialize($meta->value); } if (!$activities) { return false; } unset($activities[$agentId]); foreach ($activities as $index => $activity) { if ((time() - $activity) > 60) { unset($activities[$index]); } } $meta->value = maybe_serialize($activities); $meta->save(); return true; } /** * getSuggestedTickets method will return the list of Suggested tickets * This method will get the agent id as parameter and fetch ticket information that are waiting to reply or unassigned * @param $agentId * @param int $limit * @return mixed */ public static function getSuggestedTickets($agentId, $limit = 5) { $restrictedBusinessBoxes = PermissionManager::getRestrictedMailboxIds(); //Get lis of tickets which are waiting for reply $tickets = Ticket::where('agent_id', $agentId) ->where(function ($q) use ($restrictedBusinessBoxes) { $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes); $q->orWhereNull('mailbox_id'); }) ->where('status', '!=', 'closed') ->applyFilters([ 'waiting_for_reply' => 'yes' ]) ->oldest('last_customer_response') ->limit($limit) ->with( [ 'customer', 'mailbox', 'tags', 'agent' ]) ->get(); //If no ticket is available for reply and logged-in user has permission to manage unassigned tickets if($tickets->isEmpty() && PermissionManager::currentUserCan('fst_manage_unassigned_tickets')) { //Get the ticket list which status is not closed and agent id is null or 0 $tickets = Ticket::where('status', '!=', 'closed') ->oldest('id') ->where(function ($q) use ($restrictedBusinessBoxes) { $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes); $q->orWhereNull('mailbox_id'); }) ->where(function ($q) { $q->whereNull('agent_id'); $q->orWhere('agent_id', '0'); }) ->with( [ 'customer', 'mailbox', 'tags', ]) ->limit($limit) ->get(); } return $tickets; } // This method will return all tagged/mentioned/watcher ticket's ids for filtering public static function getWatcherTicketIds($agentId) { $mentioned = TagPivot::where('source_type', 'ticket_watcher') ->where('tag_id', $agentId) ->latest('id') ->get(['source_id']); $ticketIds = array_column($mentioned->toArray(), 'source_id'); return $ticketIds; } // This method will return all tagged/mentioned/watcher tickets of logged in agent public static function getTicketsToWatch() { $agent = Helper::getCurrentAgent(); $restrictedBusinessBoxes = PermissionManager::getRestrictedMailboxIds(); $tickets = Ticket::with( [ 'customer', 'mailbox', 'tags', 'agent' ]) ->limit(5) ->where(function ($q) use ($restrictedBusinessBoxes) { $q->whereNotIn('mailbox_id', $restrictedBusinessBoxes); $q->orWhereNull('mailbox_id'); }) ->join('fs_tag_pivot', 'fs_tag_pivot.source_id', '=', 'fs_tickets.id') ->where('fs_tag_pivot.source_type', '=', 'ticket_watcher') ->where('fs_tag_pivot.tag_id', '=', $agent->id) ->select(['fs_tickets.*']) ->get(); return $tickets; } // This method will return all ticket watcher inside a ticket public static function getWatchers($watchers) { $watcherAgents = []; foreach ($watchers as $watcher) { $watcherAgents[] = Agent::where('id', absint($watcher->tag_id))->select(['id', 'first_name', 'last_name'])->first(); } return $watcherAgents; } /** @internal Not called from core controllers — available for Pro/hook usage. */ public static function getCarbonCopyCustomerInfo($ticketId){ $existing = Meta::where('object_type', 'beginning_cc_info')->where('object_id', $ticketId)->first(); if($existing){ return Helper::safeUnserialize($existing->value, []); } return []; } // This method will count total tickets public static function countAllTickets() { return (new Ticket())->count(); } // This method will count all un-assigned tickets public static function countUnassignedTickets() { return Ticket::whereNull('agent_id')->count(); } // This method will count all closed tickets public static function countClosedTickets() { return Ticket::where('status', 'closed')->count(); } // This method will count all New tickets public static function countNewTickets() { return Ticket::where('status', 'new')->count(); } // This method will count all Active tickets public static function countActiveTickets() { return Ticket::where('status', 'active')->count(); } public static function getTicketEssentials($type = '') { $agents = Agent::select(['id', 'first_name', 'last_name']) ->where('person_type', 'agent') ->get()->toArray(); $data = [ 'client_priorities' => Helper::customerTicketPriorities(), 'ticket_statuses_group' => Helper::ticketStatusGroups(), 'agents' => $agents, 'changeable_ticket_statuses' => Helper::changeableTicketStatuses(), 'admin_priorities' => Helper::adminTicketPriorities(), 'enable_draft_mode' => Helper::getBusinessSettings('enable_draft_mode', 'no'), 'max_file_upload' => Helper::getBusinessSettings('max_file_upload', 3), 'support_products' => Product::select(['id', 'title'])->orderedByTitle()->get(), 'ticket_tags' => TicketTag::select(['id', 'title'])->get()->toArray(), 'mailboxes' => MailBox::getAccessibleBoxes(), 'ticket_statuses' => Helper::ticketStatuses(), ]; return $type ? $data[$type] : $data; } public static function getLabelSearch($agentId) { $lists = Meta::where('object_id', $agentId) ->where('object_type', 'search_meta') ->where('key', 'label_search') ->first(); $unserialize = []; if ($lists) { $unserialize = Helper::safeUnserialize($lists->value); } return $unserialize; } public static function saveSearchLabel($agent_id, $searchData, $filterType) { $agent_id = get_current_user_id(); $existingRecord = Meta::where('object_id', $agent_id) ->where('object_type', 'search_meta') ->where('key', 'label_search') ->first(); // If record exists, unserialize the data or initialize an empty array $existingData = $existingRecord ? Helper::safeUnserialize($existingRecord->value) ?: [] : []; // Check if it's an update or new entry $isUpdate = isset($searchData['id']) && array_filter($existingData, function ($item) use ($searchData) { return isset($item['id']) && $item['id'] == $searchData['id']; }); // Handle adding or updating $updatedData = self::addOrUpdateSearchData($existingData, $searchData); // Save the updated data back to the database if ($existingRecord) { $existingRecord->update([ 'value' => maybe_serialize($updatedData) ]); } else { Meta::insert([ 'object_type' => 'search_meta', 'key' => 'label_search', 'object_id' => $agent_id, 'value' => maybe_serialize($updatedData) ]); } $message = $isUpdate ? __('Your saved search has been updated successfully!', 'fluent-support') : __('Your search has been saved successfully!', 'fluent-support'); return [ 'message' => $message, ]; } private static function addOrUpdateSearchData(array $existingData, array $searchData): array { if (isset($searchData['id'])) { $updated = false; // Update the existing record with the matching ID foreach ($existingData as &$record) { if (isset($record['id']) && $record['id'] == $searchData['id']) { $record = array_merge($record, $searchData); $updated = true; break; } } // If no matching record found, append the new data if (!$updated) { $existingData[] = $searchData; } } else { // If no ID provided, generate a new one and add the data $newId = self::generateNewId($existingData); $searchData['id'] = $newId; $existingData[] = $searchData; } return $existingData; } public static function deleteSavedSearch($search_id) { $agent_id = get_current_user_id(); $existingRecord = Meta::where('object_id', $agent_id) ->where('object_type', 'search_meta') ->where('key', 'label_search') ->first(); if ($existingRecord) { $existingData = Helper::safeUnserialize($existingRecord->value) ?: []; $updatedData = array_filter($existingData, function ($item) use ($search_id) { return isset($item['id']) && $item['id'] != $search_id; }); if (count($existingData) !== count($updatedData)) { $existingRecord->update([ 'value' => maybe_serialize(array_values($updatedData)) ]); return [ 'message' => __('Search deleted successfully.', 'fluent-support'), ]; } return [ 'message' => __('Search ID not found.', 'fluent-support'), ]; } } private static function generateNewId(array $existingData): int { if (empty($existingData)) { return 1; } $maxId = max(array_column($existingData, 'id')); return $maxId + 1; } }