OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

settings.php

58 KB
<?php $this->layout('layouts/admin', [
    'page_title'         => 'Self-Service-Portal',
    'current_admin_page' => 'portal',
    'extra_css'          => ['/assets/css/portal-admin.css'],
]);
$placementLabels = ['tile' => 'Kachel', 'menu' => 'Menü', 'both' => 'Kachel + Menü'];
$overlay = (int)($portal_overlay ?? 85);

// Effektives Upload-Limit des Servers (kleinster Wert aus PHP-Konfiguration).
// Das App-Wunschlimit je Feld wird darauf gedeckelt, damit die Hinweise nicht
// mehr versprechen, als der Server annimmt.
$uploadLimit = (int)($upload_limit_bytes ?? 2 * 1024 * 1024);
$fmtMb = static function (int $bytes): string {
    $mb = $bytes / (1024 * 1024);
    // Ganze MB ohne Nachkommastelle, krumme Werte mit einer (deutsches Komma).
    $str = (abs($mb - round($mb)) < 0.05) ? (string)(int)round($mb) : number_format($mb, 1, ',', '.');
    return $str . ' MB';
};
$logoLimit = min(2 * 1024 * 1024, $uploadLimit);
$bgLimit   = min(8 * 1024 * 1024, $uploadLimit);
?>

<div class="settings-page pa-page">

    <nav class="settings-tabs">
        <a class="settings-tab active" href="/admin/portal">
            <span class="settings-tab-icon"><i class="fas fa-sliders-h"></i></span> <?= $this->te('Einstellungen') ?>
        </a>
        <a class="settings-tab" href="/admin/portal/kennzahlen">
            <span class="settings-tab-icon"><i class="fas fa-chart-line"></i></span> <?= $this->te('Kennzahlen') ?>
        </a>
        <a class="settings-tab" href="/admin/portal/confirmations">
            <span class="settings-tab-icon"><i class="fas fa-clipboard-check"></i></span> <?= $this->te('Geräte-Bestätigungen') ?>
        </a>
    </nav>

    <div class="pa-status">
        <span class="status-pill <?= !empty($enabled) ? 'status-pill--success' : 'status-pill--neutral' ?>">
            <?= $this->te(!empty($enabled) ? 'Portal aktiv' : 'Portal deaktiviert') ?>
        </span>
        <span class="pa-status-item"><i class="fas fa-users"></i> <strong><?= (int)$active_persons ?></strong> <?= $this->te('aktive Personen im Verzeichnis') ?></span>
        <span class="pa-status-item"><i class="fas fa-link"></i> <code>/portal</code></span>
        <a href="/portal" target="_blank" rel="noopener" class="btn btn-sm btn-default pa-status-action">
            <i class="fas fa-external-link-alt"></i> <?= $this->te('Portal öffnen') ?>
        </a>
    </div>

    <form method="post" action="/admin/portal" enctype="multipart/form-data" id="pa-form">
        <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">

        <section class="settings-card">
            <div class="settings-card-header">
                <span class="settings-card-icon">&#127968;</span>
                <div>
                    <h2><?= $this->te('Grundeinstellungen') ?></h2>
                    <p class="settings-card-desc"><?= $this->t('Portal für Mitarbeiter:innen aus dem Verzeichnis – Tickets, Anträge und eigene Geräte unter <code>/portal</code>.') ?></p>
                </div>
            </div>
            <div class="settings-card-body">
                <p class="pa-lead"><?= $this->t('Mitarbeiter:innen aus dem <strong>Verzeichnis</strong> (LDAP/M365-Sync) melden sich über die normale Login-Seite an und landen im Portal – <strong>ohne</strong> als Benutzer im Tool zu erscheinen. IT-Mitarbeiter behalten ihren gewohnten Zugang und erhalten zusätzlich die Portal-Ansicht.') ?></p>
                <div class="settings-toggles">
                    <label class="settings-toggle">
                        <input type="checkbox" name="enabled" value="1" id="pa-enabled" <?= !empty($enabled) ? 'checked' : '' ?>>
                        <span class="toggle-text">
                            <strong><?= $this->te('Self-Service-Portal aktivieren') ?></strong>
                            <small><?= $this->te('Solange deaktiviert, verhält sich das System exakt wie bisher – kein Portal-Zugang, keine Änderung am Login.') ?></small>
                        </span>
                    </label>
                    <label class="settings-toggle">
                        <input type="checkbox" name="portal_magic" value="1" <?= !empty($portal_magic) ? 'checked' : '' ?>>
                        <span class="toggle-text">
                            <strong><?= $this->te('Anmelde-Link (Magic Link) anbieten') ?></strong>
                            <small><?= $this->te('Manuell gepflegte Verzeichnis-Personen ohne LDAP-/M365-Konto fordern sich auf der Anmeldeseite einen Einmal-Link per E-Mail an. Deaktiviert verschwindet der Hinweis von der Anmeldeseite und bereits verschickte Links funktionieren nicht mehr.') ?></small>
                        </span>
                    </label>
                </div>
                <div class="form-group" style="margin-top:1rem;">
                    <label for="ticket-max-age"><?= $this->te('Alte Tickets ausblenden') ?></label>
                    <div class="pa-inline-field">
                        <input type="number" id="ticket-max-age" name="ticket_max_age" class="form-control"
                               min="0" max="120" value="<?= (int)($ticket_max_age ?? 0) ?>">
                        <span><?= $this->te('Monate') ?></span>
                    </div>
                    <small class="form-hint"><?= $this->t('Unter „Meine Tickets" werden <strong>abgeschlossene</strong> Tickets ausgeblendet, deren Erstelldatum älter ist. Offene Tickets bleiben immer sichtbar. <strong>0 = ohne Begrenzung</strong> (alle anzeigen).') ?></small>
                </div>
            </div>
        </section>

        <div id="pa-dependent" class="pa-dependent">
            <p class="pa-note pa-note--off" id="pa-off-note" hidden>
                <i class="fas fa-info-circle"></i>
                <?= $this->te('Das Portal ist deaktiviert. Die folgenden Einstellungen lassen sich trotzdem vorbereiten – sie greifen, sobald du das Portal aktivierst.') ?>
            </p>

            <?php
            // Zusatzhinweis je Bereich, wenn das zugrunde liegende Modul fehlt.
            $moduleReq = [
                'selfservice_mod_requests'  => !empty($workflows_module)  ? '' : 'Benötigt das Workflows-Modul – derzeit deaktiviert.',
                'selfservice_mod_approvals' => !empty($workflows_module)  ? '' : 'Benötigt das Workflows-Modul – derzeit deaktiviert.',
                'selfservice_mod_devices'   => !empty($assets_module)     ? '' : 'Benötigt das Asset-Modul – derzeit deaktiviert.',
                'selfservice_mod_status'    => !empty($statuspage_module) ? '' : 'Benötigt das Statusseiten-Modul – derzeit deaktiviert.',
            ];
            $moduleDesc = [
                'selfservice_mod_tickets'   => 'Tickets erstellen, verfolgen und beantworten.',
                'selfservice_mod_requests'  => 'Im Portal freigegebene Anträge starten und deren Stand verfolgen.',
                'selfservice_mod_approvals' => 'Genehmiger:innen ohne Tool-Konto entscheiden Anträge direkt im Portal, statt nur per Mail-Token-Link.',
                'selfservice_mod_devices'   => 'Zugewiesene Geräte einsehen, Probleme melden und – falls aktiv – bestätigen.',
                'selfservice_mod_knowledge' => 'Freigegebene Anleitungen lesen und durchsuchen.',
                'selfservice_mod_status'    => 'Aktuellen Zustand der IT-Dienste und geplante Wartungen anzeigen; beeinträchtigte Dienste erscheinen zusätzlich auf der Startseite.',
            ];
            ?>
            <section class="settings-card">
                <div class="settings-card-header">
                    <span class="settings-card-icon">&#129513;</span>
                    <div>
                        <h2><?= $this->te('Bereiche') ?></h2>
                        <p class="settings-card-desc"><?= $this->te('Lege fest, welche Funktionen im Portal sichtbar sind. Ein abgeschalteter Bereich verschwindet aus Navigation und Startseite und ist auch direkt nicht erreichbar.') ?></p>
                    </div>
                </div>
                <div class="settings-card-body">
                    <div class="settings-toggles">
                        <?php foreach (($module_labels ?? []) as $modKey => $modLabel):
                            $field = substr($modKey, strlen('selfservice_'));
                            $req   = $moduleReq[$modKey] ?? '';
                        ?>
                        <label class="settings-toggle">
                            <input type="checkbox" name="<?= $this->e($field) ?>" value="1" <?= !empty($module_state[$modKey]) ? 'checked' : '' ?>>
                            <span class="toggle-text">
                                <strong><?= $this->e($modLabel) ?></strong>
                                <small><?= $this->e($moduleDesc[$modKey] ?? '') ?>
                                    <?php if ($req !== ''): ?><span class="pa-mod-req"><?= $this->e($req) ?></span><?php endif; ?>
                                </small>
                            </span>
                        </label>
                        <?php endforeach; ?>
                    </div>
                </div>
            </section>

            <section class="settings-card">
                <div class="settings-card-header">
                    <span class="settings-card-icon">&#127912;</span>
                    <div>
                        <h2><?= $this->te('Erscheinungsbild') ?></h2>
                        <p class="settings-card-desc"><?= $this->te('Titel, Logo, Anrede, Begrüßungstext und Hintergrundbild des Portals.') ?></p>
                    </div>
                </div>
                <div class="settings-card-body">
                    <div class="form-row-2">
                        <div class="form-group">
                            <label for="portal-title"><?= $this->te('Portal-Titel') ?></label>
                            <input type="text" id="portal-title" name="portal_title" class="form-control" autocomplete="off"
                                   maxlength="100" value="<?= $this->e($portal_title) ?>" placeholder="<?= $this->te('z. B. IT-Serviceportal Musterfirma') ?>">
                            <small class="form-hint"><?= $this->te('Kopfleiste und Browser-Tab. Leer =') ?> „<?= $this->e(($app_name ?? 'OpenNIT') . ' · Self-Service') ?>&ldquo;.</small>
                        </div>
                        <div class="form-group">
                            <label for="portal-address"><?= $this->te('Anrede') ?></label>
                            <select id="portal-address" name="portal_address" class="form-control">
                                <option value="du" <?= ($portal_address ?? 'du') !== 'sie' ? 'selected' : '' ?>><?= $this->te('Du (informell)') ?></option>
                                <option value="sie" <?= ($portal_address ?? 'du') === 'sie' ? 'selected' : '' ?>><?= $this->te('Sie (formell)') ?></option>
                            </select>
                            <small class="form-hint"><?= $this->te('Bestimmt, wie das Portal die Mitarbeiter:innen anspricht.') ?></small>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="portal-welcome"><?= $this->te('Begrüßungstext auf der Startseite') ?></label>
                        <textarea id="portal-welcome" name="portal_welcome" class="form-control" rows="3" maxlength="500"
                                  placeholder="<?= $this->te('Willkommen im Self-Service-Portal. Hier können Sie Anliegen an die IT richten und deren Stand verfolgen.') ?>"><?= $this->e($portal_welcome ?? '') ?></textarea>
                        <small class="form-hint"><?= $this->te('Leer = Standardtext passend zur gewählten Anrede. Zeilenumbrüche bleiben erhalten.') ?></small>
                    </div>

                    <div class="form-group">
                        <label for="portal-footer"><?= $this->te('Fußzeilen-Text') ?></label>
                        <textarea id="portal-footer" name="portal_footer" class="form-control" rows="3" maxlength="1000"
                                  placeholder="<?= $this->e(($app_name ?? 'OpenNIT') . ' · Self-Service-Portal') ?>"><?= $this->e($portal_footer ?? '') ?></textarea>
                        <small class="form-hint"><?= $this->te('Erscheint zentriert in der Fußzeile des Portals. Leer =') ?> „<?= $this->e(($app_name ?? 'OpenNIT') . ' · Self-Service-Portal') ?>&ldquo;.
                            <?= $this->t('Einfaches HTML ist erlaubt – z.&nbsp;B. ein Link: <code>Bei Fragen erreichen Sie die IT unter &lt;a href="tel:08003301000"&gt;0800 / 330 1000&lt;/a&gt;</code>. Zulässig sind nur <code>a</code>, <code>br</code>, <code>strong</code>, <code>b</code>, <code>em</code>, <code>i</code>, <code>span</code>, <code>small</code>; alles andere wird entfernt.') ?></small>
                    </div>

                    <div class="form-group">
                        <label for="portal-logo"><?= $this->te('Portal-Logo') ?></label>
                        <div class="pa-media">
                            <div class="pa-media-preview pa-media-preview--logo">
                                <?php if (!empty($portal_logo)): ?>
                                    <img src="<?= $this->e($portal_logo) ?>" alt="<?= $this->te('Portal-Logo') ?>">
                                <?php else: ?>
                                    <span class="pa-media-empty"><i class="fas fa-image"></i> <?= $this->te('App-Logo') ?></span>
                                <?php endif; ?>
                            </div>
                            <div class="pa-media-fields">
                                <input type="file" id="portal-logo" name="portal_logo" class="form-control form-control-sm"
                                       accept="image/jpeg,image/png,image/gif,image/webp"
                                       data-max-bytes="<?= $logoLimit ?>" data-max-label="<?= $this->e($fmtMb($logoLimit)) ?>">
                                <small class="form-hint"><?= $this->te('JPEG, PNG, GIF oder WebP, max.') ?> <?= $this->e($fmtMb($logoLimit)) ?>. <?= $this->te('Leer = App-Logo wird verwendet.') ?></small>
                                <?php if (!empty($portal_logo)): ?>
                                <label class="settings-toggle pa-media-remove">
                                    <input type="checkbox" name="portal_logo_remove" value="1">
                                    <span class="toggle-text"><strong><?= $this->te('Logo entfernen') ?></strong> <small><?= $this->te('Zurück zum App-Logo.') ?></small></span>
                                </label>
                                <?php endif; ?>
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="portal-background"><?= $this->te('Hintergrundbild') ?></label>
                        <div class="pa-media">
                            <?php $bgPreviewUrl = !empty($portal_background) ? '/portal/branding-background?v=' . substr(md5((string)$portal_background), 0, 10) : ''; ?>
                            <div class="pa-media-preview pa-media-preview--bg" id="pa-bg-preview"
                                 <?= $bgPreviewUrl !== '' ? 'style="--pa-bg:url(\'' . $this->e($bgPreviewUrl) . '\');--pa-overlay:' . number_format($overlay / 100, 2, '.', '') . '"' : '' ?>>
                                <?php if (empty($portal_background)): ?>
                                    <span class="pa-media-empty"><i class="fas fa-image"></i> <?= $this->te('Kein Bild') ?></span>
                                <?php else: ?>
                                    <span class="pa-media-caption"><?= $this->te('Vorschau') ?></span>
                                <?php endif; ?>
                            </div>
                            <div class="pa-media-fields">
                                <input type="file" id="portal-background" name="portal_background" class="form-control form-control-sm"
                                       accept="image/jpeg,image/png,image/gif,image/webp"
                                       data-max-bytes="<?= $bgLimit ?>" data-max-label="<?= $this->e($fmtMb($bgLimit)) ?>">
                                <small class="form-hint"><?= $this->te('JPEG, PNG, GIF oder WebP, max.') ?> <?= $this->e($fmtMb($bgLimit)) ?>. <?= $this->te('Das Bild füllt die Fläche aus und bleibt beim Scrollen stehen.') ?>
                                    <?php if ($bgLimit < 8 * 1024 * 1024): ?><br><span class="pa-mod-req"><?= $this->t('Server-Limit: größere Dateien werden abgewiesen. Für mehr <code>upload_max_filesize</code>/<code>post_max_size</code> in der PHP-Konfiguration anheben.') ?></span><?php endif; ?>
                                </small>
                                <?php if (!empty($portal_background)): ?>
                                <label class="settings-toggle pa-media-remove">
                                    <input type="checkbox" name="portal_background_remove" value="1">
                                    <span class="toggle-text"><strong><?= $this->te('Hintergrundbild entfernen') ?></strong> <small><?= $this->te('Zurück zum einfarbigen Hintergrund.') ?></small></span>
                                </label>
                                <?php endif; ?>
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="portal-overlay"><?= $this->te('Deckkraft des weißen Overlays') ?></label>
                        <div class="pa-slider">
                            <input type="range" id="portal-overlay" name="portal_overlay" min="0" max="100" step="5" value="<?= $overlay ?>">
                            <output class="pa-slider-value" id="portal-overlay-value"><?= $overlay ?> %</output>
                        </div>
                        <small class="form-hint"><?= $this->te('Weiße Schicht über dem Hintergrundbild. Höhere Werte machen das Bild dezenter und halten Texte gut lesbar; 0 % zeigt das Bild unverändert.') ?></small>
                    </div>

                    <div class="form-group">
                        <label for="portal-text-color"><?= $this->te('Textfarbe außerhalb der Karten') ?></label>
                        <?php $paTextColor = trim((string)($portal_text_color ?? '')); ?>
                        <div class="pa-inline-field">
                            <input type="color" id="portal-text-color" name="portal_text_color"
                                   value="<?= $paTextColor !== '' ? $this->e($paTextColor) : '#0f172a' ?>"
                                   <?= $paTextColor === '' ? 'disabled' : '' ?>>
                            <label class="settings-toggle" style="margin:0;">
                                <input type="checkbox" id="portal-text-color-on" name="portal_text_color_on" value="1" <?= $paTextColor !== '' ? 'checked' : '' ?>>
                                <span class="toggle-text"><strong><?= $this->te('Eigene Farbe verwenden') ?></strong></span>
                            </label>
                        </div>
                        <small class="form-hint"><?= $this->te('Betrifft Texte, die direkt auf dem Hintergrund liegen (Begrüßung, Seitentitel, Zurück-Link) – nicht die Karten. Nützlich bei dunklem Hintergrundbild, damit diese Texte lesbar bleiben. Ohne Haken gilt die Standardfarbe.') ?></small>
                    </div>
                </div>
            </section>

            <section class="settings-card">
                <div class="settings-card-header">
                    <span class="settings-card-icon">&#128101;</span>
                    <div>
                        <h2><?= $this->te('Portal-Zugriff') ?></h2>
                        <p class="settings-card-desc"><?= $this->te('Optional auf eine Gruppe beschränken. Ohne Gruppe haben alle aktiven Verzeichnis-Personen Zugriff.') ?></p>
                    </div>
                </div>
                <div class="settings-card-body">
                    <div class="form-row-2">
                        <div class="form-group">
                            <label for="access-group-entra"><?= $this->te('M365 / Entra ID – Gruppen-Objekt-ID') ?></label>
                            <input type="text" id="access-group-entra" name="access_group_entra" class="form-control" autocomplete="off"
                                   value="<?= $this->e($access_group_entra) ?>" placeholder="<?= $this->te('GUID der Sicherheitsgruppe') ?>">
                            <small class="form-hint"><?= $this->t('Voraussetzung: In der Azure-App-Registrierung muss der <strong>Groups-Claim</strong> im ID-Token aktiviert sein (Token-Konfiguration → Gruppenanspruch „Sicherheitsgruppen&ldquo;). Fehlt der Claim, schlägt die Gruppen-Prüfung bewusst fehl (fail-closed).') ?></small>
                        </div>
                        <div class="form-group">
                            <label for="access-group-ldap"><?= $this->te('LDAP / AD – Gruppen-DN') ?></label>
                            <input type="text" id="access-group-ldap" name="access_group_ldap" class="form-control" autocomplete="off"
                                   value="<?= $this->e($access_group_ldap) ?>" placeholder="<?= $this->te('CN=Portal-Nutzer,OU=Gruppen,DC=firma,DC=de') ?>">
                            <small class="form-hint"><?= $this->t('Vergleich gegen das <code>memberOf</code>-Attribut des angemeldeten Kontos (direkte Mitgliedschaft).') ?></small>
                        </div>
                    </div>
                </div>
            </section>

            <section class="settings-card">
                <div class="settings-card-header">
                    <span class="settings-card-icon">&#128272;</span>
                    <div>
                        <h2><?= $this->te('Login-Weiche') ?></h2>
                        <p class="settings-card-desc"><?= $this->te('Steuert, ob ein SSO-Login (Azure/LDAP) einen IT-Benutzer anlegt oder in die Portal-Prüfung läuft.') ?></p>
                    </div>
                </div>
                <div class="settings-card-body">
                    <p class="pa-note">
                        <i class="fas fa-shield-alt"></i>
                        <?= $this->t('<strong>Bestehende, aktive Benutzer melden sich immer an</strong> – unabhängig von dieser Einstellung kann niemand ausgesperrt werden. Die Weiche betrifft nur Logins, zu denen noch <em>kein</em> Benutzer existiert.') ?>
                    </p>
                    <div class="form-group">
                        <label for="provisioning"><?= $this->te('Neue SSO-Logins') ?></label>
                        <select id="provisioning" name="provisioning" class="form-control">
                            <option value="all" <?= $provisioning === 'all' ? 'selected' : '' ?>><?= $this->te('Jeder Login wird IT-Benutzer (bisheriges Verhalten)') ?></option>
                            <option value="existing" <?= $provisioning === 'existing' ? 'selected' : '' ?>><?= $this->te('Nur bestehende Benutzer – neue Logins gehen ins Portal (empfohlen bei aktivem Portal)') ?></option>
                            <option value="group" <?= $provisioning === 'group' ? 'selected' : '' ?>><?= $this->te('Per Gruppe – Mitglieder der IT-Gruppe werden Benutzer, alle anderen gehen ins Portal') ?></option>
                        </select>
                        <small class="form-hint"><?= $this->t('Achtung bei „Jeder Login wird IT-Benutzer&ldquo; und aktivem Portal: Mitarbeiter:innen würden beim ersten SSO-Login weiterhin als Benutzer angelegt und landen <em>nicht</em> im Portal. Für den Portal-Betrieb daher „Nur bestehende Benutzer&ldquo; oder „Per Gruppe&ldquo; wählen.') ?></small>
                    </div>
                    <div class="form-row-2">
                        <div class="form-group">
                            <label for="user-group-entra"><?= $this->te('IT-Gruppe M365 / Entra ID – Gruppen-Objekt-ID') ?></label>
                            <input type="text" id="user-group-entra" name="user_group_entra" class="form-control" autocomplete="off"
                                   value="<?= $this->e($user_group_entra) ?>" placeholder="<?= $this->te('GUID der IT-Mitarbeiter-Gruppe') ?>">
                            <small class="form-hint"><?= $this->te('Nur bei „Per Gruppe&ldquo; wirksam.') ?></small>
                        </div>
                        <div class="form-group">
                            <label for="user-group-ldap"><?= $this->te('IT-Gruppe LDAP / AD – Gruppen-DN') ?></label>
                            <input type="text" id="user-group-ldap" name="user_group_ldap" class="form-control" autocomplete="off"
                                   value="<?= $this->e($user_group_ldap) ?>" placeholder="<?= $this->te('CN=IT-Team,OU=Gruppen,DC=firma,DC=de') ?>">
                            <small class="form-hint"><?= $this->te('Nur bei „Per Gruppe&ldquo; wirksam.') ?></small>
                        </div>
                    </div>
                </div>
            </section>

            <section class="settings-card">
                <div class="settings-card-header">
                    <span class="settings-card-icon">&#128203;</span>
                    <div>
                        <h2><?= $this->te('Geräte-Selbstbestätigung') ?></h2>
                        <p class="settings-card-desc"><?= $this->te('Mitarbeiter:innen bestätigen im Portal periodisch, dass ihre Geräte noch vorhanden sind – die Inventur läuft damit nebenbei mit.') ?></p>
                    </div>
                </div>
                <div class="settings-card-body">
                    <?php if (empty($assets_module)): ?>
                    <p class="pa-note pa-note--warn">
                        <i class="fas fa-exclamation-triangle"></i>
                        <?= $this->te('Das Asset-Modul ist derzeit deaktiviert – ohne Assets gibt es nichts zu bestätigen.') ?>
                    </p>
                    <?php endif; ?>
                    <div class="form-group">
                        <label class="settings-toggle">
                            <input type="checkbox" name="confirm_enabled" value="1" id="pa-confirm-enabled" <?= !empty($confirm_enabled) ? 'checked' : '' ?>>
                            <span class="toggle-text">
                                <strong><?= $this->te('Geräte-Selbstbestätigung aktivieren') ?></strong>
                                <small><?= $this->te('Fällige Geräte erscheinen unter „Meine Geräte“ mit der Rückfrage „Gerät noch vorhanden?“. Deaktiviert ist im Portal nichts davon sichtbar.') ?></small>
                            </span>
                        </label>
                    </div>
                    <div id="pa-confirm-fields" class="pa-dependent">
                        <div class="form-group">
                            <label for="confirm-interval"><?= $this->te('Bestätigungs-Intervall') ?></label>
                            <div class="pa-inline-field">
                                <input type="number" id="confirm-interval" name="confirm_interval" class="form-control"
                                       min="1" max="60" value="<?= (int)($confirm_interval ?? 12) ?>">
                                <span><?= $this->te('Monate') ?></span>
                            </div>
                            <small class="form-hint"><?= $this->te('Nach dieser Zeit wird ein Gerät erneut zur Bestätigung fällig. Standard: 12 Monate.') ?></small>
                        </div>
                        <div class="form-group">
                            <label class="settings-toggle">
                                <input type="checkbox" name="confirm_mail" value="1" <?= !empty($confirm_mail) ? 'checked' : '' ?>>
                                <span class="toggle-text">
                                    <strong><?= $this->te('Erinnerungs-Mail versenden') ?></strong>
                                    <small><?= $this->te('Ein Cron-Task prüft täglich, ob eine neue Erinnerungswelle fällig ist, und schreibt Personen mit offenen Geräten an.') ?>
                                        <?php if (!empty($confirm_mail_last)): ?><?= $this->te('Zuletzt versendet am') ?> <?= $this->e(date('d.m.Y', strtotime((string)$confirm_mail_last))) ?>.<?php endif; ?></small>
                                </span>
                            </label>
                        </div>
                        <div class="pa-stats">
                            <span class="pa-stat"><strong><?= (int)($confirm_stats['confirmed'] ?? 0) ?></strong> <?= $this->te('bestätigt') ?></span>
                            <span class="pa-stat"><strong><?= (int)($confirm_stats['open'] ?? 0) ?></strong> <?= $this->te('offen') ?></span>
                            <span class="pa-stat"><strong><?= (int)($confirm_stats['missing'] ?? 0) ?></strong> <?= $this->te('nicht mehr vorhanden') ?></span>
                            <a href="/admin/portal/confirmations" class="pa-stat-link"><?= $this->te('Rückläufer-Report öffnen') ?> <i class="fas fa-arrow-right"></i></a>
                        </div>
                    </div>
                </div>
            </section>
        </div>

        <div class="pa-actions">
            <button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> <?= $this->te('Einstellungen speichern') ?></button>
            <span class="pa-actions-hint"><?= $this->te('Gilt für alle Abschnitte auf dieser Seite – eigene Links werden separat gespeichert.') ?></span>
        </div>
    </form>

    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128279;</span>
            <div>
                <h2><?= $this->te('Eigene Links') ?></h2>
                <p class="settings-card-desc"><?= $this->te('Verweise auf andere Programme (z.&nbsp;B. Intranet) – wahlweise als Kachel auf der Startseite oder als Eintrag in der Kopfleiste.') ?></p>
            </div>
        </div>
        <div class="settings-card-body">
            <?php if (!empty($links)): ?>
            <div class="table-wrapper pa-links-table">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th scope="col"><?= $this->te('Titel') ?></th>
                            <th scope="col"><?= $this->te('Adresse') ?></th>
                            <th scope="col"><?= $this->te('Anzeige') ?></th>
                            <th scope="col"><?= $this->te('Standort') ?></th>
                            <th scope="col" class="pa-col-num"><?= $this->te('Reihenfolge') ?></th>
                            <th scope="col"><?= $this->te('Status') ?></th>
                            <th scope="col" class="pa-col-actions"></th>
                        </tr>
                    </thead>
                    <tbody>
                    <?php if (!empty($global_content_hint)): ?>
                        <div class="alert alert-info" role="status">
                            <strong><?= $this->te('Hinweis zu standortbezogenen Inhalten:') ?></strong>
                            <?= $this->te('Es sind') ?> <?= (int)$global_content_hint['domains'] ?> <?= $this->te('E-Mail-Domain(s) Standorten zugeordnet, aber') ?> <?= (int)$global_content_hint['links'] ?> <?= $this->te('Link(s) und') ?>
                            <?= (int)$global_content_hint['announcements'] ?> <?= $this->te('Ank&uuml;ndigung(en) stehen auf &bdquo;Alle Standorte&ldquo; &ndash; sie erscheinen damit bei allen Portal-Nutzern.') ?>
                        </div>
                    <?php endif; ?>
                    <?php
                    // Standort-Namen für die Anzeige (leer = alle Standorte)
                    $locNames = [];
                    foreach (($locations ?? []) as $lc) { $locNames[(int)$lc['id']] = (string)$lc['name']; }
                    $locLabel = function ($id) use ($locNames) {
                        $id = (int)$id;
                        return $id > 0 ? ($locNames[$id] ?? ('#' . $id)) : null;
                    };
                    ?>
                    <?php foreach ($links as $l): ?>
                        <tr>
                            <td>
                                <span class="pa-link-title">
                                    <span class="pa-link-icon"><i class="fas <?= $this->e($l['icon']) ?>"></i></span>
                                    <span>
                                        <strong><?= $this->e($l['title']) ?></strong>
                                        <?php if (!empty($l['description'])): ?><br><span class="text-muted"><?= $this->e($l['description']) ?></span><?php endif; ?>
                                    </span>
                                </span>
                            </td>
                            <td><span class="text-muted pa-link-url"><?= $this->e($l['url']) ?></span>
                                <?php if ((int)$l['new_tab'] === 1): ?><i class="fas fa-external-link-alt pa-link-tab" title="<?= $this->te('Öffnet in neuem Tab') ?>"></i><?php endif; ?>
                            </td>
                            <td><?= $this->e($placementLabels[$l['placement']] ?? $l['placement']) ?></td>
                            <td>
                                <?php $ln = $locLabel($l['location_id'] ?? 0); ?>
                                <?php if ($ln !== null): ?>
                                    <span class="badge badge-secondary"><?= $this->e($ln) ?></span>
                                <?php else: ?>
                                    <span class="text-muted"><?= $this->te('Alle') ?></span>
                                <?php endif; ?>
                            </td>
                            <td class="pa-col-num"><?= (int)$l['sort_order'] ?></td>
                            <td>
                                <?php if ((int)$l['is_active'] === 1): ?>
                                    <span class="status-pill status-pill--success"><?= $this->te('Aktiv') ?></span>
                                <?php else: ?>
                                    <span class="status-pill status-pill--neutral"><?= $this->te('Inaktiv') ?></span>
                                <?php endif; ?>
                            </td>
                            <td class="pa-col-actions">
                                <button type="button" class="btn btn-xs btn-default"
                                        onclick='portalLinkEdit(<?= htmlspecialchars(json_encode($l, JSON_UNESCAPED_UNICODE), ENT_QUOTES) ?>)'
                                        title="<?= $this->te('Bearbeiten') ?>"><i class="fas fa-pencil-alt"></i></button>
                                <form method="post" action="/admin/portal/links/<?= (int)$l['id'] ?>/delete"
                                      onsubmit="return confirm('Link wirklich entfernen?');">
                                    <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                                    <button type="submit" class="btn btn-xs btn-outline-danger" title="<?= $this->te('Entfernen') ?>"><i class="fas fa-times"></i></button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php else: ?>
            <div class="empty-state">
                <i class="fas fa-link"></i>
                <p><?= $this->te('Noch keine eigenen Links angelegt.') ?></p>
            </div>
            <?php endif; ?>

            <form method="post" action="/admin/portal/links" id="portal-link-form" class="pa-link-form">
                <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                <input type="hidden" name="id" id="pl-id" value="">
                <h3 class="pa-subhead" id="pl-heading"><?= $this->te('Neuen Link anlegen') ?></h3>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pl-title"><?= $this->te('Titel') ?> <span class="text-danger">*</span></label>
                        <input type="text" id="pl-title" name="title" class="form-control" maxlength="100" required placeholder="<?= $this->te('z. B. Intranet') ?>">
                    </div>
                    <div class="form-group">
                        <label for="pl-url"><?= $this->te('Adresse') ?> <span class="text-danger">*</span></label>
                        <input type="text" id="pl-url" name="url" class="form-control" maxlength="500" required placeholder="https://intranet.firma.de">
                        <small class="form-hint"><?= $this->t('Vollständige Adresse mit <code>https://</code> oder ein interner Pfad wie <code>/portal/hilfe</code>.') ?></small>
                    </div>
                </div>

                <div class="form-group">
                    <label for="pl-description"><?= $this->te('Kurzbeschreibung (für die Kachel)') ?></label>
                    <input type="text" id="pl-description" name="description" class="form-control" maxlength="255" placeholder="<?= $this->te('Nachrichten, Formulare und Termine') ?>">
                </div>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pl-placement"><?= $this->te('Anzeige') ?></label>
                        <select id="pl-placement" name="placement" class="form-control">
                            <option value="tile"><?= $this->te('Kachel auf der Startseite') ?></option>
                            <option value="menu"><?= $this->te('Eintrag in der Kopfleiste') ?></option>
                            <option value="both"><?= $this->te('Beides') ?></option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="pl-sort"><?= $this->te('Reihenfolge') ?></label>
                        <input type="number" id="pl-sort" name="sort_order" class="form-control" value="0">
                        <small class="form-hint"><?= $this->te('Kleinere Werte stehen weiter vorn.') ?></small>
                    </div>
                </div>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pl-location"><?= $this->te('Standort') ?></label>
                        <select id="pl-location" name="location_id" class="form-control">
                            <?php if ($can_manage_global ?? true): ?>
                                <option value=""><?= $this->te('Alle Standorte') ?></option>
                            <?php endif; ?>
                            <?php foreach (($locations ?? []) as $loc): ?>
                                <option value="<?= (int)$loc['id'] ?>"><?= str_repeat('&nbsp;&nbsp;', (int)($loc['_depth'] ?? 0)) . $this->e($loc['name']) ?></option>
                            <?php endforeach; ?>
                        </select>
                        <small class="form-hint"><?= $this->te('Leer = f&uuml;r alle sichtbar. Ein Oberstandort gilt auch f&uuml;r dessen Unterstandorte.') ?></small>
                    </div>
                    <div class="form-group"></div>
                </div>

                <div class="form-group">
                    <label for="pl-icon-search"><?= $this->te('Symbol') ?></label>
                    <input type="hidden" id="pl-icon" name="icon" value="fa-link">
                    <div class="pli-picker">
                        <div class="pli-current">
                            <span class="pli-preview"><i class="fas fa-link" id="pl-icon-preview"></i></span>
                            <code id="pl-icon-name">fa-link</code>
                        </div>
                        <input type="search" id="pl-icon-search" class="form-control form-control-sm"
                               placeholder="<?= $this->te('Symbol suchen (z. B. globe, news, clock)…') ?>" autocomplete="off"
                               aria-label="<?= $this->te('Symbole durchsuchen') ?>">
                        <div class="pli-grid" id="pl-icon-grid">
                            <?php foreach (($icon_choices ?? []) as $ic): ?>
                            <button type="button" class="pli-item<?= $ic === 'fa-link' ? ' active' : '' ?>"
                                    data-icon="<?= $this->e($ic) ?>" title="<?= $this->e($ic) ?>">
                                <i class="fas <?= $this->e($ic) ?>"></i>
                            </button>
                            <?php endforeach; ?>
                        </div>
                    </div>
                    <small class="form-hint"><?= $this->te('Erscheint auf der Kachel und im Menü des Portals.') ?></small>
                </div>

                <div class="settings-toggles pa-link-toggles">
                    <label class="settings-toggle">
                        <input type="checkbox" name="new_tab" id="pl-newtab" value="1" checked>
                        <span class="toggle-text"><strong><?= $this->te('In neuem Tab öffnen') ?></strong></span>
                    </label>
                    <label class="settings-toggle">
                        <input type="checkbox" name="is_active" id="pl-active" value="1" checked>
                        <span class="toggle-text"><strong><?= $this->te('Aktiv') ?></strong> <small><?= $this->te('Nur aktive Links erscheinen im Portal.') ?></small></span>
                    </label>
                </div>

                <div class="pa-form-actions">
                    <button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> <?= $this->te('Link speichern') ?></button>
                    <button type="button" class="btn btn-default" id="pl-reset" hidden><?= $this->te('Abbrechen') ?></button>
                </div>
            </form>
        </div>
    </section>

    <section class="settings-card">
        <div class="settings-card-header">
            <span class="settings-card-icon">&#128227;</span>
            <div>
                <h2><?= $this->te('Ankündigungen') ?></h2>
                <p class="settings-card-desc"><?= $this->te('Allgemeine Hinweise für Mitarbeiter:innen (z.&nbsp;B. „Neue Software verfügbar"). Erscheinen im aktiven Zeitraum als Karte auf der Portal-Startseite – getrennt von Störungen und Wartungen.') ?></p>
            </div>
        </div>
        <div class="settings-card-body">
            <?php
            $annLevels = ['info' => 'Info', 'success' => 'Erfolg', 'warning' => 'Warnung'];
            $fmtLocal  = static fn(?string $v): string => $v ? date('d.m.Y H:i', strtotime($v)) : '';
            ?>
            <?php if (!empty($announcements)): ?>
            <div class="table-wrapper pa-links-table">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th scope="col"><?= $this->te('Titel') ?></th>
                            <th scope="col"><?= $this->te('Stufe') ?></th>
                            <th scope="col"><?= $this->te('Standort') ?></th>
                            <th scope="col"><?= $this->te('Zeitraum') ?></th>
                            <th scope="col"><?= $this->te('Status') ?></th>
                            <th scope="col" class="pa-col-actions"></th>
                        </tr>
                    </thead>
                    <tbody>
                    <?php foreach ($announcements as $a): ?>
                        <tr>
                            <td>
                                <strong><?= $this->e($a['title']) ?></strong>
                                <?php if (!empty($a['body'])): ?><br><span class="text-muted"><?= $this->e(mb_strimwidth((string)$a['body'], 0, 90, '…')) ?></span><?php endif; ?>
                            </td>
                            <td><?= $this->e($annLevels[$a['level']] ?? $a['level']) ?></td>
                            <td>
                                <?php $an = $locLabel($a['location_id'] ?? 0); ?>
                                <?php if ($an !== null): ?>
                                    <span class="badge badge-secondary"><?= $this->e($an) ?></span>
                                <?php else: ?>
                                    <span class="text-muted"><?= $this->te('Alle') ?></span>
                                <?php endif; ?>
                            </td>
                            <td class="text-muted">
                                <?php if (empty($a['starts_at']) && empty($a['ends_at'])): ?>—<?php else: ?>
                                    <?= $this->e($fmtLocal($a['starts_at'] ?? null) ?: 'sofort') ?> – <?= $this->e($fmtLocal($a['ends_at'] ?? null) ?: 'unbefristet') ?>
                                <?php endif; ?>
                            </td>
                            <td>
                                <?php if ((int)$a['is_active'] === 1): ?>
                                    <span class="status-pill status-pill--success"><?= $this->te('Aktiv') ?></span>
                                <?php else: ?>
                                    <span class="status-pill status-pill--neutral"><?= $this->te('Inaktiv') ?></span>
                                <?php endif; ?>
                            </td>
                            <td class="pa-col-actions">
                                <button type="button" class="btn btn-xs btn-default"
                                        onclick='portalAnnEdit(<?= htmlspecialchars(json_encode($a, JSON_UNESCAPED_UNICODE), ENT_QUOTES) ?>)'
                                        title="<?= $this->te('Bearbeiten') ?>"><i class="fas fa-pencil-alt"></i></button>
                                <form method="post" action="/admin/portal/announcements/<?= (int)$a['id'] ?>/delete"
                                      onsubmit="return confirm('Ankündigung wirklich entfernen?');">
                                    <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                                    <button type="submit" class="btn btn-xs btn-outline-danger" title="<?= $this->te('Entfernen') ?>"><i class="fas fa-times"></i></button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
            <?php else: ?>
            <div class="empty-state">
                <i class="fas fa-bullhorn"></i>
                <p><?= $this->te('Noch keine Ankündigungen angelegt.') ?></p>
            </div>
            <?php endif; ?>

            <form method="post" action="/admin/portal/announcements" id="portal-ann-form" class="pa-link-form">
                <input type="hidden" name="_csrf" value="<?= $this->e($this->csrf()) ?>">
                <input type="hidden" name="id" id="pa-ann-id" value="">
                <h3 class="pa-subhead" id="pa-ann-heading"><?= $this->te('Neue Ankündigung') ?></h3>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pa-ann-title"><?= $this->te('Titel') ?> <span class="text-danger">*</span></label>
                        <input type="text" id="pa-ann-title" name="title" class="form-control" maxlength="200" required placeholder="<?= $this->te('z. B. Wartungsfreies Wochenende') ?>">
                    </div>
                    <div class="form-group">
                        <label for="pa-ann-level"><?= $this->te('Stufe') ?></label>
                        <select id="pa-ann-level" name="level" class="form-control">
                            <option value="info"><?= $this->te('Info (blau)') ?></option>
                            <option value="success"><?= $this->te('Erfolg (grün)') ?></option>
                            <option value="warning"><?= $this->te('Warnung (gelb)') ?></option>
                        </select>
                    </div>
                </div>

                <div class="form-group">
                    <label for="pa-ann-body"><?= $this->te('Text') ?></label>
                    <textarea id="pa-ann-body" name="body" class="form-control" rows="3" maxlength="5000" placeholder="<?= $this->te('Kurzer Hinweis für Mitarbeiter:innen…') ?>"></textarea>
                </div>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pa-ann-start"><?= $this->te('Sichtbar ab') ?></label>
                        <input type="datetime-local" id="pa-ann-start" name="starts_at" class="form-control">
                        <small class="form-hint"><?= $this->te('Leer = sofort.') ?></small>
                    </div>
                    <div class="form-group">
                        <label for="pa-ann-end"><?= $this->te('Sichtbar bis') ?></label>
                        <input type="datetime-local" id="pa-ann-end" name="ends_at" class="form-control">
                        <small class="form-hint"><?= $this->te('Leer = unbefristet.') ?></small>
                    </div>
                </div>

                <div class="form-row-2">
                    <div class="form-group">
                        <label for="pa-ann-location"><?= $this->te('Standort') ?></label>
                        <select id="pa-ann-location" name="location_id" class="form-control">
                            <?php if ($can_manage_global ?? true): ?>
                                <option value=""><?= $this->te('Alle Standorte') ?></option>
                            <?php endif; ?>
                            <?php foreach (($locations ?? []) as $loc): ?>
                                <option value="<?= (int)$loc['id'] ?>"><?= str_repeat('&nbsp;&nbsp;', (int)($loc['_depth'] ?? 0)) . $this->e($loc['name']) ?></option>
                            <?php endforeach; ?>
                        </select>
                        <small class="form-hint"><?= $this->te('Leer = f&uuml;r alle sichtbar. Ein Oberstandort gilt auch f&uuml;r dessen Unterstandorte.') ?></small>
                    </div>
                    <div class="form-group"></div>
                </div>

                <div class="settings-toggles pa-link-toggles">
                    <label class="settings-toggle">
                        <input type="checkbox" name="is_active" id="pa-ann-active" value="1" checked>
                        <span class="toggle-text"><strong><?= $this->te('Aktiv') ?></strong> <small><?= $this->te('Nur aktive Ankündigungen erscheinen im Portal.') ?></small></span>
                    </label>
                </div>

                <div class="pa-form-actions">
                    <button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> <?= $this->te('Ankündigung speichern') ?></button>
                    <button type="button" class="btn btn-default" id="pa-ann-reset" hidden><?= $this->te('Abbrechen') ?></button>
                </div>
            </form>
        </div>
    </section>
</div>


<script>
// Ankündigung bearbeiten: bestehende Werte ins Formular laden; Abbrechen setzt zurück.
// datetime-local erwartet 'YYYY-MM-DDTHH:MM' – aus dem DB-DATETIME ableiten.
function portalAnnToLocal(v) {
    if (!v) { return ''; }
    return String(v).replace(' ', 'T').slice(0, 16);
}
function portalAnnEdit(a) {
    document.getElementById('pa-ann-id').value = a.id;
    document.getElementById('pa-ann-title').value = a.title || '';
    document.getElementById('pa-ann-body').value = a.body || '';
    document.getElementById('pa-ann-level').value = a.level || 'info';
    document.getElementById('pa-ann-start').value = portalAnnToLocal(a.starts_at);
    document.getElementById('pa-ann-end').value = portalAnnToLocal(a.ends_at);
    document.getElementById('pa-ann-active').checked = String(a.is_active) === '1';
    document.getElementById('pa-ann-heading').textContent = 'Ankündigung bearbeiten';
    document.getElementById('pa-ann-location').value = a.location_id ? String(a.location_id) : '';
    document.getElementById('pa-ann-reset').hidden = false;
    document.getElementById('portal-ann-form').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
document.getElementById('pa-ann-reset').addEventListener('click', function () {
    document.getElementById('portal-ann-form').reset();
    document.getElementById('pa-ann-id').value = '';
    document.getElementById('pa-ann-heading').textContent = 'Neue Ankündigung';
    this.hidden = true;
});
</script>


<script>
// Abhaengige Abschnitte optisch daempfen, solange das Portal bzw. die
// Selbstbestaetigung aus ist - bedienbar bleiben sie, damit man alles
// vorbereiten kann.
function paSyncDependent(sourceId, targetId, noteId) {
    var source = document.getElementById(sourceId);
    var target = document.getElementById(targetId);
    if (!source || !target) { return; }
    var apply = function () {
        target.classList.toggle('is-off', !source.checked);
        if (noteId) {
            var note = document.getElementById(noteId);
            if (note) { note.hidden = source.checked; }
        }
    };
    source.addEventListener('change', apply);
    apply();
}
paSyncDependent('pa-enabled', 'pa-dependent', 'pa-off-note');
paSyncDependent('pa-confirm-enabled', 'pa-confirm-fields', null);

// Overlay-Regler: Prozentwert und Vorschau des Hintergrundbilds mitfuehren.
(function () {
    var slider = document.getElementById('portal-overlay');
    if (!slider) { return; }
    var value   = document.getElementById('portal-overlay-value');
    var preview = document.getElementById('pa-bg-preview');
    slider.addEventListener('input', function () {
        value.textContent = slider.value + ' %';
        if (preview) { preview.style.setProperty('--pa-overlay', (slider.value / 100).toFixed(2)); }
    });
})();

// Eigene Textfarbe: Farbwaehler nur aktiv, wenn der Haken gesetzt ist.
(function () {
    var toggle = document.getElementById('portal-text-color-on');
    var picker = document.getElementById('portal-text-color');
    if (!toggle || !picker) { return; }
    var apply = function () { picker.disabled = !toggle.checked; };
    toggle.addEventListener('change', apply);
    apply();
})();

// Datei-Uploads vor dem Absenden gegen das Server-Limit pruefen: eine zu große
// Datei wuerde sonst serverseitig abgewiesen (oder – bei Ueberschreiten von
// post_max_size – den kompletten POST samt CSRF-Token verwerfen). Deshalb hier
// frueh und mit klarer Meldung stoppen.
(function () {
    var form = document.getElementById('pa-form');
    if (!form) { return; }
    var fileInputs = form.querySelectorAll('input[type="file"][data-max-bytes]');

    function tooBig(input) {
        var max = parseInt(input.getAttribute('data-max-bytes'), 10);
        if (!max || !input.files || !input.files.length) { return null; }
        var f = input.files[0];
        return f.size > max ? { name: f.name, label: input.getAttribute('data-max-label') || '' } : null;
    }

    fileInputs.forEach(function (input) {
        input.addEventListener('change', function () {
            var over = tooBig(input);
            input.setCustomValidity(over ? 'Datei zu groß (max. ' + over.label + ').' : '');
            if (over) {
                alert('„' + over.name + '" ist zu groß.\nErlaubt sind maximal ' + over.label + '. Bitte ein kleineres Bild wählen.');
                input.value = '';
                input.setCustomValidity('');
            }
        });
    });

    form.addEventListener('submit', function (e) {
        for (var i = 0; i < fileInputs.length; i++) {
            var over = tooBig(fileInputs[i]);
            if (over) {
                e.preventDefault();
                alert('„' + over.name + '" ist zu groß.\nErlaubt sind maximal ' + over.label + '. Bitte ein kleineres Bild wählen.');
                fileInputs[i].focus();
                return false;
            }
        }
    });
})();

// Symbolauswahl: Klick setzt das versteckte Feld, das Suchfeld filtert die Kacheln.
function portalLinkSetIcon(icon) {
    document.getElementById('pl-icon').value = icon;
    document.getElementById('pl-icon-name').textContent = icon;
    document.getElementById('pl-icon-preview').className = 'fas ' + icon;
    document.querySelectorAll('#pl-icon-grid .pli-item').forEach(function (btn) {
        btn.classList.toggle('active', btn.dataset.icon === icon);
    });
}
document.getElementById('pl-icon-grid').addEventListener('click', function (e) {
    var btn = e.target.closest('.pli-item');
    if (btn) { portalLinkSetIcon(btn.dataset.icon); }
});
document.getElementById('pl-icon-search').addEventListener('input', function () {
    var needle = this.value.trim().toLowerCase();
    var visible = 0;
    document.querySelectorAll('#pl-icon-grid .pli-item').forEach(function (btn) {
        var match = needle === '' || btn.dataset.icon.indexOf(needle) !== -1;
        btn.style.display = match ? '' : 'none';
        if (match) { visible++; }
    });
    var empty = document.getElementById('pl-icon-empty');
    if (!empty) {
        empty = document.createElement('p');
        empty.id = 'pl-icon-empty';
        empty.className = 'pli-empty';
        empty.textContent = 'Kein Symbol gefunden.';
        document.getElementById('pl-icon-grid').appendChild(empty);
    }
    empty.style.display = visible === 0 ? '' : 'none';
});

// Bearbeiten: bestehenden Link ins Formular laden; Abbrechen setzt zurueck.
function portalLinkEdit(link) {
    document.getElementById('pl-id').value = link.id;
    document.getElementById('pl-title').value = link.title;
    document.getElementById('pl-url').value = link.url;
    document.getElementById('pl-description').value = link.description || '';
    portalLinkSetIcon(link.icon || 'fa-link');
    document.getElementById('pl-placement').value = link.placement;
    document.getElementById('pl-sort').value = link.sort_order;
    document.getElementById('pl-newtab').checked = String(link.new_tab) === '1';
    document.getElementById('pl-active').checked = String(link.is_active) === '1';
    document.getElementById('pl-location').value = link.location_id ? String(link.location_id) : '';
    document.getElementById('pl-heading').textContent = 'Link bearbeiten';
    document.getElementById('pl-reset').hidden = false;
    document.getElementById('portal-link-form').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
document.getElementById('pl-reset').addEventListener('click', function () {
    document.getElementById('portal-link-form').reset();
    document.getElementById('pl-id').value = '';
    portalLinkSetIcon('fa-link');
    document.getElementById('pl-heading').textContent = 'Neuen Link anlegen';
    this.hidden = true;
});
</script>