belongsToMany(User::class)->withTimestamps(); } public function company() { return $this->belongsTo(Company::class); } /** * HTML listing of the group's static agents for the details (show) view. * * @return string */ public function getStaticAgentsAttribute(): string { $agents = static::staticAgentsFromAsterisk($this->id); if (empty($agents)) { return ''; } return implode('
', array_map(function ($agent) { return e((string) $agent); }, $agents)); } /** * Read current external phone members from the queue. * This reads directly from asterisk.queues_details and does not require a new * database migration for the groups table. * * @param int $groupId * @return array */ public static function queuePhonesFromAsterisk($groupId): array { try { $extensionMembers = \DB::table('group_user') ->join('asterisk.devices', 'asterisk.devices.id', '=', 'group_user.user_id') ->where('group_user.group_id', $groupId) ->pluck('group_user.user_id') ->map(function ($userId) { return (string) $userId; }) ->all(); return \DB::table('asterisk.queues_details') ->where('id', $groupId) ->where('keyword', 'member') ->pluck('data') ->map(function ($data) use ($extensionMembers) { $data = (string) $data; if (preg_match('#^Local/([^@]+)@from-queue/n(?:,\d+)?$#', trim($data), $matches) && in_array($matches[1], $extensionMembers, true)) { return null; } return static::normalizeQueuePhoneMember($data); }) ->filter() ->values() ->toArray(); } catch (\Exception $e) { return []; } } /** * Read every static agent currently stored in the PBX queue, formatted as * "number,priority" so the group form can behave like the FreePBX queue form. * * @param int $groupId * @return array */ public static function staticAgentsFromAsterisk($groupId): array { try { return \DB::table('asterisk.queues_details') ->where('id', $groupId) ->where('keyword', 'member') ->pluck('data') ->map(function ($data) { return static::normalizeQueuePhoneMember((string) $data, true); }) ->filter() ->values() ->toArray(); } catch (\Exception $e) { return []; } } /** * Parse a raw phone-number input string into a clean array of values. * * @param mixed $raw * @return array */ public static function normalizeQueuePhoneInput($raw): array { if (! is_string($raw) && ! is_array($raw)) { return []; } $items = is_array($raw) ? $raw : preg_split('/[\r\n,;]+/', (string) $raw); $items = array_filter(array_map(function ($value) { return trim((string) $value); }, (array) $items)); return array_values(array_unique($items)); } /** * Parse the "Static Agents" textarea value. * * Accepts one agent per line in FreePBX style: number,priority * (for example "998,1" or "01093316977,0"). Lines without a priority * default to priority 0. * * @param mixed $raw * @return array List of ['number' => string, 'priority' => string] entries. */ public static function parseStaticAgents($raw): array { $lines = is_array($raw) ? $raw : preg_split('/[\r\n;]+/', (string) $raw); $agents = []; $indexByNumber = []; foreach ((array) $lines as $line) { $line = trim((string) $line); if ($line === '') { continue; } $number = $line; $priority = '0'; if (preg_match('/^(.*?),(\d+)$/', $line, $matches)) { $number = trim($matches[1]); $priority = $matches[2]; } if ($number === '') { continue; } // Prefix the key so numeric strings (e.g. "01093316977") are not // cast to integers by PHP, which would drop the leading zero. $key = 'n:' . $number; if (isset($indexByNumber[$key])) { $agents[$indexByNumber[$key]]['priority'] = $priority; } else { $indexByNumber[$key] = count($agents); $agents[] = ['number' => $number, 'priority' => $priority]; } } return $agents; } /** * Pull the PBX queue membership back into the group. * * Reads every queue member, then: * - returns all agents formatted as "number,priority" (for Static Agents); * - syncs the group's users (group_user) to the user IDs that match the * extension members found in the queue. * * @param int $groupId * @return array ['agents' => array, 'user_ids' => array] */ public static function syncGroupUsersFromQueue($groupId): array { $groupId = (int) $groupId; $report = ['agents' => [], 'user_ids' => []]; try { $group = static::find($groupId); if (! $group) { return $report; } // If the group has no PBX queue at all, leave the local users // untouched — there is nothing to pull in from the PBX. $queue = \DB::table('asterisk.queues_config')->where('extension', $groupId)->first(); if (! $queue) { $report['agents'] = static::staticAgentsFromAsterisk($groupId); return $report; } // All agents currently in the PBX queue, e.g. "998,1". $agents = static::staticAgentsFromAsterisk($groupId); $report['agents'] = $agents; // Extension numbers present in the queue. $numbers = []; foreach ($agents as $agent) { $parsed = static::parseStaticAgents($agent); if (! empty($parsed)) { $numbers[] = $parsed[0]['number']; } } // Keep only numbers that correspond to real users. $userIds = []; if (! empty($numbers)) { $query = User::whereIn('id', $numbers); if ($group->company_id) { $query->where('company_id', $group->company_id); } $userIds = $query->pluck('id')->all(); } // Sync the group_user pivot table with the matched user IDs. $group->users()->sync($userIds); $report['user_ids'] = $userIds; return $report; } catch (\Exception $e) { return $report; } } /** * Convert a raw queue member string into a plain phone number. * * @param string $member * @param bool $withPriority When true, keep the penalty as "number,priority". * @return string|null */ protected static function normalizeQueuePhoneMember(string $member, bool $withPriority = false) { $member = trim($member); if ($member === '') { return null; } $priority = '0'; if (preg_match('/,(\d+)$/', $member, $matches)) { $priority = $matches[1]; $member = preg_replace('/,\d+$/', '', $member); } if (preg_match('#^Local/(.+?)@#', $member, $matches)) { $member = $matches[1]; } if ($member === '') { return null; } return $withPriority ? $member . ',' . $priority : $member; } }