#!/usr/bin/php -q (speed-dial) so contacts do NOT auto-populate line keys. * - Optional --notify will send PJSIP check-sync to all numeric endpoints. */ $bootstrap_settings['freepbx_auth'] = false; require_once('/etc/freepbx.conf'); $provision_dir = '/tftpboot'; // <-- adjust if needed $out_file = $provision_dir . '/482567bcdeff-features.cfg'; $do_notify = in_array('--notify', $argv, true); $db = FreePBX::Database(); $rows = []; /** Try Core 'users' table first (name + extension) */ try { $stmt = $db->prepare(" SELECT name, extension FROM users WHERE extension REGEXP '^[0-9]+$' ORDER BY CAST(extension AS UNSIGNED) "); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Exception $e) { // ignore, fall back below } /** Fall back to PJSIP ps_endpoints (id is ext; parse callerid "Name <1001>") */ if (!$rows) { $stmt = $db->prepare(" SELECT id AS extension, callerid FROM ps_endpoints ORDER BY CAST(id AS UNSIGNED) "); $stmt->execute(); $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($tmp as $r) { $ext = trim((string)$r['extension']); if (!preg_match('/^[0-9]+$/', $ext)) continue; // only numeric extensions $cid = trim((string)($r['callerid'] ?? '')); // Try to parse descriptive name from callerid $name = 'Ext ' . $ext; if ($cid !== '') { // Common formats: "John Smith" <1001> OR John Smith <1001> if (preg_match('/^\"?([^\"<]+)\"?\s*<\s*' . preg_quote($ext, '/') . '\s*>$/', $cid, $m)) { $name = trim($m[1]); } else { // If callerid is just a name without angle brackets, use it if (strpos($cid, '<') === false) { $name = $cid; } } } $rows[] = ['name' => $name, 'extension' => $ext]; } } if (!$rows) { fwrite(STDERR, "No extensions found (users/ps_endpoints returned no rows).\n"); exit(1); } array_unshift($rows, [ 'name' => 'Night time', 'extension' => '*271', ]); $blank = ['name' => '', 'extension' => '']; array_splice($rows, 1, 0, array_fill(0, 5, $blank)); $xmlString = file_get_contents($out_file); if ($xmlString === false) { throw new RuntimeException("Unable to read file: {$out_file}"); } // Strip UTF-8 BOM and leading whitespace $xmlString = preg_replace('/^\xEF\xBB\xBF/', '', $xmlString); // BOM $xmlString = ltrim($xmlString); // If there are any stray bytes before the first '<', trim them $firstAngle = strpos($xmlString, '<'); if ($firstAngle !== false && $firstAngle > 0) { $xmlString = substr($xmlString, $firstAngle); } $xml = new DOMDocument('1.0', 'UTF-8'); $xml->preserveWhiteSpace = true; // pretty-print $xml->formatOutput = true; $xml->xmlStandalone = true; // keep standalone="yes" if (!$xml->loadXML($xmlString)) { $errs = libxml_get_errors(); libxml_clear_errors(); $msg = "Failed to parse XML.\n"; foreach ($errs as $e) { $msg .= "[level {$e->level}] {$e->message} at line {$e->line}\n"; } throw new RuntimeException($msg); } $xpath = new DOMXPath($xml); $attendantNodes = $xpath->query('/polycomConfig/attendant'); if ($attendantNodes->length === 0) { throw new RuntimeException("No element found at /polycomConfig/attendant"); } /** @var DOMElement $attendant */ $attendant = $attendantNodes->item(0); // 1) Remove all existing attendant.resourceList.* attributes $toRemove = []; foreach ($attendant->attributes as $attr) { if (strpos($attr->nodeName, 'attendant.resourceList.') === 0) { $toRemove[] = $attr->nodeName; } } foreach ($toRemove as $name) { $attendant->removeAttribute($name); } $index = 1; foreach ($rows as $r) { $label = trim((string)($r['name'] ?? '')); $address = trim((string)($r['extension'] ?? '')); $type = trim((string)("normal")); if ($address === '' && $index > 6) continue; $attendant->setAttribute("attendant.resourceList.{$index}.address", $address); $attendant->setAttribute("attendant.resourceList.{$index}.label", $label); $attendant->setAttribute("attendant.resourceList.{$index}.type", $type); $index++; } /** Ensure provision dir exists */ if (!is_dir($provision_dir)) { fwrite(STDERR, "Provisioning directory not found: $provision_dir\n"); exit(2); } /** Atomic write */ $tmpfile = $out_file . '.tmp'; if ($xml->save($tmpfile) === false) { fwrite(STDERR, "Failed to write temporary file $tmpfile\n"); exit(3); } if (!@rename($tmpfile, $out_file)) { @unlink($tmpfile); fwrite(STDERR, "Failed to move $tmpfile to $out_file (permissions?)\n"); exit(4); } echo "Wrote $out_file \n"; /** Optional: push check-sync to re-download directory without reboot */ if ($do_notify) { // Use the extension list we already built $notified = 0; foreach ($rows as $r) { $ext = trim((string)$r['extension']); if ($ext === '') continue; // In FreePBX, PJSIP endpoint id is typically the extension number $cmd = "asterisk -rx \"pjsip send notify polycom-check-cfg endpoint " . escapeshellarg($ext) . "\""; exec($cmd, $o, $rc); if ($rc === 0) $notified++; } echo "Sent check-sync to $notified endpoints\n"; }