Initial commit - NexusPBX IP Telephony Platform
This commit is contained in:
commit
c5de8d0c71
25 files changed
+18123
No files matched your search
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class AmiClient
|
||||
{
|
||||
public function __construct(
|
||||
private string $host,
|
||||
private int $port,
|
||||
private string $username,
|
||||
private string $secret,
|
||||
private int $timeout = 3
|
||||
) {}
|
||||
|
||||
public static function fromDatabase(PDO $db): self
|
||||
{
|
||||
$row = $db->query('SELECT ami_host, ami_port, ami_user, ami_secret FROM settings WHERE id = 1')->fetch();
|
||||
return new self(
|
||||
(string)($row['ami_host'] ?? '127.0.0.1'),
|
||||
(int)($row['ami_port'] ?? 5038),
|
||||
(string)($row['ami_user'] ?? 'pbxgui'),
|
||||
(string)($row['ami_secret'] ?? 'AsteriskAmi2026!')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teste la connexion et l'authentification AMI
|
||||
*/
|
||||
public function testConnection(): array
|
||||
{
|
||||
$fp = $this->connectAndLogin($banner);
|
||||
$versionOutput = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'core show version',
|
||||
]);
|
||||
$this->logoff($fp);
|
||||
|
||||
return [
|
||||
'connected' => true,
|
||||
'banner' => trim($banner),
|
||||
'version' => $this->extractCommandOutput($versionOutput),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recharge à chaud l'ensemble des modules PJSIP, force le Re-Register SIP immédiat des Trunks, et recharge le Dialplan via AMI
|
||||
*/
|
||||
public function reloadPjsipAndDialplan(): array
|
||||
{
|
||||
$fp = $this->connectAndLogin($banner);
|
||||
|
||||
// Recharge l'intégralité de la pile PJSIP (res_pjsip.so, res_pjsip_outbound_registration.so, res_pjsip_endpoint_identifier_ip.so, etc.)
|
||||
$pjsipRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'pjsip reload',
|
||||
]);
|
||||
|
||||
// Force l'envoi immédiat d'une requête SIP REGISTER vers tous les opérateurs (reprend immédiatement la main si un autre PBX était enregistré)
|
||||
$regRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'pjsip send register *all',
|
||||
]);
|
||||
|
||||
// Recharge le dialplan (extensions.conf / extensions.custom.conf)
|
||||
$dialplanRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'dialplan reload',
|
||||
]);
|
||||
|
||||
// Recharge les classes de musique d'attente (musiconhold.conf)
|
||||
$mohRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'moh reload',
|
||||
]);
|
||||
|
||||
// Recharge le module RTP (plages de ports rtpstart/rtpend & NAT)
|
||||
$rtpRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'module reload res_rtp_asterisk.so',
|
||||
]);
|
||||
|
||||
// Recharge les touches en cours d'appel (features.conf : transferts ##, *2, etc.)
|
||||
$featuresRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'module reload features',
|
||||
]);
|
||||
|
||||
// Recharge la messagerie vocale (voicemail.conf & voicemail.custom.conf)
|
||||
$voicemailRes = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'voicemail reload',
|
||||
]);
|
||||
|
||||
$this->logoff($fp);
|
||||
|
||||
// Laisse 1.25s au scheduler PJSIP (1s) + registrar opérateur (OVH) pour répondre 200 OK avant le rafraîchissement de l'état UI
|
||||
usleep(1250000);
|
||||
|
||||
return [
|
||||
'banner' => trim($banner),
|
||||
'pjsip' => $this->extractCommandOutput($pjsipRes),
|
||||
'register' => $this->extractCommandOutput($regRes),
|
||||
'dialplan' => $this->extractCommandOutput($dialplanRes),
|
||||
'moh' => $this->extractCommandOutput($mohRes),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Force l'envoi immédiat d'un paquet SIP REGISTER sur un trunk précis ou tous les trunks (*all)
|
||||
*/
|
||||
public function forceRegister(string $regObject = '*all'): string
|
||||
{
|
||||
$fp = $this->connectAndLogin($banner);
|
||||
$raw = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => 'pjsip send register ' . $regObject,
|
||||
]);
|
||||
$this->logoff($fp);
|
||||
usleep(1250000);
|
||||
return $this->extractCommandOutput($raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute une commande CLI Asterisk via AMI (ex: "pjsip show endpoints")
|
||||
*/
|
||||
public function runCommand(string $cliCommand): string
|
||||
{
|
||||
$fp = $this->connectAndLogin($banner);
|
||||
$raw = $this->sendAction($fp, [
|
||||
'Action' => 'Command',
|
||||
'Command' => $cliCommand,
|
||||
]);
|
||||
$this->logoff($fp);
|
||||
return $this->extractCommandOutput($raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
private function connectAndLogin(?string &$banner = null)
|
||||
{
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
|
||||
if (!$fp) {
|
||||
throw new RuntimeException("Connexion socket AMI refusée sur {$this->host}:{$this->port} ({$errstr} [{$errno}]). Vérifiez qu'Asterisk et manager.conf sont actifs.");
|
||||
}
|
||||
|
||||
stream_set_timeout($fp, $this->timeout);
|
||||
$banner = (string)fgets($fp, 1024);
|
||||
|
||||
$loginResp = $this->sendAction($fp, [
|
||||
'Action' => 'Login',
|
||||
'Username' => $this->username,
|
||||
'Secret' => $this->secret,
|
||||
'Events' => 'off',
|
||||
]);
|
||||
|
||||
if (!preg_match('/Response:\s*Success/i', $loginResp)) {
|
||||
fclose($fp);
|
||||
throw new RuntimeException("Échec d'authentification AMI pour l'utilisateur '{$this->username}' sur {$this->host}:{$this->port}.");
|
||||
}
|
||||
|
||||
return $fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $fp
|
||||
*/
|
||||
private function sendAction($fp, array $headers): string
|
||||
{
|
||||
$packet = '';
|
||||
foreach ($headers as $k => $v) {
|
||||
$packet .= "{$k}: {$v}\r\n";
|
||||
}
|
||||
$packet .= "\r\n";
|
||||
|
||||
fwrite($fp, $packet);
|
||||
|
||||
$response = '';
|
||||
while (!feof($fp)) {
|
||||
$line = fgets($fp, 4096);
|
||||
if ($line === false) {
|
||||
break;
|
||||
}
|
||||
$response .= $line;
|
||||
if (str_ends_with($response, "\r\n\r\n") || str_ends_with($response, "\n\n")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $fp
|
||||
*/
|
||||
private function logoff($fp): void
|
||||
{
|
||||
@fwrite($fp, "Action: Logoff\r\n\r\n");
|
||||
@fclose($fp);
|
||||
}
|
||||
|
||||
private function extractCommandOutput(string $raw): string
|
||||
{
|
||||
$lines = preg_split('/\r?\n/', trim($raw)) ?: [];
|
||||
$clean = [];
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^(Response|Privilege|ActionID|Message):/i', $line)) {
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($line, 'Output:')) {
|
||||
$val = ltrim(substr($line, 7));
|
||||
if ($val !== '') {
|
||||
$clean[] = $val;
|
||||
}
|
||||
} elseif ($line !== '--END COMMAND--') {
|
||||
$clean[] = $line;
|
||||
}
|
||||
}
|
||||
return trim(implode("\n", $clean));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user