diff --git a/public/api.php b/public/api.php index 974fe0c..7c9ad3d 100644 --- a/public/api.php +++ b/public/api.php @@ -1802,30 +1802,53 @@ try { case 'save_company_branding': $company_name = trim((string)($payload['company_name'] ?? '')); $company_logo = trim((string)($payload['company_logo'] ?? '')); + $ucp_logo = trim((string)($payload['ucp_logo'] ?? '')); - if ($company_logo !== '' && !preg_match('/^data:image\/(png|jpeg|jpg|webp|svg\+xml|gif);base64,/i', $company_logo)) { - throw new InvalidArgumentException("Format d'image invalide (utilisez PNG, SVG, JPG ou WebP)."); - } - if (strlen($company_logo) > 2 * 1024 * 1024) { - throw new InvalidArgumentException("Le logo est trop volumineux (max 1.5 Mo)."); + foreach (['company_logo' => $company_logo, 'ucp_logo' => $ucp_logo] as $fieldKey => $logoVal) { + if (!array_key_exists($fieldKey, $payload)) { + continue; + } + if ($logoVal !== '' && !preg_match('/^data:image\/(png|jpeg|jpg|webp|svg\+xml|gif);base64,/i', $logoVal)) { + throw new InvalidArgumentException("Format d'image invalide (utilisez PNG, SVG, JPG ou WebP)."); + } + if (strlen($logoVal) > 2 * 1024 * 1024) { + throw new InvalidArgumentException("Le logo est trop volumineux (max 1.5 Mo)."); + } } - if (array_key_exists('company_name', $payload) && array_key_exists('company_logo', $payload)) { - $stmt = $db->prepare('UPDATE settings SET company_name = :cn, company_logo = :cl, updated_at = CURRENT_TIMESTAMP WHERE id = 1'); - $stmt->execute([':cn' => $company_name, ':cl' => $company_logo]); - } elseif (array_key_exists('company_logo', $payload)) { - $stmt = $db->prepare('UPDATE settings SET company_logo = :cl, updated_at = CURRENT_TIMESTAMP WHERE id = 1'); - $stmt->execute([':cl' => $company_logo]); - } elseif (array_key_exists('company_name', $payload)) { - $stmt = $db->prepare('UPDATE settings SET company_name = :cn, updated_at = CURRENT_TIMESTAMP WHERE id = 1'); - $stmt->execute([':cn' => $company_name]); + $sets = []; + $params = []; + if (array_key_exists('company_name', $payload)) { + $sets[] = 'company_name = :cn'; + $params[':cn'] = $company_name; + } + if (array_key_exists('company_logo', $payload)) { + $sets[] = 'company_logo = :cl'; + $params[':cl'] = $company_logo; + } + if (array_key_exists('ucp_logo', $payload)) { + $sets[] = 'ucp_logo = :ul'; + $params[':ul'] = $ucp_logo; + } + + if ($sets) { + $sets[] = 'updated_at = CURRENT_TIMESTAMP'; + $stmt = $db->prepare('UPDATE settings SET ' . implode(', ', $sets) . ' WHERE id = 1'); + $stmt->execute($params); + } + + $msg = 'Logo et identité entreprise mis à jour sur NexusPBX !'; + if (array_key_exists('ucp_logo', $payload) && !array_key_exists('company_logo', $payload) && !array_key_exists('company_name', $payload)) { + $msg = $ucp_logo === '' + ? 'Logo UCP réinitialisé (hérite du logo principal).' + : 'Logo spécifique UCP mis à jour !'; + } elseif (array_key_exists('company_logo', $payload) && $company_logo === '' && !array_key_exists('company_name', $payload) && !array_key_exists('ucp_logo', $payload)) { + $msg = 'Logo entreprise réinitialisé.'; } echo json_encode([ 'success' => true, - 'message' => $company_logo === '' && !array_key_exists('company_name', $payload) - ? 'Logo entreprise réinitialisé.' - : 'Logo et identité entreprise mis à jour sur NexusPBX !', + 'message' => $msg, ]); break; diff --git a/public/assets/app.js b/public/assets/app.js index c45f5ed..fa4e86a 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -243,6 +243,7 @@ function renderAll() { function renderCompanyBranding() { const s = appState.settings || {}; const logo = (s.company_logo || '').trim(); + const ucpLogo = (s.ucp_logo || '').trim(); const name = (s.company_name || '').trim(); const emptyBox = document.getElementById('sidebar-company-empty'); @@ -270,6 +271,21 @@ function renderCompanyBranding() { } } + const ucpPreviewImg = document.getElementById('settings-ucp-preview-img'); + const ucpPreviewEmpty = document.getElementById('settings-ucp-preview-empty'); + if (ucpPreviewImg && ucpPreviewEmpty) { + if (ucpLogo) { + ucpPreviewImg.src = ucpLogo; + ucpPreviewImg.classList.remove('hidden'); + ucpPreviewEmpty.classList.add('hidden'); + } else { + ucpPreviewImg.src = ''; + ucpPreviewImg.classList.add('hidden'); + ucpPreviewEmpty.textContent = logo ? 'Hérite du logo principal' : 'NexusPBX par défaut'; + ucpPreviewEmpty.classList.remove('hidden'); + } + } + if (logo || name) { emptyBox?.classList.add('hidden'); filledBox?.classList.remove('hidden'); @@ -321,7 +337,11 @@ function triggerCompanyLogoPicker() { document.getElementById('company-logo-file-input')?.click(); } -function handleCompanyLogoUpload(input) { +function triggerUcpLogoPicker() { + document.getElementById('ucp-logo-file-input')?.click(); +} + +function uploadBrandingLogo(input, fieldName) { const file = input?.files?.[0]; if (!file) return; @@ -335,7 +355,7 @@ function handleCompanyLogoUpload(input) { reader.onload = async (ev) => { const dataUrl = String(ev.target?.result || ''); try { - const res = await apiCall('save_company_branding', { company_logo: dataUrl }); + const res = await apiCall('save_company_branding', { [fieldName]: dataUrl }); showToast(res.message, 'success'); await loadState(); } catch (err) { @@ -347,9 +367,17 @@ function handleCompanyLogoUpload(input) { reader.readAsDataURL(file); } -async function clearCompanyLogo() { +function handleCompanyLogoUpload(input) { + uploadBrandingLogo(input, 'company_logo'); +} + +function handleUcpLogoUpload(input) { + uploadBrandingLogo(input, 'ucp_logo'); +} + +async function clearBrandingLogo(fieldName) { try { - const res = await apiCall('save_company_branding', { company_logo: '' }); + const res = await apiCall('save_company_branding', { [fieldName]: '' }); showToast(res.message, 'info'); await loadState(); } catch (err) { @@ -357,6 +385,14 @@ async function clearCompanyLogo() { } } +function clearCompanyLogo() { + clearBrandingLogo('company_logo'); +} + +function clearUcpLogo() { + clearBrandingLogo('ucp_logo'); +} + function renderSummaryCards() { const exts = appState.extensions || []; const grps = appState.groups || []; diff --git a/public/index.php b/public/index.php index b37e137..5e9e758 100644 --- a/public/index.php +++ b/public/index.php @@ -1648,6 +1648,7 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev')
+
@@ -1675,6 +1676,28 @@ $isDevEnv = file_exists(__DIR__ . '/../.dev') class="w-full px-3 py-2 rounded-lg bg-anthracite-900 border border-anthracite-700 text-sm text-white">
+ + +
+
+
+ + Hérite du logo principal +
+
+
Logo UCP (Portail Utilisateur — optionnel)
+

Si vide, l'UCP réutilise le logo principal (ou NexusPBX). Utile pour importer un logo foncé adapté au thème clair UCP.

+
+
+
+ + +
+
diff --git a/src/Database.php b/src/Database.php index 50f581b..4811b66 100644 --- a/src/Database.php +++ b/src/Database.php @@ -48,7 +48,7 @@ class Database // Si la base existe déjà, éviter d'exécuter l'intégralité du DDL à chaque requête HTTP // ce qui sature SQLite de verrous exclusifs inutiles. On s'assure des colonnes requises. try { - self::$instance->query("SELECT ucp_https_port, fail2ban_ucp_maxretry, groundwire_secret FROM settings LIMIT 1"); + self::$instance->query("SELECT ucp_https_port, fail2ban_ucp_maxretry, groundwire_secret, ucp_logo FROM settings LIMIT 1"); self::$instance->query("SELECT groundwire_enabled FROM extensions LIMIT 1"); self::$instance->query("SELECT id FROM ucp_users LIMIT 1"); } catch (\Throwable $e) { @@ -395,6 +395,9 @@ class Database if (!in_array('company_logo', $existing, true)) { $db->exec("ALTER TABLE settings ADD COLUMN company_logo TEXT NOT NULL DEFAULT ''"); } + if (!in_array('ucp_logo', $existing, true)) { + $db->exec("ALTER TABLE settings ADD COLUMN ucp_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"); } diff --git a/ucp/api.php b/ucp/api.php index a99be63..68b8472 100644 --- a/ucp/api.php +++ b/ucp/api.php @@ -98,7 +98,9 @@ try { switch ($action) { case 'get_state': - $branding = $db->query('SELECT company_name, company_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(PDO::FETCH_ASSOC) ?: []; + $branding = $db->query('SELECT company_name, company_logo, ucp_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(PDO::FETCH_ASSOC) ?: []; + $compLogo = trim((string)($branding['company_logo'] ?? '')); + $ucpLogo = trim((string)($branding['ucp_logo'] ?? '')); echo json_encode([ 'success' => true, 'data' => [ @@ -110,8 +112,10 @@ try { 'last_login_at' => $currentUser['last_login_at'], ], 'branding' => [ - 'company_name' => (string)($branding['company_name'] ?? ''), - 'company_logo' => (string)($branding['company_logo'] ?? ''), + 'company_name' => (string)($branding['company_name'] ?? ''), + 'company_logo' => $compLogo, + 'ucp_logo' => $ucpLogo, + 'effective_logo' => $ucpLogo !== '' ? $ucpLogo : $compLogo, ], 'csrf_token' => UcpAuth::getCsrfToken(), 'extensions' => UcpManager::getExtensionsState($db, $allowedExts, $ucpUserId), diff --git a/ucp/index.php b/ucp/index.php index 64b9a02..00080bb 100644 --- a/ucp/index.php +++ b/ucp/index.php @@ -13,9 +13,11 @@ $ucpEnabled = UcpAuth::isUcpEnabled($db); $currentUser = $ucpEnabled ? UcpAuth::getCurrentUser($db) : null; $isAuth = ($currentUser !== null); -$branding = $db->query('SELECT company_name, company_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(\PDO::FETCH_ASSOC) ?: []; +$branding = $db->query('SELECT company_name, company_logo, ucp_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(\PDO::FETCH_ASSOC) ?: []; $companyName = trim((string)($branding['company_name'] ?? '')); $companyLogo = trim((string)($branding['company_logo'] ?? '')); +$ucpLogo = trim((string)($branding['ucp_logo'] ?? '')); +$ucpEffectiveLogo = $ucpLogo !== '' ? $ucpLogo : $companyLogo; $appVerData = file_exists(__DIR__ . '/../version.json') ? json_decode((string)file_get_contents(__DIR__ . '/../version.json'), true) : []; $appVersion = $appVerData['version'] ?? '2.5.0'; $csrfToken = UcpAuth::getCsrfToken(); @@ -136,8 +138,8 @@ $csrfToken = UcpAuth::getCsrfToken();
- - Logo + + Logo NexusPBX @@ -180,8 +182,8 @@ $csrfToken = UcpAuth::getCsrfToken();
- - Logo + + Logo NexusPBX @@ -430,8 +432,8 @@ $csrfToken = UcpAuth::getCsrfToken();
- - Logo + + Logo NexusPBX diff --git a/version.json b/version.json index 2e427c2..982440d 100644 --- a/version.json +++ b/version.json @@ -1,9 +1,16 @@ { - "version": "2.6.7", + "version": "2.7.0", "codename": "Pulsar UCP", "release_date": "2026-11-12", "min_php_version": "8.2", "changelog": [ + { + "version": "2.7.0", + "date": "2026-11-12", + "changes": [ + "Branding UCP : Ajout d'un logo spécifique optionnel pour le portail UCP (thème clair), avec repli automatique sur le logo principal puis sur le logo NexusPBX par défaut" + ] + }, { "version": "2.6.7", "date": "2026-11-12",