Login-Seite individualisierbar (Branding, Texte, Hintergrund)
Some checks failed
CI / PHP Lint (push) Has been cancelled
CI / PHP Lint-1 (push) Has been cancelled
CI / Unit Tests & Static Analysis (push) Has been cancelled
CI / PHP Lint (pull_request) Has been cancelled
CI / PHP Lint-1 (pull_request) Has been cancelled
CI / Unit Tests & Static Analysis (pull_request) Has been cancelled

Neuer Einstellungs-Tab "Login-Seite" (Administration → Einstellungen):

- Firmenname und eigenes Logo für die Anmeldeseite
- Überschrift, Beschreibungstext, Stichpunkte (eine Zeile je Punkt) und
  Fußzeile frei setzbar
- Hintergrundbild für die linke Bildspalte, alternativ Farbverlauf mit
  Farbwähler (Start-/Endfarbe) sowie einstellbarer Abdunklung für die
  Lesbarkeit auf hellen Bildern
- Bildspalte abschaltbar: dann zentrierte Anmeldekarte auf dem
  App-Hintergrund
- "Vorschau öffnen" zeigt die Login-Seite als angemeldeter Admin
  (login.php?preview=1), ohne die Sitzung zu beenden

Alle Felder sind optional; leere Werte fallen auf die bisherigen
Standardtexte und -farben zurück. Bestehende Installationen sehen
unverändert aus, es ist keine Migration nötig.

README um den Abschnitt "Login-Seite individualisieren" mit Beispiel-
Screenshots ergänzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Friederich Loheide 2026-09-22 20:40:48 +00:00
parent 498c7e28e0
commit 9a4b2e1cd4
10 changed files with 308 additions and 16 deletions

View file

@ -10,7 +10,10 @@ require_once __DIR__ . '/includes/I18n.php';
try {
$auth = new Auth();
if ($auth->isLoggedIn()) { header('Location: index.php'); exit; }
// Angemeldete Admins koennen die Login-Seite mit ?preview=1 ansehen
// (Vorschau aus den Einstellungen), ohne abgemeldet zu werden.
$isPreview = isset($_GET['preview']) && $auth->isLoggedIn() && $auth->isAdmin();
if ($auth->isLoggedIn() && !$isPreview) { header('Location: index.php'); exit; }
} catch (Exception $e) {
die('Fehler beim Initialisieren: ' . $e->getMessage());
}
@ -117,6 +120,34 @@ try {
$showLocalLogin = isset($_GET['local']) && $_GET['local'] === '1';
// --- Individualisierung der Login-Seite -------------------------------
// Alle Werte sind optional; leer bedeutet "Standard verwenden".
$loginBrand = $db->getSetting('login_brand_name', '') ?: $appTitle;
$loginLogo = $db->getSetting('login_logo_url', '') ?: $logoUrl;
$showPanel = $db->getSetting('login_panel_enabled', '1') === '1';
$claimTitle = $db->getSetting('login_claim_title', '') ?: __('auth_claim_title');
$claimText = $db->getSetting('login_claim_text', '') ?: __('auth_claim_text');
$featureRaw = trim((string)$db->getSetting('login_features', ''));
if ($featureRaw !== '') {
$loginFeatures = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $featureRaw))));
} else {
$loginFeatures = [__('auth_feature_1'), __('auth_feature_2'), __('auth_feature_3')];
}
$loginFooter = $db->getSetting('login_footer', '') ?: ('© ' . date('Y') . ' ' . $loginBrand);
$loginBgImage = trim((string)$db->getSetting('login_bg_image', ''));
$loginBgFrom = $db->getSetting('login_bg_from', '') ?: '#3b2f8f';
$loginBgTo = $db->getSetting('login_bg_to', '') ?: '#6d5ce7';
$loginOverlay = max(0, min(90, (int)$db->getSetting('login_bg_overlay', '40')));
$visualStyle = '--login-from:' . htmlspecialchars($loginBgFrom, ENT_QUOTES)
. ';--login-to:' . htmlspecialchars($loginBgTo, ENT_QUOTES)
. ';--login-overlay:' . ($loginOverlay / 100);
if ($loginBgImage !== '') {
$visualStyle .= ";--login-image:url('" . htmlspecialchars($loginBgImage, ENT_QUOTES) . "')";
}
} catch (Exception $e) {
die('Datenbankfehler: ' . $e->getMessage());
}
@ -134,24 +165,32 @@ try {
<link rel="stylesheet" href="assets/global.css">
<script>(function(){ const t=localStorage.getItem('theme')||'light'; document.documentElement.setAttribute('data-theme',t); })();</script>
</head>
<body class="auth-body">
<body class="auth-body<?= $showPanel ? '' : ' auth-body-single' ?>">
<section class="auth-visual">
<?php if ($showPanel): ?>
<section class="auth-visual<?= $loginBgImage !== '' ? ' has-image' : '' ?>" style="<?= $visualStyle ?>">
<div class="auth-brand">
<span class="brand-mark"><i class="fas fa-wifi"></i></span>
<span><?= htmlspecialchars($appTitle) ?></span>
<?php if ($loginLogo): ?>
<img src="<?= htmlspecialchars($loginLogo) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="auth-brand-logo">
<?php else: ?>
<span class="brand-mark"><i class="fas fa-wifi"></i></span>
<span><?= htmlspecialchars($loginBrand) ?></span>
<?php endif; ?>
</div>
<div class="auth-claim">
<h2><?= __('auth_claim_title') ?></h2>
<p><?= __('auth_claim_text') ?></p>
<?php if ($claimTitle !== ''): ?><h2><?= htmlspecialchars($claimTitle) ?></h2><?php endif; ?>
<?php if ($claimText !== ''): ?><p><?= htmlspecialchars($claimText) ?></p><?php endif; ?>
<?php if (!empty($loginFeatures)): ?>
<ul class="auth-features">
<li><span class="tick"><i class="fas fa-check"></i></span> <?= __('auth_feature_1') ?></li>
<li><span class="tick"><i class="fas fa-check"></i></span> <?= __('auth_feature_2') ?></li>
<li><span class="tick"><i class="fas fa-check"></i></span> <?= __('auth_feature_3') ?></li>
<?php foreach ($loginFeatures as $feature): ?>
<li><span class="tick"><i class="fas fa-check"></i></span> <?= htmlspecialchars($feature) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="auth-foot">&copy; <?= date('Y') ?> <?= htmlspecialchars($appTitle) ?></div>
<div class="auth-foot"><?= htmlspecialchars($loginFooter) ?></div>
</section>
<?php endif; ?>
<section class="auth-panel">
<div class="auth-tools">
@ -167,8 +206,15 @@ try {
</div>
<div class="login-container">
<?php if ($logoUrl): ?>
<img src="<?= htmlspecialchars($logoUrl) ?>" alt="Logo" class="logo">
<?php if (!$showPanel): ?>
<?php if ($loginLogo): ?>
<img src="<?= htmlspecialchars($loginLogo) ?>" alt="<?= htmlspecialchars($loginBrand) ?>" class="logo">
<?php else: ?>
<div class="auth-mark">
<span class="brand-mark"><i class="fas fa-wifi"></i></span>
<span><?= htmlspecialchars($loginBrand) ?></span>
</div>
<?php endif; ?>
<?php endif; ?>
<h1><?= __('login_title') ?></h1>
<p class="subtitle"><?= __('login_subtitle') ?></p>