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,871 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Database
|
||||
{
|
||||
private static ?PDO $instance = null;
|
||||
public const DB_PATH = __DIR__ . '/../storage/database.sqlite';
|
||||
|
||||
public static function getDatabasePath(): string
|
||||
{
|
||||
return self::DB_PATH;
|
||||
}
|
||||
|
||||
public static function resetConnection(): void
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
|
||||
public static function getConnection(bool $forceReconnect = false): PDO
|
||||
{
|
||||
if (self::$instance === null || $forceReconnect) {
|
||||
self::$instance = null;
|
||||
$dir = dirname(self::DB_PATH);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0770, true);
|
||||
}
|
||||
|
||||
$isNew = !file_exists(self::DB_PATH);
|
||||
|
||||
self::$instance = new PDO('sqlite:' . self::DB_PATH, null, null, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
self::$instance->exec('PRAGMA foreign_keys = ON;');
|
||||
self::$instance->exec('PRAGMA journal_mode = WAL;');
|
||||
self::$instance->exec('PRAGMA busy_timeout = 5000;');
|
||||
|
||||
self::initializeSchema();
|
||||
|
||||
if ($isNew) {
|
||||
@chmod(self::DB_PATH, 0660);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function initializeSchema(): void
|
||||
{
|
||||
$db = self::$instance;
|
||||
if (!$db) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = <<<SQL
|
||||
-- 1. Table settings : Paramètres globaux du serveur Asterisk & AMI
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
domain TEXT NOT NULL DEFAULT 'pbx.local',
|
||||
external_ip TEXT NOT NULL DEFAULT '',
|
||||
local_net TEXT NOT NULL DEFAULT '192.168.1.0/24',
|
||||
udp_port INTEGER NOT NULL DEFAULT 5060,
|
||||
tls_port INTEGER NOT NULL DEFAULT 5061,
|
||||
letsencrypt_domain TEXT NOT NULL DEFAULT '',
|
||||
ami_host TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
ami_port INTEGER NOT NULL DEFAULT 5038,
|
||||
ami_user TEXT NOT NULL DEFAULT 'pbxgui',
|
||||
ami_secret TEXT NOT NULL DEFAULT 'AsteriskAmi2026!',
|
||||
wizard_completed INTEGER NOT NULL DEFAULT 0,
|
||||
tls_verify_client INTEGER NOT NULL DEFAULT 0,
|
||||
external_udp_port INTEGER NOT NULL DEFAULT 0,
|
||||
external_tls_port INTEGER NOT NULL DEFAULT 0,
|
||||
local_networks_json TEXT NOT NULL DEFAULT '["10.10.0.0/16","192.168.1.0/24"]',
|
||||
rtp_start INTEGER NOT NULL DEFAULT 10000,
|
||||
rtp_end INTEGER NOT NULL DEFAULT 20000,
|
||||
rtp_checksums INTEGER NOT NULL DEFAULT 1,
|
||||
rtp_strict INTEGER NOT NULL DEFAULT 1,
|
||||
rtp_timeout INTEGER NOT NULL DEFAULT 30,
|
||||
rtp_keepalive INTEGER NOT NULL DEFAULT 15,
|
||||
allow_anonymous_sip INTEGER NOT NULL DEFAULT 0,
|
||||
last_applied_at DATETIME DEFAULT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO settings (id) VALUES (1);
|
||||
|
||||
-- 2. Table users : Authentification sur l'interface d'administration
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(64) NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 3. Table extensions : Postes téléphoniques PJSIP (UDP interne ou TLS+SRTP distant)
|
||||
CREATE TABLE IF NOT EXISTS extensions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
extension VARCHAR(16) NOT NULL UNIQUE,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
secret VARCHAR(128) NOT NULL,
|
||||
transport VARCHAR(8) NOT NULL DEFAULT 'udp' CHECK (transport IN ('udp', 'tls')),
|
||||
encryption INTEGER NOT NULL DEFAULT 0 CHECK (encryption IN (0, 1, 2)),
|
||||
codecs VARCHAR(64) NOT NULL DEFAULT 'alaw,ulaw,opus',
|
||||
max_contacts INTEGER NOT NULL DEFAULT 2,
|
||||
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 4. Table groups : Groupes d'appel (Ring Groups)
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
extension VARCHAR(16) NOT NULL UNIQUE,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
strategy VARCHAR(16) NOT NULL DEFAULT 'ringall' CHECK (strategy IN ('ringall', 'hunt', 'random')),
|
||||
ring_time INTEGER NOT NULL DEFAULT 20,
|
||||
members TEXT NOT NULL DEFAULT '[]',
|
||||
failover_dest VARCHAR(64) NOT NULL DEFAULT 'hangup',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 5. Table trunks : Opérateurs SIP entrants / sortants (Configuration PJSIP complète)
|
||||
CREATE TABLE IF NOT EXISTS trunks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
host VARCHAR(128) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
outbound_proxy VARCHAR(128) NOT NULL DEFAULT '',
|
||||
transport VARCHAR(8) NOT NULL DEFAULT 'udp',
|
||||
auth_mode VARCHAR(16) NOT NULL DEFAULT 'userpass',
|
||||
username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
auth_username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
secret VARCHAR(128) NOT NULL DEFAULT '',
|
||||
register INTEGER NOT NULL DEFAULT 1 CHECK (register IN (0, 1)),
|
||||
server_uri VARCHAR(192) NOT NULL DEFAULT '',
|
||||
client_uri VARCHAR(192) NOT NULL DEFAULT '',
|
||||
contact_user VARCHAR(64) NOT NULL DEFAULT '',
|
||||
expiration INTEGER NOT NULL DEFAULT 3600,
|
||||
retry_interval INTEGER NOT NULL DEFAULT 60,
|
||||
line_support INTEGER NOT NULL DEFAULT 1 CHECK (line_support IN (0, 1)),
|
||||
from_user VARCHAR(64) NOT NULL DEFAULT '',
|
||||
from_domain VARCHAR(128) NOT NULL DEFAULT '',
|
||||
outbound_callerid VARCHAR(128) NOT NULL DEFAULT '',
|
||||
send_pai INTEGER NOT NULL DEFAULT 1 CHECK (send_pai IN (0, 1)),
|
||||
send_rpid INTEGER NOT NULL DEFAULT 0 CHECK (send_rpid IN (0, 1)),
|
||||
trust_id_inbound INTEGER NOT NULL DEFAULT 1 CHECK (trust_id_inbound IN (0, 1)),
|
||||
codecs VARCHAR(64) NOT NULL DEFAULT 'alaw,ulaw,g722',
|
||||
dtmf_mode VARCHAR(16) NOT NULL DEFAULT 'rfc4733',
|
||||
media_encryption VARCHAR(16) NOT NULL DEFAULT 'no',
|
||||
nat_symmetric INTEGER NOT NULL DEFAULT 1 CHECK (nat_symmetric IN (0, 1)),
|
||||
direct_media INTEGER NOT NULL DEFAULT 0 CHECK (direct_media IN (0, 1)),
|
||||
qualify_frequency INTEGER NOT NULL DEFAULT 60,
|
||||
max_channels INTEGER NOT NULL DEFAULT 0,
|
||||
match_ips TEXT NOT NULL DEFAULT '',
|
||||
context VARCHAR(64) NOT NULL DEFAULT 'from-external',
|
||||
inbound_dest VARCHAR(64) NOT NULL DEFAULT 'ivr-accueil',
|
||||
did_routes_json TEXT NOT NULL DEFAULT '{}',
|
||||
outbound_prefix VARCHAR(32) NOT NULL DEFAULT '',
|
||||
custom_endpoint_params TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 6. Table ivrs : Serveurs vocaux interactifs (SVI / IVR)
|
||||
CREATE TABLE IF NOT EXISTS ivrs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
audio_file VARCHAR(128) NOT NULL DEFAULT 'hello-world',
|
||||
timeout INTEGER NOT NULL DEFAULT 5,
|
||||
options_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 7. Table outbound_routes : Routes Sortantes (Priorité, Séquence Trunks, Dial Patterns, Droits Groupes & Postes)
|
||||
CREATE TABLE IF NOT EXISTS outbound_routes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
priority INTEGER NOT NULL DEFAULT 10,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
outbound_callerid VARCHAR(128) NOT NULL DEFAULT '',
|
||||
override_ext_cid INTEGER NOT NULL DEFAULT 0 CHECK (override_ext_cid IN (0, 1)),
|
||||
route_password VARCHAR(64) NOT NULL DEFAULT '',
|
||||
route_type VARCHAR(32) NOT NULL DEFAULT 'normal',
|
||||
moh_class VARCHAR(64) NOT NULL DEFAULT 'default',
|
||||
trunk_sequence_json TEXT NOT NULL DEFAULT '[]',
|
||||
dial_patterns_json TEXT NOT NULL DEFAULT '[]',
|
||||
permission_mode VARCHAR(16) NOT NULL DEFAULT 'all',
|
||||
allowed_groups_json TEXT NOT NULL DEFAULT '[]',
|
||||
allowed_extensions_json TEXT NOT NULL DEFAULT '[]',
|
||||
continue_next_route INTEGER NOT NULL DEFAULT 1 CHECK (continue_next_route IN (0, 1)),
|
||||
congestion_dest VARCHAR(64) NOT NULL DEFAULT 'terminate-congestion',
|
||||
record_mode VARCHAR(16) NOT NULL DEFAULT 'dontcare',
|
||||
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 8. Table extension_groups : Groupes d'extensions (Sites, Services, Droits Routes Sortantes - ex: Nice, Paris)
|
||||
CREATE TABLE IF NOT EXISTS extension_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
description VARCHAR(128) NOT NULL DEFAULT '',
|
||||
color VARCHAR(32) NOT NULL DEFAULT 'cyan',
|
||||
members TEXT NOT NULL DEFAULT '[]',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 9. Table sounds : Médiathèque & Fichiers Sons (MoH, annonces, IVR, congestion classés par site et usage)
|
||||
CREATE TABLE IF NOT EXISTS sounds (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
slug VARCHAR(128) NOT NULL UNIQUE,
|
||||
site_category VARCHAR(64) NOT NULL DEFAULT 'Global',
|
||||
usage_type VARCHAR(32) NOT NULL DEFAULT 'announcement',
|
||||
description VARCHAR(255) NOT NULL DEFAULT '',
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
original_filename VARCHAR(255) NOT NULL DEFAULT '',
|
||||
duration_sec INTEGER NOT NULL DEFAULT 0,
|
||||
file_size INTEGER NOT NULL DEFAULT 0,
|
||||
format VARCHAR(16) NOT NULL DEFAULT 'wav',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 10. Table sound_sites : Catégories de sites pour les sons (Global, Site Nice, Site Paris...)
|
||||
CREATE TABLE IF NOT EXISTS sound_sites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
description VARCHAR(255) NOT NULL DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 11. Table certificates : Gestionnaire de Certificats SSL / TLS (Let's Encrypt & Custom)
|
||||
CREATE TABLE IF NOT EXISTS certificates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL DEFAULT 'custom', -- letsencrypt, custom, selfsigned
|
||||
primary_domain VARCHAR(255) NOT NULL,
|
||||
alt_names TEXT NOT NULL DEFAULT '',
|
||||
issuer VARCHAR(255) NOT NULL DEFAULT '',
|
||||
valid_from DATETIME DEFAULT NULL,
|
||||
valid_to DATETIME DEFAULT NULL,
|
||||
days_remaining INTEGER NOT NULL DEFAULT 0,
|
||||
cert_path TEXT NOT NULL,
|
||||
key_path TEXT NOT NULL,
|
||||
chain_path TEXT NOT NULL DEFAULT '',
|
||||
fullchain_path TEXT NOT NULL DEFAULT '',
|
||||
email VARCHAR(255) NOT NULL DEFAULT '',
|
||||
auto_renew INTEGER NOT NULL DEFAULT 1,
|
||||
is_default INTEGER NOT NULL DEFAULT 0,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'valid', -- valid, expiring_soon, expired, error
|
||||
last_error TEXT NOT NULL DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 12. Table feature_codes : Codes de Fonctions Téléphoniques & Services Asterisk
|
||||
CREATE TABLE IF NOT EXISTS feature_codes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
feature_key VARCHAR(64) UNIQUE NOT NULL,
|
||||
code VARCHAR(16) NOT NULL,
|
||||
default_code VARCHAR(16) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
category VARCHAR(64) NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
SQL;
|
||||
|
||||
$db->exec($schema);
|
||||
self::migrateSettingsTable($db);
|
||||
self::migrateExtensionsTable($db);
|
||||
self::migrateTrunksTable($db);
|
||||
self::migrateOutboundRoutesTable($db);
|
||||
self::migrateExtensionGroupsTable($db);
|
||||
self::migrateSoundsTable($db);
|
||||
self::migrateSoundSitesTable($db);
|
||||
self::migrateIvrsTable($db);
|
||||
self::migrateCertificatesTable($db);
|
||||
self::migrateFeatureCodesTable($db);
|
||||
|
||||
// Création du compte admin par défaut (admin / admin123) s'il n'existe aucun utilisateur
|
||||
$userCount = (int)$db->query('SELECT COUNT(*) FROM users')->fetchColumn();
|
||||
if ($userCount === 0) {
|
||||
$stmt = $db->prepare('INSERT INTO users (username, password_hash) VALUES (:u, :p)');
|
||||
$stmt->execute([
|
||||
':u' => 'admin',
|
||||
':p' => password_hash('admin123', PASSWORD_DEFAULT),
|
||||
]);
|
||||
}
|
||||
|
||||
// Données de démonstration initiales si la table extensions est vide
|
||||
$extCount = (int)$db->query('SELECT COUNT(*) FROM extensions')->fetchColumn();
|
||||
if ($extCount === 0) {
|
||||
$stmtExt = $db->prepare('
|
||||
INSERT INTO extensions (extension, name, secret, transport, encryption, codecs, max_contacts, enabled)
|
||||
VALUES (:ext, :name, :secret, :transport, :encryption, :codecs, :max_contacts, 1)
|
||||
');
|
||||
$stmtExt->execute([
|
||||
':ext' => '101',
|
||||
':name' => 'Accueil Bureau (LAN)',
|
||||
':secret' => 'LanPass101!Sip',
|
||||
':transport' => 'udp',
|
||||
':encryption' => 0,
|
||||
':codecs' => 'alaw,ulaw,g722',
|
||||
':max_contacts' => 2,
|
||||
]);
|
||||
$stmtExt->execute([
|
||||
':ext' => '102',
|
||||
':name' => 'Direction Nomade (TLS/SRTP)',
|
||||
':secret' => 'TlsSecure102!Sip',
|
||||
':transport' => 'tls',
|
||||
':encryption' => 1,
|
||||
':codecs' => 'opus,g722,alaw',
|
||||
':max_contacts' => 3,
|
||||
]);
|
||||
|
||||
$stmtGrp = $db->prepare('
|
||||
INSERT INTO groups (extension, name, strategy, ring_time, members, failover_dest)
|
||||
VALUES (:ext, :name, :strategy, :ring_time, :members, :failover)
|
||||
');
|
||||
$stmtGrp->execute([
|
||||
':ext' => '600',
|
||||
':name' => 'Support Général',
|
||||
':strategy' => 'ringall',
|
||||
':ring_time' => 25,
|
||||
':members' => json_encode(['101', '102']),
|
||||
':failover' => 'hangup',
|
||||
]);
|
||||
|
||||
$stmtIvr = $db->prepare('
|
||||
INSERT INTO ivrs (name, audio_file, timeout, options_json)
|
||||
VALUES (:name, :audio, :timeout, :options)
|
||||
');
|
||||
$stmtIvr->execute([
|
||||
':name' => 'ivr-accueil',
|
||||
':audio' => 'custom/bienvenue-entreprise',
|
||||
':timeout' => 5,
|
||||
':options' => json_encode([
|
||||
'1' => 'ext-101',
|
||||
'2' => 'grp-600',
|
||||
'0' => 'ext-102',
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateSettingsTable(PDO $db): void
|
||||
{
|
||||
$cols = $db->query('PRAGMA table_info(settings)')->fetchAll();
|
||||
$existing = array_column($cols, 'name');
|
||||
|
||||
if (!in_array('company_name', $existing, true)) {
|
||||
$db->exec("ALTER TABLE settings ADD COLUMN company_name VARCHAR(128) NOT NULL DEFAULT ''");
|
||||
}
|
||||
if (!in_array('company_logo', $existing, true)) {
|
||||
$db->exec("ALTER TABLE settings ADD COLUMN company_logo TEXT NOT NULL DEFAULT ''");
|
||||
}
|
||||
if (!in_array('default_moh_sound_id', $existing, true)) {
|
||||
$db->exec("ALTER TABLE settings ADD COLUMN default_moh_sound_id INTEGER DEFAULT 2");
|
||||
}
|
||||
if (!in_array('tls_verify_client', $existing, true)) {
|
||||
$db->exec("ALTER TABLE settings ADD COLUMN tls_verify_client INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
|
||||
$extraSettings = [
|
||||
'external_udp_port' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'external_tls_port' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'local_networks_json' => "TEXT NOT NULL DEFAULT '[\"10.10.0.0/16\",\"192.168.1.0/24\"]'",
|
||||
'rtp_start' => "INTEGER NOT NULL DEFAULT 10000",
|
||||
'rtp_end' => "INTEGER NOT NULL DEFAULT 20000",
|
||||
'rtp_checksums' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'rtp_strict' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'rtp_timeout' => "INTEGER NOT NULL DEFAULT 30",
|
||||
'rtp_keepalive' => "INTEGER NOT NULL DEFAULT 15",
|
||||
'allow_anonymous_sip' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'system_language' => "VARCHAR(16) NOT NULL DEFAULT 'fr'",
|
||||
'backup_auto_enabled' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'backup_frequency' => "VARCHAR(32) NOT NULL DEFAULT 'daily'",
|
||||
'backup_retention' => "INTEGER NOT NULL DEFAULT 7",
|
||||
'backup_storage_type' => "VARCHAR(16) NOT NULL DEFAULT 'local'",
|
||||
'backup_ssh_host' => "VARCHAR(128) NOT NULL DEFAULT ''",
|
||||
'backup_ssh_port' => "INTEGER NOT NULL DEFAULT 22",
|
||||
'backup_ssh_user' => "VARCHAR(64) NOT NULL DEFAULT ''",
|
||||
'backup_ssh_auth_type'=> "VARCHAR(16) NOT NULL DEFAULT 'password'",
|
||||
'backup_ssh_password' => "TEXT NOT NULL DEFAULT ''",
|
||||
'backup_ssh_remote_path' => "VARCHAR(255) NOT NULL DEFAULT '/backups/nexus'",
|
||||
'backup_last_run' => "VARCHAR(32) DEFAULT NULL",
|
||||
'backup_last_status' => "TEXT NOT NULL DEFAULT ''",
|
||||
'firewall_enabled' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'admin_https_port' => "INTEGER NOT NULL DEFAULT 8443",
|
||||
'certbot_strict_port80' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'fail2ban_sip_maxretry' => "INTEGER NOT NULL DEFAULT 3",
|
||||
'fail2ban_sip_findtime' => "INTEGER NOT NULL DEFAULT 600",
|
||||
'fail2ban_sip_bantime' => "INTEGER NOT NULL DEFAULT 3600",
|
||||
'fail2ban_web_maxretry' => "INTEGER NOT NULL DEFAULT 3",
|
||||
'fail2ban_web_bantime' => "INTEGER NOT NULL DEFAULT 3600",
|
||||
'fail2ban_whitelist_ips' => "TEXT NOT NULL DEFAULT '127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 82.122.84.60'",
|
||||
];
|
||||
foreach ($extraSettings as $colName => $colDef) {
|
||||
if (!in_array($colName, $existing, true)) {
|
||||
$db->exec("ALTER TABLE settings ADD COLUMN {$colName} {$colDef}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateExtensionsTable(PDO $db): void
|
||||
{
|
||||
$sql = $db->query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'extensions'")->fetchColumn();
|
||||
if ($sql && str_contains($sql, "CHECK (encryption IN (0, 1))")) {
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$db->exec("CREATE TABLE extensions_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
extension VARCHAR(16) NOT NULL UNIQUE,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
secret VARCHAR(128) NOT NULL,
|
||||
transport VARCHAR(8) NOT NULL DEFAULT 'udp' CHECK (transport IN ('udp', 'tls')),
|
||||
encryption INTEGER NOT NULL DEFAULT 0 CHECK (encryption IN (0, 1, 2)),
|
||||
codecs VARCHAR(64) NOT NULL DEFAULT 'alaw,ulaw,opus',
|
||||
max_contacts INTEGER NOT NULL DEFAULT 2,
|
||||
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
$db->exec("INSERT INTO extensions_new SELECT * FROM extensions");
|
||||
$db->exec("DROP TABLE extensions");
|
||||
$db->exec("ALTER TABLE extensions_new RENAME TO extensions");
|
||||
$db->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$cols = $db->query('PRAGMA table_info(extensions)')->fetchAll();
|
||||
$existing = array_column($cols, 'name');
|
||||
|
||||
$extraExtensionCols = [
|
||||
'voicemail_enabled' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'voicemail_pin' => "VARCHAR(32) NOT NULL DEFAULT ''",
|
||||
'force_pin_change' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'voicemail_skip_pin' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'voicemail_email' => "VARCHAR(128) NOT NULL DEFAULT ''",
|
||||
'voicemail_attach' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'voicemail_delete' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'ring_time' => "INTEGER NOT NULL DEFAULT 20",
|
||||
];
|
||||
foreach ($extraExtensionCols as $colName => $colDef) {
|
||||
if (!in_array($colName, $existing, true)) {
|
||||
$db->exec("ALTER TABLE extensions ADD COLUMN {$colName} {$colDef}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateTrunksTable(PDO $db): void
|
||||
{
|
||||
$cols = $db->query('PRAGMA table_info(trunks)')->fetchAll();
|
||||
$existing = array_column($cols, 'name');
|
||||
|
||||
if (!in_array('outbound_proxy', $existing, true)) {
|
||||
// Migration sans perte de la table trunks vers le schéma PJSIP complet (avec support UDP/TCP/TLS)
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$db->exec('ALTER TABLE trunks RENAME TO trunks_old');
|
||||
$db->exec("
|
||||
CREATE TABLE trunks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL UNIQUE,
|
||||
host VARCHAR(128) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
outbound_proxy VARCHAR(128) NOT NULL DEFAULT '',
|
||||
transport VARCHAR(8) NOT NULL DEFAULT 'udp',
|
||||
auth_mode VARCHAR(16) NOT NULL DEFAULT 'userpass',
|
||||
username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
auth_username VARCHAR(64) NOT NULL DEFAULT '',
|
||||
secret VARCHAR(128) NOT NULL DEFAULT '',
|
||||
register INTEGER NOT NULL DEFAULT 1 CHECK (register IN (0, 1)),
|
||||
server_uri VARCHAR(192) NOT NULL DEFAULT '',
|
||||
client_uri VARCHAR(192) NOT NULL DEFAULT '',
|
||||
contact_user VARCHAR(64) NOT NULL DEFAULT '',
|
||||
expiration INTEGER NOT NULL DEFAULT 3600,
|
||||
retry_interval INTEGER NOT NULL DEFAULT 60,
|
||||
line_support INTEGER NOT NULL DEFAULT 1 CHECK (line_support IN (0, 1)),
|
||||
from_user VARCHAR(64) NOT NULL DEFAULT '',
|
||||
from_domain VARCHAR(128) NOT NULL DEFAULT '',
|
||||
outbound_callerid VARCHAR(128) NOT NULL DEFAULT '',
|
||||
send_pai INTEGER NOT NULL DEFAULT 1 CHECK (send_pai IN (0, 1)),
|
||||
send_rpid INTEGER NOT NULL DEFAULT 0 CHECK (send_rpid IN (0, 1)),
|
||||
trust_id_inbound INTEGER NOT NULL DEFAULT 1 CHECK (trust_id_inbound IN (0, 1)),
|
||||
codecs VARCHAR(64) NOT NULL DEFAULT 'alaw,ulaw,g722',
|
||||
dtmf_mode VARCHAR(16) NOT NULL DEFAULT 'rfc4733',
|
||||
media_encryption VARCHAR(16) NOT NULL DEFAULT 'no',
|
||||
nat_symmetric INTEGER NOT NULL DEFAULT 1 CHECK (nat_symmetric IN (0, 1)),
|
||||
direct_media INTEGER NOT NULL DEFAULT 0 CHECK (direct_media IN (0, 1)),
|
||||
qualify_frequency INTEGER NOT NULL DEFAULT 60,
|
||||
max_channels INTEGER NOT NULL DEFAULT 0,
|
||||
match_ips TEXT NOT NULL DEFAULT '',
|
||||
context VARCHAR(64) NOT NULL DEFAULT 'from-external',
|
||||
inbound_dest VARCHAR(64) NOT NULL DEFAULT 'ivr-accueil',
|
||||
did_routes_json TEXT NOT NULL DEFAULT '{}',
|
||||
outbound_prefix VARCHAR(32) NOT NULL DEFAULT '',
|
||||
custom_endpoint_params TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
");
|
||||
$db->exec("
|
||||
INSERT INTO trunks (id, name, host, port, username, secret, transport, register, codecs, inbound_dest, enabled, created_at)
|
||||
SELECT id, name, host, port, username, secret, transport, register, codecs, inbound_dest, enabled, created_at
|
||||
FROM trunks_old
|
||||
");
|
||||
$db->exec('DROP TABLE trunks_old');
|
||||
$db->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
$cols = $db->query('PRAGMA table_info(trunks)')->fetchAll();
|
||||
$existing = array_column($cols, 'name');
|
||||
}
|
||||
|
||||
// Colonnes universelles PJSIP supplémentaires (Session Timers, Fax T.38, Diversion, Retries, Langue, Keepalive)
|
||||
$extraColumns = [
|
||||
'max_retries' => "INTEGER NOT NULL DEFAULT 10000",
|
||||
'forbidden_retry_interval' => "INTEGER NOT NULL DEFAULT 600",
|
||||
'fatal_retry_interval' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'send_diversion' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'timers' => "VARCHAR(16) NOT NULL DEFAULT 'yes'",
|
||||
'language' => "VARCHAR(16) NOT NULL DEFAULT 'fr'",
|
||||
'rtp_keepalive' => "INTEGER NOT NULL DEFAULT 15",
|
||||
't38_udptl' => "VARCHAR(16) NOT NULL DEFAULT 'no'",
|
||||
'fax_detect' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'inband_progress' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'custom_reg_params' => "TEXT NOT NULL DEFAULT ''",
|
||||
'custom_aor_params' => "TEXT NOT NULL DEFAULT ''",
|
||||
'moh_sound_id' => "INTEGER NOT NULL DEFAULT 0",
|
||||
];
|
||||
|
||||
foreach ($extraColumns as $colName => $colDef) {
|
||||
if (!in_array($colName, $existing, true)) {
|
||||
$db->exec("ALTER TABLE trunks ADD COLUMN {$colName} {$colDef}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateOutboundRoutesTable(PDO $db): void
|
||||
{
|
||||
$count = (int)$db->query('SELECT COUNT(*) FROM outbound_routes')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
// Vérifie quels trunks et postes existent déjà pour pré-initialiser la route sortante principale
|
||||
$trunks = $db->query('SELECT name, outbound_callerid FROM trunks ORDER BY id ASC')->fetchAll();
|
||||
$firstTrunkName = !empty($trunks) ? (string)$trunks[0]['name'] : 'ovh-telecom';
|
||||
$firstTrunkCid = !empty($trunks) && trim((string)$trunks[0]['outbound_callerid']) !== ''
|
||||
? trim((string)$trunks[0]['outbound_callerid'])
|
||||
: 'Sytelis Consulting <0422560185>';
|
||||
|
||||
$exts = $db->query('SELECT extension FROM extensions ORDER BY CAST(extension AS INTEGER) ASC')->fetchAll(PDO::FETCH_COLUMN);
|
||||
$grps = $db->query('SELECT extension FROM groups ORDER BY CAST(extension AS INTEGER) ASC')->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$stmt = $db->prepare('
|
||||
INSERT INTO outbound_routes (
|
||||
priority, name, outbound_callerid, override_ext_cid, route_password,
|
||||
route_type, moh_class, trunk_sequence_json, dial_patterns_json,
|
||||
permission_mode, allowed_groups_json, allowed_extensions_json,
|
||||
continue_next_route, congestion_dest, record_mode, enabled
|
||||
) VALUES (
|
||||
:priority, :name, :cid, :override_cid, :pwd,
|
||||
:rtype, :moh, :trunks, :patterns,
|
||||
:pmode, :agroups, :aexts,
|
||||
:cont, :cdest, :rec, 1
|
||||
)
|
||||
');
|
||||
|
||||
// Route 1 : OVH (principale)
|
||||
$stmt->execute([
|
||||
':priority' => 1,
|
||||
':name' => 'OVH',
|
||||
':cid' => $firstTrunkCid,
|
||||
':override_cid' => 0,
|
||||
':pwd' => '',
|
||||
':rtype' => 'normal',
|
||||
':moh' => 'default',
|
||||
':trunks' => json_encode([$firstTrunkName]),
|
||||
':patterns' => json_encode([
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '0[1-59]XXXXXXXX', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '0[67]XXXXXXXX', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '080[0-9]XXXXXX', 'callerid' => ''],
|
||||
]),
|
||||
':pmode' => 'all',
|
||||
':agroups' => json_encode($grps),
|
||||
':aexts' => json_encode($exts),
|
||||
':cont' => 1,
|
||||
':cdest' => 'terminate-busy',
|
||||
':rec' => 'dontcare',
|
||||
]);
|
||||
|
||||
// Route 2 : Emergency (Numéros d'urgence)
|
||||
$stmt->execute([
|
||||
':priority' => 2,
|
||||
':name' => 'Emergency',
|
||||
':cid' => $firstTrunkCid,
|
||||
':override_cid' => 1,
|
||||
':pwd' => '',
|
||||
':rtype' => 'emergency',
|
||||
':moh' => 'default',
|
||||
':trunks' => json_encode([$firstTrunkName]),
|
||||
':patterns' => json_encode([
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '15', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '17', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '18', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '112', 'callerid' => ''],
|
||||
['prepend' => '', 'prefix' => '', 'match_pattern' => '114', 'callerid' => ''],
|
||||
]),
|
||||
':pmode' => 'all',
|
||||
':agroups' => json_encode($grps),
|
||||
':aexts' => json_encode($exts),
|
||||
':cont' => 1,
|
||||
':cdest' => 'terminate-congestion',
|
||||
':rec' => 'dontcare',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateExtensionGroupsTable(PDO $db): void
|
||||
{
|
||||
$count = (int)$db->query('SELECT COUNT(*) FROM extension_groups')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
$stmt = $db->prepare('INSERT INTO extension_groups (name, description, color, members) VALUES (:n, :d, :c, :m)');
|
||||
$stmt->execute([
|
||||
':n' => 'Groupe Nice',
|
||||
':d' => 'Extensions du site de Nice (Trunk local Livebox)',
|
||||
':c' => 'emerald',
|
||||
':m' => json_encode(['101', '102']),
|
||||
]);
|
||||
$stmt->execute([
|
||||
':n' => 'Groupe Paris',
|
||||
':d' => 'Extensions du site de Paris (Trunk principal OVH)',
|
||||
':c' => 'indigo',
|
||||
':m' => json_encode(['103']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateSoundsTable(PDO $db): void
|
||||
{
|
||||
$count = (int)$db->query('SELECT COUNT(*) FROM sounds')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
$stmt = $db->prepare('
|
||||
INSERT INTO sounds (name, slug, site_category, usage_type, description, filename, original_filename, duration_sec, file_size, format)
|
||||
VALUES (:name, :slug, :site, :usage, :desc, :file, :orig, :dur, :size, :fmt)
|
||||
');
|
||||
|
||||
$demoSounds = [
|
||||
[
|
||||
':name' => 'Message d\'accueil SVI Standard',
|
||||
':slug' => 'bienvenue-entreprise',
|
||||
':site' => 'Global',
|
||||
':usage' => 'ivr',
|
||||
':desc' => 'Jingle d\'accueil principal du serveur vocal interactif',
|
||||
':file' => 'bienvenue-entreprise.wav',
|
||||
':orig' => 'bienvenue-entreprise.wav',
|
||||
':dur' => 2,
|
||||
':size' => 31800,
|
||||
':fmt' => 'wav',
|
||||
],
|
||||
[
|
||||
':name' => 'Musique d\'attente Standard',
|
||||
':slug' => 'attente-musicale-standard',
|
||||
':site' => 'Global',
|
||||
':usage' => 'moh',
|
||||
':desc' => 'Mélodie douce d\'attente (MoH) par défaut pour les appels mis en garde',
|
||||
':file' => 'attente-musicale-standard.wav',
|
||||
':orig' => 'attente-musicale-standard.wav',
|
||||
':dur' => 4,
|
||||
':size' => 63600,
|
||||
':fmt' => 'wav',
|
||||
],
|
||||
[
|
||||
':name' => 'Message Ligne Occupée & Congestion',
|
||||
':slug' => 'ligne-occupee-congestion',
|
||||
':site' => 'Global',
|
||||
':usage' => 'congestion',
|
||||
':desc' => 'Tonalité cadencée d\'occupation si aucun trunk ou poste n\'est disponible',
|
||||
':file' => 'ligne-occupee-congestion.wav',
|
||||
':orig' => 'ligne-occupee-congestion.wav',
|
||||
':dur' => 1,
|
||||
':size' => 16000,
|
||||
':fmt' => 'wav',
|
||||
],
|
||||
[
|
||||
':name' => 'Annonce Fermeture Nice',
|
||||
':slug' => 'annonce-fermeture-nice',
|
||||
':site' => 'Site Nice',
|
||||
':usage' => 'announcement',
|
||||
':desc' => 'Message informant des horaires d\'ouverture du bureau de Nice',
|
||||
':file' => 'annonce-fermeture-nice.wav',
|
||||
':orig' => 'annonce-fermeture-nice.wav',
|
||||
':dur' => 2,
|
||||
':size' => 28800,
|
||||
':fmt' => 'wav',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($demoSounds as $ds) {
|
||||
$stmt->execute($ds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateSoundSitesTable(PDO $db): void
|
||||
{
|
||||
$count = (int)$db->query('SELECT COUNT(*) FROM sound_sites')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
$stmt = $db->prepare('INSERT INTO sound_sites (name, description) VALUES (:n, :d)');
|
||||
$stmt->execute([':n' => 'Global', ':d' => 'Fichiers généraux partagés par tout le PBX']);
|
||||
$stmt->execute([':n' => 'Site Nice', ':d' => 'Annonces et musiques spécifiques au site de Nice']);
|
||||
$stmt->execute([':n' => 'Site Paris', ':d' => 'Annonces et musiques spécifiques au site de Paris']);
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateIvrsTable(PDO $db): void
|
||||
{
|
||||
$cols = $db->query("PRAGMA table_info(ivrs)")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$existing = array_column($cols, 'name');
|
||||
|
||||
$extraColumns = [
|
||||
'description' => "TEXT NOT NULL DEFAULT ''",
|
||||
'direct_dial' => "INTEGER NOT NULL DEFAULT 1",
|
||||
'invalid_retries' => "INTEGER NOT NULL DEFAULT 3",
|
||||
'invalid_sound_id' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'invalid_dest' => "VARCHAR(64) NOT NULL DEFAULT 'hangup'",
|
||||
'timeout_retries' => "INTEGER NOT NULL DEFAULT 2",
|
||||
'timeout_sound_id' => "INTEGER NOT NULL DEFAULT 0",
|
||||
'timeout_dest' => "VARCHAR(64) NOT NULL DEFAULT 'hangup'",
|
||||
];
|
||||
|
||||
foreach ($extraColumns as $colName => $colDef) {
|
||||
if (!in_array($colName, $existing, true)) {
|
||||
$db->exec("ALTER TABLE ivrs ADD COLUMN {$colName} {$colDef}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateCertificatesTable(PDO $db): void
|
||||
{
|
||||
$count = (int)$db->query('SELECT COUNT(*) FROM certificates')->fetchColumn();
|
||||
if ($count === 0) {
|
||||
$defaultCert = '/etc/asterisk/keys/asterisk.crt';
|
||||
$defaultKey = '/etc/asterisk/keys/asterisk.key';
|
||||
|
||||
$domain = 'pbx.local';
|
||||
$issuer = 'Asterisk PBX System';
|
||||
$validFrom = date('Y-m-d H:i:s');
|
||||
$validTo = date('Y-m-d H:i:s', time() + (3650 * 86400));
|
||||
$daysRemaining = 3650;
|
||||
|
||||
if (file_exists($defaultCert)) {
|
||||
$raw = @file_get_contents($defaultCert);
|
||||
if ($raw) {
|
||||
$parsed = @openssl_x509_parse($raw);
|
||||
if ($parsed) {
|
||||
$domain = $parsed['subject']['CN'] ?? 'pbx.local';
|
||||
$issuer = $parsed['issuer']['O'] ?? ($parsed['issuer']['CN'] ?? 'Asterisk Self-Signed CA');
|
||||
if (!empty($parsed['validFrom_time_t'])) {
|
||||
$validFrom = date('Y-m-d H:i:s', $parsed['validFrom_time_t']);
|
||||
}
|
||||
if (!empty($parsed['validTo_time_t'])) {
|
||||
$validTo = date('Y-m-d H:i:s', $parsed['validTo_time_t']);
|
||||
$daysRemaining = (int)round(($parsed['validTo_time_t'] - time()) / 86400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare('INSERT INTO certificates (
|
||||
name, type, primary_domain, alt_names, issuer, valid_from, valid_to, days_remaining,
|
||||
cert_path, key_path, chain_path, fullchain_path, is_default, status
|
||||
) VALUES (
|
||||
:name, :type, :domain, :alt_names, :issuer, :valid_from, :valid_to, :days_rem,
|
||||
:cert_path, :key_path, :chain_path, :fullchain_path, 1, :status
|
||||
)');
|
||||
|
||||
$stmt->execute([
|
||||
':name' => 'Certificat Auto-signé Système',
|
||||
':type' => 'selfsigned',
|
||||
':domain' => $domain,
|
||||
':alt_names' => '',
|
||||
':issuer' => $issuer,
|
||||
':valid_from' => $validFrom,
|
||||
':valid_to' => $validTo,
|
||||
':days_rem' => $daysRemaining,
|
||||
':cert_path' => $defaultCert,
|
||||
':key_path' => $defaultKey,
|
||||
':chain_path' => '',
|
||||
':fullchain_path' => $defaultCert,
|
||||
':status' => ($daysRemaining > 0) ? 'valid' : 'expired',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function migrateFeatureCodesTable(PDO $db): void
|
||||
{
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS feature_codes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
feature_key VARCHAR(64) UNIQUE NOT NULL,
|
||||
code VARCHAR(16) NOT NULL,
|
||||
default_code VARCHAR(16) NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
category VARCHAR(64) NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
|
||||
$defaults = [
|
||||
// Supervision & Interception BLF
|
||||
['pickup_blf', '**', '**', "Interception d'appel dirigée (BLF Yealink)", 'pickup', "Récupère l'appel du poste surveillé en appuyant sur sa touche BLF ou en composant ** suivi de son numéro."],
|
||||
['pickup_group', '*8', '*8', "Interception d'appel de groupe", 'pickup', "Récupère n'importe quel appel qui sonne dans votre groupe de postes (ou composez *8 suivi du numéro pour une capture dirigée)."],
|
||||
|
||||
// Renvois d'appels
|
||||
['cf_activate', '*72', '*72', "Activer le renvoi d'appels inconditionnel", 'forward', "Renvoie immédiatement tous les appels vers un autre numéro (composez *72 ou *72 suivi du numéro de destination)."],
|
||||
['cf_deactivate', '*73', '*73', "Désactiver le renvoi d'appels inconditionnel", 'forward', "Annule le renvoi inconditionnel de vos appels."],
|
||||
['cfu_activate', '*52', '*52', "Activer le renvoi sur non-réponse / indisponibilité", 'forward', "Renvoie vos appels uniquement en cas de non-réponse ou d'indisponibilité."],
|
||||
['cfu_deactivate', '*53', '*53', "Désactiver le renvoi sur non-réponse / indisponibilité", 'forward', "Annule le renvoi sur non-réponse / indisponibilité."],
|
||||
|
||||
// Ne pas déranger (DND)
|
||||
['dnd_activate', '*78', '*78', "Activer le mode Ne-pas-Déranger (DND)", 'dnd', "Rejette les appels entrants en envoyant le signal occupé ou renvoie vers la messagerie."],
|
||||
['dnd_deactivate', '*79', '*79', "Désactiver le mode Ne-pas-Déranger (DND)", 'dnd', "Rétablit la sonnerie normale de votre combiné téléphonique."],
|
||||
['dnd_toggle', '*76', '*76', "Inverser l'état Ne-pas-Déranger (Toggle)", 'dnd', "Bascule rapidement entre l'état Ne-pas-déranger Actif et Inactif."],
|
||||
|
||||
// Messagerie vocale
|
||||
['voicemail_direct', '*97', '*97', "Accès direct à votre boîte vocale", 'voicemail', "Consulte vos messages vocaux sans mot de passe depuis votre propre poste."],
|
||||
['voicemail_general', '*98', '*98', "Consulter la boîte vocale d'une autre extension", 'voicemail', "Accède à n'importe quelle boîte vocale en saisissant le numéro de poste et le code PIN."],
|
||||
|
||||
// Sécurité & Filtrage
|
||||
['blacklist_add', '*30', '*30', "Ajouter un numéro à la liste noire (Blacklist)", 'security', "Bloque définitivement les appels provenant d'un numéro indésirable."],
|
||||
['blacklist_remove', '*31', '*31', "Retirer un numéro de la liste noire", 'security', "Débloque un numéro précédemment blacklisté."],
|
||||
['blacklist_last', '*32', '*32', "Bloquer instantanément le dernier appelant", 'security', "Ajoute immédiatement le numéro du dernier appel reçu à la liste noire."],
|
||||
|
||||
// Outils & Diagnostics
|
||||
['speak_exten', '*65', '*65', "Identifier vocalement votre numéro de poste", 'tools', "Asterisk énonce à voix haute le numéro de l'extension actuelle (Speak your extension)."],
|
||||
['last_caller', '*69', '*69', "Identifier vocalement le dernier appelant reçu", 'tools', "Énonce le numéro du dernier appel reçu et propose de le rappeler."],
|
||||
['echo_test', '*43', '*43', "Test d'écho audio bidirectionnel", 'tools', "Permet de tester micro, écouteur et latence réseau en renvoyant votre voix en miroir."],
|
||||
['speaking_clock', '*60', '*60', "Horloge parlante / Test audio", 'tools', "Donne la date et l'heure exacte et valide le fonctionnement des codecs audio."],
|
||||
['moh_test', '*44', '*44', "Test d'écoute de la musique d'attente (MoH)", 'tools', "Diffuse la musique d'attente générale pour valider la qualité sonore."]
|
||||
];
|
||||
|
||||
$stmtCheck = $db->prepare('SELECT COUNT(*) FROM feature_codes WHERE feature_key = :k');
|
||||
$stmtInsert = $db->prepare('INSERT INTO feature_codes (feature_key, code, default_code, name, category, description, enabled) VALUES (:k, :c, :def, :n, :cat, :d, 1)');
|
||||
|
||||
foreach ($defaults as $item) {
|
||||
$stmtCheck->execute([':k' => $item[0]]);
|
||||
if ((int)$stmtCheck->fetchColumn() === 0) {
|
||||
$stmtInsert->execute([
|
||||
':k' => $item[0],
|
||||
':c' => $item[1],
|
||||
':def' => $item[2],
|
||||
':n' => $item[3],
|
||||
':cat' => $item[4],
|
||||
':d' => $item[5],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user