feat(ucp): NexusPBX v2.5.0 Portail Utilisateur isolé (Refs #4)
This commit is contained in:
1 parent
7922e338e3
commit
72e43f1a9a
18 files changed
+3499
-62
No files matched your search
+342
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../src/Database.php';
|
||||
require_once __DIR__ . '/../src/FirewallManager.php';
|
||||
require_once __DIR__ . '/../src/AmiClient.php';
|
||||
require_once __DIR__ . '/../src/ConfigGenerator.php';
|
||||
require_once __DIR__ . '/../src/CdrManager.php';
|
||||
require_once __DIR__ . '/../src/PhonebookManager.php';
|
||||
require_once __DIR__ . '/../src/UcpAuth.php';
|
||||
require_once __DIR__ . '/../src/UcpManager.php';
|
||||
|
||||
use App\Database;
|
||||
use App\UcpAuth;
|
||||
use App\UcpManager;
|
||||
|
||||
$action = trim((string)($_GET['action'] ?? $_POST['action'] ?? ''));
|
||||
$isBinaryEndpoint = in_array($action, ['stream_voicemail', 'avatar'], true);
|
||||
|
||||
if (!$isBinaryEndpoint) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
try {
|
||||
$db = Database::getConnection();
|
||||
UcpAuth::startSession();
|
||||
|
||||
if (!UcpAuth::isUcpEnabled($db)) {
|
||||
http_response_code(503);
|
||||
echo json_encode(['success' => false, 'message' => 'Le portail utilisateur UCP est actuellement désactivé par l’administrateur.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Lecture du payload JSON si applicable
|
||||
$payload = $_POST;
|
||||
$contentType = (string)($_SERVER['CONTENT_TYPE'] ?? '');
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$raw = file_get_contents('php://input');
|
||||
$decoded = json_decode((string)$raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$payload = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 1. Authentification (routes publiques de session)
|
||||
// -------------------------------------------------------------------------
|
||||
if ($action === 'login') {
|
||||
$username = (string)($payload['username'] ?? '');
|
||||
$password = (string)($payload['password'] ?? '');
|
||||
|
||||
if (UcpAuth::login($db, $username, $password)) {
|
||||
$user = UcpAuth::getCurrentUser($db);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'username' => $user['username'] ?? $username,
|
||||
'csrf_token' => UcpAuth::getCsrfToken(),
|
||||
'message' => 'Connexion réussie.',
|
||||
]);
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Identifiant ou mot de passe incorrect.',
|
||||
]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'logout') {
|
||||
UcpAuth::logout();
|
||||
echo json_encode(['success' => true, 'message' => 'Déconnexion effectuée.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 2. Barrière d'authentification UCP obligatoire
|
||||
// -------------------------------------------------------------------------
|
||||
$currentUser = UcpAuth::getCurrentUser($db);
|
||||
if (!$currentUser) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'message' => 'Session expirée ou non authentifiée.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Protection CSRF sur toutes les actions modificatrices POST
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$csrfHeader = (string)($_SERVER['HTTP_X_CSRF_TOKEN'] ?? $payload['csrf_token'] ?? '');
|
||||
if (!UcpAuth::verifyCsrfToken($csrfHeader)) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'message' => 'Jeton de sécurité CSRF invalide. Rechargez la page.']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$ucpUserId = (int)$currentUser['id'];
|
||||
$allowedExts = UcpAuth::getAllowedExtensions($db, $ucpUserId);
|
||||
|
||||
switch ($action) {
|
||||
case 'get_state':
|
||||
$branding = $db->query('SELECT company_name, company_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'user' => [
|
||||
'id' => $ucpUserId,
|
||||
'username' => (string)$currentUser['username'],
|
||||
'display_name' => (string)$currentUser['display_name'],
|
||||
'email' => (string)$currentUser['email'],
|
||||
'last_login_at' => $currentUser['last_login_at'],
|
||||
],
|
||||
'branding' => [
|
||||
'company_name' => (string)($branding['company_name'] ?? ''),
|
||||
'company_logo' => (string)($branding['company_logo'] ?? ''),
|
||||
],
|
||||
'csrf_token' => UcpAuth::getCsrfToken(),
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'forward_targets' => UcpManager::getForwardTargets($db),
|
||||
],
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'save_call_features':
|
||||
$ext = (string)($payload['extension'] ?? '');
|
||||
UcpManager::setCallFeatures($db, $allowedExts, $ext, [
|
||||
'dnd' => !empty($payload['dnd']),
|
||||
'cf' => (string)($payload['cf'] ?? ''),
|
||||
'cfu' => (string)($payload['cfu'] ?? ''),
|
||||
]);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'message' => "Paramètres d'appel du poste {$ext} appliqués en direct.",
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'get_voicemails':
|
||||
$ext = (string)($_GET['extension'] ?? $payload['extension'] ?? '');
|
||||
if ($ext === '' && !empty($allowedExts)) {
|
||||
$ext = (string)$allowedExts[0]['extension'];
|
||||
}
|
||||
$messages = $ext !== '' ? UcpManager::getVoicemails($allowedExts, $ext) : [];
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'messages' => $messages,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'stream_voicemail':
|
||||
$ext = (string)($_GET['extension'] ?? '');
|
||||
$folder = (string)($_GET['folder'] ?? '');
|
||||
$msgId = (string)($_GET['id'] ?? '');
|
||||
$download = !empty($_GET['download']);
|
||||
|
||||
$filePath = UcpManager::getVoicemailAudioPath($allowedExts, $ext, $folder, $msgId);
|
||||
$size = filesize($filePath);
|
||||
$fileExt = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||||
$mime = ($fileExt === 'gsm') ? 'audio/gsm' : 'audio/wav';
|
||||
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header("Content-Type: {$mime}");
|
||||
header('Accept-Ranges: bytes');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
if ($download) {
|
||||
$dlName = "voicemail_{$ext}_{$msgId}.{$fileExt}";
|
||||
header("Content-Disposition: attachment; filename=\"{$dlName}\"");
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
[$param, $range] = explode('=', $_SERVER['HTTP_RANGE'], 2);
|
||||
if (strtolower(trim($param)) === 'bytes') {
|
||||
[$from, $to] = explode('-', $range, 2);
|
||||
$from = intval($from);
|
||||
$to = ($to === '') ? ($size - 1) : intval($to);
|
||||
if ($from > $to || $from >= $size) {
|
||||
http_response_code(416);
|
||||
exit;
|
||||
}
|
||||
$length = $to - $from + 1;
|
||||
http_response_code(206);
|
||||
header("Content-Range: bytes {$from}-{$to}/{$size}");
|
||||
header("Content-Length: {$length}");
|
||||
$fp = fopen($filePath, 'rb');
|
||||
fseek($fp, $from);
|
||||
$remaining = $length;
|
||||
while ($remaining > 0 && !feof($fp)) {
|
||||
$chunk = min(8192, $remaining);
|
||||
echo fread($fp, $chunk);
|
||||
$remaining -= $chunk;
|
||||
flush();
|
||||
}
|
||||
fclose($fp);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Length: {$size}");
|
||||
readfile($filePath);
|
||||
exit;
|
||||
|
||||
case 'toggle_voicemail_read':
|
||||
$ext = (string)($payload['extension'] ?? '');
|
||||
$folder = (string)($payload['folder'] ?? '');
|
||||
$msgId = (string)($payload['id'] ?? '');
|
||||
UcpManager::moveVoicemailFolder($db, $allowedExts, $ext, $folder, $msgId);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'messages' => UcpManager::getVoicemails($allowedExts, $ext),
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'message' => ($folder === 'INBOX') ? 'Message marqué comme lu (archivé).' : 'Message marqué comme non lu.',
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'delete_voicemail':
|
||||
$ext = (string)($payload['extension'] ?? '');
|
||||
$folder = (string)($payload['folder'] ?? '');
|
||||
$msgId = (string)($payload['id'] ?? '');
|
||||
UcpManager::deleteVoicemail($db, $allowedExts, $ext, $folder, $msgId);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'messages' => UcpManager::getVoicemails($allowedExts, $ext),
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'message' => 'Message vocal supprimé définitivement.',
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'save_voicemail_prefs':
|
||||
$ext = (string)($payload['extension'] ?? '');
|
||||
$pin = (string)($payload['pin'] ?? '');
|
||||
$email = (string)($payload['email'] ?? '');
|
||||
UcpManager::updateVoicemailPrefs($db, $allowedExts, $ext, $pin, $email);
|
||||
$freshAllowed = UcpAuth::getAllowedExtensions($db, $ucpUserId);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'extensions' => UcpManager::getExtensionsState($db, $freshAllowed),
|
||||
'message' => 'Préférences messagerie vocale enregistrées et appliquées.',
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'get_personal_cdr':
|
||||
$data = UcpManager::getPersonalCdr($db, $allowedExts, [
|
||||
'direction' => $_GET['direction'] ?? 'all',
|
||||
'disposition' => $_GET['disposition'] ?? 'all',
|
||||
'period' => $_GET['period'] ?? 'all',
|
||||
'search' => $_GET['search'] ?? '',
|
||||
]);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'get_phonebook':
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'contacts' => UcpManager::getPhonebookForUser($db, $allowedExts),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'save_my_email':
|
||||
$ext = (string)($payload['extension'] ?? '');
|
||||
$email = (string)($payload['email'] ?? '');
|
||||
UcpManager::updateMyContactEmail($db, $allowedExts, $ext, $email);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'contacts' => UcpManager::getPhonebookForUser($db, $allowedExts),
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'message' => "E-mail annuaire du poste {$ext} mis à jour.",
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'upload_my_avatar':
|
||||
$ext = (string)($_POST['extension'] ?? '');
|
||||
if (empty($_FILES['avatar'])) {
|
||||
throw new InvalidArgumentException("Aucun fichier image reçu.");
|
||||
}
|
||||
$avatarUrl = UcpManager::uploadMyAvatar($db, $allowedExts, $ext, $_FILES['avatar']);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'photo_url' => $avatarUrl,
|
||||
'contacts' => UcpManager::getPhonebookForUser($db, $allowedExts),
|
||||
'extensions' => UcpManager::getExtensionsState($db, $allowedExts),
|
||||
'message' => "Photo de profil du poste {$ext} mise à jour.",
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
$file = (string)($_GET['file'] ?? '');
|
||||
$path = UcpManager::resolveAvatarPath($file);
|
||||
if (!$path) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
$extName = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
$mime = match ($extName) {
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
default => 'image/jpeg',
|
||||
};
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
header("Content-Type: {$mime}");
|
||||
header('Cache-Control: public, max-age=3600');
|
||||
readfile($path);
|
||||
exit;
|
||||
|
||||
case 'change_password':
|
||||
$cur = (string)($payload['current_password'] ?? '');
|
||||
$new = (string)($payload['new_password'] ?? '');
|
||||
UcpAuth::updateOwnPassword($db, $ucpUserId, $cur, $new);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mot de passe UCP modifié avec succès.',
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Action UCP inconnue.']);
|
||||
break;
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
http_response_code(400);
|
||||
if ($isBinaryEndpoint) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $e->getMessage();
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
http_response_code(500);
|
||||
if ($isBinaryEndpoint) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Erreur serveur.';
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user