getAgi()->answer(); } public function run() { \Log::info('GLN capture started'); $client = $this->getAgi(); $linkedid = $client->getFullVariable('CHANNEL(LINKEDID)'); $selection = $this->isMultiLanguageEnabled() ? $this->glnPromptSelection($client) : (new GlnPromptResolver())->resolve($client, false); $prompt = $selection['prompt']; // Diagnostic: which prompt was selected and why. No caller data is logged. \Log::info('GLN prompt selected', [ 'linkedid' => $linkedid, 'queue_number' => $selection['queue_number'], 'queue_name' => $selection['queue_name'], 'current_queue_number' => $selection['current_queue_number'], 'language' => $selection['language'], 'language_source' => $selection['language_source'], 'playback_path' => $prompt, ]); $firstResult = $client->getData($prompt, (string) self::FIRST_DIGIT_TIMEOUT_MS, 1); $firstDigit = $firstResult->isTimeout() ? '' : $firstResult->getDigits(); if ($firstDigit === '#' || ($firstDigit === '' && !$firstResult->isTimeout())) { \Log::info('GLN capture skipped by caller', ['linkedid' => $linkedid]); $this->routeToDefaultQueue(); return false; } $digits = is_string($firstDigit) && ctype_digit($firstDigit) ? $firstDigit : ''; $invalidInput = $digits === ''; while (strlen($digits) < self::DIGIT_COUNT + 1 && !$invalidInput) { $result = $client->waitDigit(self::INTER_DIGIT_TIMEOUT_MS); if ($result->isTimeout()) { break; } $digit = $result->getDigits(); if ($digit === '#') { \Log::info('GLN capture skipped by caller', ['linkedid' => $linkedid]); $this->routeToDefaultQueue(); return false; } if (!is_string($digit) || strlen($digit) !== 1 || !ctype_digit($digit)) { $invalidInput = true; break; } $digits .= $digit; } if (strlen($digits) !== self::DIGIT_COUNT || $invalidInput || !$linkedid) { \Log::warning('GLN capture incomplete or missing linked ID', [ 'linkedid' => $linkedid, 'digit_count' => strlen($digits), ]); $this->routeToDefaultQueue(); return false; } Redis::setex('call-gln:' . $linkedid, self::REDIS_TTL_SECONDS, $digits); CallLogSyncService::saveGln($linkedid, $digits); \Log::info('GLN captured', ['linkedid' => $linkedid]); $this->routeToDefaultQueue(); return true; } protected function routeToDefaultQueue(): void { $this->getAgi()->exec('Goto', ['ext-queues', $this->getDefaultQueue(), '1']); } /** * @return array{queue_number: ?string, queue_name: ?string, current_queue_number: ?string, language: string, language_source: string, prompt: string} */ protected function glnPromptSelection(IClient $client): array { return (new GlnPromptResolver())->resolve($client, $this->isMultiLanguageEnabled()); } protected function isMultiLanguageEnabled(): bool { if ($this->multiLanguageEnabled === null) { $companyId = Company::value('id'); $this->multiLanguageEnabled = $companyId ? (bool) CompanySettings::get( SettingsHelper::S_COMPANY_GLN_MULTI_LANGUAGE_ENABLED, false, $companyId ) : false; } return $this->multiLanguageEnabled; } protected function getDefaultQueue(): string { $companyId = Company::value('id'); $queue = CompanySettings::get( SettingsHelper::S_COMPANY_GLN_DESTINATION_QUEUE, SettingsHelper::DEFAULT_COMPANY_GLN_DESTINATION_QUEUE, $companyId ); return trim((string) $queue) ?: SettingsHelper::DEFAULT_COMPANY_GLN_DESTINATION_QUEUE; } public function shutdown() { return true; } public function errorHandler($type, $message, $file, $line) { \Log::error('GLN AGI error', compact('type', 'message', 'file', 'line')); } public function signalHandler($signal) { $this->shutdown(); exit(0); } }