feat(inbound): add CID name prefix support per trunk and per inbound route

This commit is contained in:
sorcier committed 2026-11-03 20:01:16 +01:00
1 parent 9518e10616
commit 8804501c42
5 files changed
+188 -12

No files matched your search

+143 -8
View File
@@ -1309,16 +1309,19 @@ class ConfigGenerator
// 2b-bis. Contextes standards FreePBX / Passerelles FXO : [from-trunk] et [from-pstn]
$lines[] = "[from-trunk]";
$lines[] = "exten => _[+*#0-9a-zA-Z].,1,NoOp(Appel entrant trunk [from-trunk] EXTEN=\${EXTEN} Appelant=\${CALLERID(num)})";
$lines[] = "exten => _[+*#0-9a-zA-Z].,1,NoOp(Appel entrant trunk [from-trunk] EXTEN=\${EXTEN} Endpoint=\${CHANNEL(endpoint)} Appelant=\${CALLERID(num)})";
$lines[] = " same => n,ExecIf(\$[\"\${CALLERID(num):0:3}\" = \"+33\"]?Set(CALLERID(num)=0\${CALLERID(num):3}))";
$lines[] = " same => n,ExecIf(\$[\"\${CALLERID(num):0:4}\" = \"0033\"]?Set(CALLERID(num)=0\${CALLERID(num):4}))";
$lines[] = " same => n,GotoIf(\$[\"\${DB(blacklist/\${CALLERID(num)})}\" != \"\"]?app-blacklisted,s,1)";
$lines[] = " same => n,ExecIf(\$[\"\${EXTEN:0:3}\" = \"+33\"]?Goto(from-external,0\${EXTEN:3},1))";
$lines[] = " same => n,ExecIf(\$[\"\${EXTEN:0:3}\" = \"+33\"]?Set(EXTEN=0\${EXTEN:3}))";
$lines[] = " same => n,GotoIf(\$[\"\${DIALPLAN_EXISTS(from-trunk-\${CHANNEL(endpoint)},\${EXTEN},1)}\" = \"1\"]?from-trunk-\${CHANNEL(endpoint)},\${EXTEN},1)";
$lines[] = " same => n,GotoIf(\$[\"\${DIALPLAN_EXISTS(from-trunk-\${CHANNEL(endpoint)},s,1)}\" = \"1\"]?from-trunk-\${CHANNEL(endpoint)},s,1)";
$lines[] = " same => n,Goto(from-external,\${EXTEN},1)";
$lines[] = "exten => s,1,NoOp(Appel entrant trunk [from-trunk] sur s - Appelant=\${CALLERID(num)})";
$lines[] = "exten => s,1,NoOp(Appel entrant trunk [from-trunk] sur s - Endpoint=\${CHANNEL(endpoint)} Appelant=\${CALLERID(num)})";
$lines[] = " same => n,ExecIf(\$[\"\${CALLERID(num):0:3}\" = \"+33\"]?Set(CALLERID(num)=0\${CALLERID(num):3}))";
$lines[] = " same => n,ExecIf(\$[\"\${CALLERID(num):0:4}\" = \"0033\"]?Set(CALLERID(num)=0\${CALLERID(num):4}))";
$lines[] = " same => n,GotoIf(\$[\"\${DB(blacklist/\${CALLERID(num)})}\" != \"\"]?app-blacklisted,s,1)";
$lines[] = " same => n,GotoIf(\$[\"\${DIALPLAN_EXISTS(from-trunk-\${CHANNEL(endpoint)},s,1)}\" = \"1\"]?from-trunk-\${CHANNEL(endpoint)},s,1)";
$lines[] = " same => n,Goto(from-external,s,1)";
$lines[] = "";
$lines[] = "[from-pstn]";
@@ -1326,6 +1329,30 @@ class ConfigGenerator
$lines[] = "exten => s,1,Goto(from-trunk,s,1)";
$lines[] = "";
// 2b-ter. Contextes dédiés par Trunk pour isolation des routes et préfixe CID
foreach ($trunks as $t) {
$epName = ($t['registration_mode'] === 'receive' && trim((string)($t['username'] ?? '')) !== '')
? trim((string)$t['username'])
: trim((string)$t['name']);
if ($epName === '') {
continue;
}
$trunkCtx = "from-trunk-{$epName}";
$lines[] = "; --- Contexte Entrant dédié au Trunk {$t['name']} (Endpoint: {$epName}) ---";
$lines[] = "[{$trunkCtx}]";
foreach ($this->generateTrunkInboundRoutes($t, $mohSoundsMap, "trunk_{$t['id']}") as $l) {
$lines[] = $l;
}
$lines[] = "";
if ($t['name'] !== $epName) {
$lines[] = "[from-trunk-{$t['name']}]";
$lines[] = "exten => _[+*#0-9a-zA-Z].,1,Goto({$trunkCtx},\${EXTEN},1)";
$lines[] = "exten => s,1,Goto({$trunkCtx},s,1)";
$lines[] = "";
}
}
// Contexte pour appels rejetés par la liste noire
$lines[] = "[app-blacklisted]";
$lines[] = "exten => s,1,NoOp(Appelant \${CALLERID(num)} bloque par la liste noire)";
@@ -1352,6 +1379,7 @@ class ConfigGenerator
$setMohLines[] = " same => n,Set(CHANNEL(musicclass)={$trunkMohClass})";
}
$trunkCidPrefix = trim((string)($t['cid_name_prefix'] ?? ''));
$routes = self::normalizeInboundRoutes((string)($t['did_routes_json'] ?? '[]'));
$routeIdx = 0;
foreach ($routes as $rule) {
@@ -1382,12 +1410,12 @@ class ConfigGenerator
}
$lines[] = "; Inbound Route [{$desc}] ({$t['name']}) : DID={$didPattern} / Appelant=Any{$timeInfo}";
$lines[] = "exten => {$didPattern},1,NoOp(Inbound [{$desc}] Appelant=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_did") as $rl) {
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_did", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
if ($isAnyDid) {
$lines[] = "exten => s,1,NoOp(Inbound [{$desc}] sur s Appelant=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_s") as $rl) {
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_s", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
}
@@ -1409,12 +1437,12 @@ class ConfigGenerator
foreach ($cidVariants as $cidVar) {
$vIdx++;
$lines[] = "exten => {$didPattern}/{$cidVar},1,NoOp(Inbound Filter [{$desc}] CID=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_cid_{$vIdx}") as $rl) {
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_cid_{$vIdx}", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
if ($isAnyDid) {
$lines[] = "exten => s/{$cidVar},1,NoOp(Inbound Filter [{$desc}] sur s CID=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_scid_{$vIdx}") as $rl) {
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$t['id']}_{$routeIdx}_scid_{$vIdx}", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
}
@@ -1918,6 +1946,7 @@ class ConfigGenerator
'did' => $did,
'description' => $desc ?: ($cid !== 'Any' ? "Filtre {$cid}" : "Route {$did}"),
'destination' => $dest,
'cid_prefix' => trim((string)($item['cid_prefix'] ?? '')),
'time_condition_enabled' => (int)$timeCondition,
'time_start' => $timeStart,
'time_finish' => $timeFinish,
@@ -1950,7 +1979,106 @@ class ConfigGenerator
return $out;
}
private function buildInboundRouteLines(array $rule, array $setMohLines, string $desc, string $tagId): array
private function generateTrunkInboundRoutes(array $t, array $mohSoundsMap, string $tagPrefix): array
{
$lines = [];
$mohSoundId = (int)($t['moh_sound_id'] ?? 0);
$trunkMohClass = ($mohSoundId > 0 && isset($mohSoundsMap[$mohSoundId]))
? 'moh-sound-' . $mohSoundsMap[$mohSoundId]['slug']
: 'default';
$setMohLines = [];
if ($trunkMohClass !== 'default') {
$setMohLines[] = " same => n,Set(__MOHCLASS={$trunkMohClass})";
$setMohLines[] = " same => n,Set(CHANNEL(musicclass)={$trunkMohClass})";
}
$trunkCidPrefix = trim((string)($t['cid_name_prefix'] ?? ''));
$routes = self::normalizeInboundRoutes((string)($t['did_routes_json'] ?? '[]'));
$routeIdx = 0;
$hasAnyRoute = false;
foreach ($routes as $rule) {
$routeIdx++;
$cidRaw = trim((string)($rule['cid'] ?? 'Any'));
$didRaw = trim((string)($rule['did'] ?? 'Any'));
$desc = trim((string)($rule['description'] ?? 'Route entrante'));
$dest = trim((string)($rule['destination'] ?? $t['inbound_dest'] ?? 'hangup'));
$isAnyDid = ($didRaw === '' || strcasecmp($didRaw, 'any') === 0 || $didRaw === '*' || $didRaw === '_X.');
$isAnyCid = ($cidRaw === '' || strcasecmp($cidRaw, 'any') === 0 || $cidRaw === '*');
$didPattern = $isAnyDid ? '_X.' : preg_replace('/[^0-9+XZN._\[\]\-]/', '', $didRaw);
if ($didPattern === '') {
$didPattern = '_X.';
}
$timeInfo = !empty($rule['time_condition_enabled'])
? " [Horaires: {$rule['time_start']}-{$rule['time_finish']} {$rule['weekdays']}]"
: "";
if ($isAnyCid) {
if ($didPattern === '_X.') {
$hasAnyRoute = true;
}
$lines[] = "; Inbound Route [{$desc}] : DID={$didPattern} / Appelant=Any{$timeInfo}";
$lines[] = "exten => {$didPattern},1,NoOp(Inbound [{$desc}] Appelant=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$tagPrefix}_{$routeIdx}_did", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
if ($isAnyDid) {
$lines[] = "exten => s,1,NoOp(Inbound [{$desc}] sur s Appelant=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$tagPrefix}_{$routeIdx}_s", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
}
} else {
$cidClean = preg_replace('/[^0-9+XZN._\[\]\-]/', '', $cidRaw);
if ($cidClean === '') {
continue;
}
$cidVariants = [$cidClean];
if (str_starts_with($cidClean, '_0') && strlen($cidClean) > 2 && $cidClean[2] !== '0') {
$rest = substr($cidClean, 2);
$cidVariants[] = '_0033' . $rest;
$cidVariants[] = '_+33' . $rest;
}
$lines[] = "; Inbound Route Filtrage Appelant [{$desc}] : DID={$didPattern} / CID={$cidClean}{$timeInfo}";
$vIdx = 0;
foreach ($cidVariants as $cidVar) {
$vIdx++;
$lines[] = "exten => {$didPattern}/{$cidVar},1,NoOp(Inbound Filter [{$desc}] CID=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$tagPrefix}_{$routeIdx}_cid_{$vIdx}", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
if ($isAnyDid) {
$lines[] = "exten => s/{$cidVar},1,NoOp(Inbound Filter [{$desc}] sur s CID=\${CALLERID(num)})";
foreach ($this->buildInboundRouteLines($rule, $setMohLines, $desc, "{$tagPrefix}_{$routeIdx}_scid_{$vIdx}", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
}
}
}
}
if (!$hasAnyRoute) {
$defaultDest = trim((string)($t['inbound_dest'] ?? 'hangup')) ?: 'hangup';
$lines[] = "; Fallback Inbound Trunk {$t['name']}";
$lines[] = "exten => _X.,1,NoOp(Inbound Trunk {$t['name']} par defaut vers \${EXTEN})";
foreach ($this->buildInboundRouteLines(['destination' => $defaultDest], $setMohLines, "Fallback {$t['name']}", "{$tagPrefix}_fb", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
$lines[] = "exten => s,1,NoOp(Inbound Trunk {$t['name']} sur s)";
foreach ($this->buildInboundRouteLines(['destination' => $defaultDest], $setMohLines, "Fallback {$t['name']}", "{$tagPrefix}_fbs", $trunkCidPrefix) as $rl) {
$lines[] = $rl;
}
}
return $lines;
}
private function buildInboundRouteLines(array $rule, array $setMohLines, string $desc, string $tagId, string $trunkCidPrefix = ''): array
{
$dest = trim((string)($rule['destination'] ?? 'hangup'));
$timeEnabled = !empty($rule['time_condition_enabled']);
@@ -1965,6 +2093,13 @@ class ConfigGenerator
$out[] = $sml;
}
$cidPrefix = trim((string)($rule['cid_prefix'] ?? '')) ?: $trunkCidPrefix;
if ($cidPrefix !== '' && !str_starts_with($dest, 'terminate-') && $dest !== 'hangup' && $dest !== 'busy' && $dest !== 'congestion') {
$formattedPrefix = rtrim($cidPrefix) . ' ';
$out[] = " same => n,ExecIf(\$[\"\${CALLERID(name)}\" = \"\"]?Set(CALLERID(name)=\${CALLERID(num)}))";
$out[] = " same => n,Set(CALLERID(name)={$formattedPrefix}\${CALLERID(name)})";
}
if ($timeEnabled) {
$tag = "tc_{$tagId}";
$out[] = " same => n,GotoIfTime({$timeRange},{$weekdays},*,*?{$tag}_open)";