Sicherheits-Header, Werkzeuge und Dokumentation
Sicherheit: - .htaccess im Projektstamm mit X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy und einer Content-Security-Policy; da alle Assets lokal liegen, erlaubt sie nur noch die eigene Herkunft (Ausnahme: hCaptcha, falls aktiviert) - includes/, tools/, tests/, updater/storage und uploads/ schützen sich über eigene .htaccess-Dateien – auch bei Installation im Unterordner - Docker: AllowOverride All, damit diese Regeln überhaupt greifen, und ein Volume für uploads/, damit Logos ein Image-Update überstehen Werkzeuge: - tools/screenshots.py erzeugt alle Bilder in docs/screenshots aus der Demo-Instanz; tools/README.md beschreibt beides - Einstellungs-Tabs sind per ?tab=… direkt verlinkbar (serverseitig, also auch ohne JavaScript) Dokumentation: Readme um Markenfarben, Bild-Upload, lokale Assets, Sicherheits-Header (inkl. Nginx-Entsprechung) und einen Abschnitt "Entwicklung" ergänzt; Screenshots neu erzeugt, Version 2.6.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e28527ed91
commit
36e06ac817
30 changed files with 307 additions and 36 deletions
|
|
@ -243,6 +243,22 @@ $cs = [
|
|||
'email_user_notification_subject' => $db->getSetting('email_user_notification_subject', '{APP_TITLE} - Berechtigungen geändert'),
|
||||
'email_user_notification_body' => $db->getSetting('email_user_notification_body', "Hallo {USER_NAME},\n\n{CHANGES}"),
|
||||
'print_template' => $db->getSetting('print_template', Ui::defaultPrintTemplate()),
|
||||
'brand_accent' => $db->getSetting('brand_accent', '') ?: Ui::DEFAULT_ACCENT,
|
||||
'brand_accent_dark' => $db->getSetting('brand_accent_dark', '') ?: Ui::DEFAULT_ACCENT_DARK,
|
||||
'brand_gradient_from' => $db->getSetting('brand_gradient_from', '') ?: Ui::DEFAULT_GRADIENT_FROM,
|
||||
'brand_gradient_to' => $db->getSetting('brand_gradient_to', '') ?: Ui::DEFAULT_GRADIENT_TO,
|
||||
'brand_radius' => $db->getSetting('brand_radius', (string)Ui::DEFAULT_RADIUS),
|
||||
'login_panel_enabled' => $db->getSetting('login_panel_enabled', '1'),
|
||||
'login_brand_name' => $db->getSetting('login_brand_name', ''),
|
||||
'login_logo_url' => $db->getSetting('login_logo_url', ''),
|
||||
'login_claim_title' => $db->getSetting('login_claim_title', ''),
|
||||
'login_claim_text' => $db->getSetting('login_claim_text', ''),
|
||||
'login_features' => $db->getSetting('login_features', ''),
|
||||
'login_footer' => $db->getSetting('login_footer', ''),
|
||||
'login_bg_image' => $db->getSetting('login_bg_image', ''),
|
||||
'login_bg_from' => $db->getSetting('login_bg_from', '#3b2f8f'),
|
||||
'login_bg_to' => $db->getSetting('login_bg_to', '#6d5ce7'),
|
||||
'login_bg_overlay' => $db->getSetting('login_bg_overlay', '40'),
|
||||
'cron_token' => $db->getSetting('cron_token', ''),
|
||||
'last_cron_sync' => $db->getSetting('last_cron_sync', ''),
|
||||
];
|
||||
|
|
@ -250,8 +266,9 @@ $cs = [
|
|||
/**
|
||||
* Bildfeld: Vorschau, Upload, alternativ URL – plus Entfernen-Schalter.
|
||||
*/
|
||||
function imageField(string $name, string $label, string $value, string $hint = '', string $accept = 'image/*'): void
|
||||
function imageField(string $name, string $label, ?string $value, string $hint = '', string $accept = 'image/*'): void
|
||||
{
|
||||
$value = (string)$value;
|
||||
$preview = Ui::mediaUrl($value, '../');
|
||||
?>
|
||||
<div class="form-group">
|
||||
|
|
@ -278,6 +295,9 @@ function imageField(string $name, string $label, string $value, string $hint = '
|
|||
<?php
|
||||
}
|
||||
|
||||
// Aktiver Tab per ?tab=… (Deep-Link, funktioniert auch ohne JavaScript)
|
||||
$activeTab = preg_replace('/[^a-z_]/', '', (string)($_GET['tab'] ?? 'general')) ?: 'general';
|
||||
|
||||
$currentPage = 'settings';
|
||||
$adminBase = '';
|
||||
?>
|
||||
|
|
@ -310,20 +330,20 @@ $adminBase = '';
|
|||
|
||||
<div class="tab-container">
|
||||
<div class="tab-navigation" id="tabNav">
|
||||
<button class="tab-button active" data-tab="general"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_general') ?></button>
|
||||
<button class="tab-button" data-tab="defaults"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_defaults') ?></button>
|
||||
<button class="tab-button" data-tab="branding"><i class="fas fa-palette" aria-hidden="true"></i> <?= __('settings_tab_branding') ?></button>
|
||||
<button class="tab-button" data-tab="login"><i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('settings_tab_login') ?></button>
|
||||
<button class="tab-button" data-tab="cron"><i class="fas fa-clock" aria-hidden="true"></i> <?= __('settings_tab_cron') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'general' ? ' active' : '' ?>" data-tab="general"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_general') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'defaults' ? ' active' : '' ?>" data-tab="defaults"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_defaults') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'branding' ? ' active' : '' ?>" data-tab="branding"><i class="fas fa-palette" aria-hidden="true"></i> <?= __('settings_tab_branding') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'login' ? ' active' : '' ?>" data-tab="login"><i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('settings_tab_login') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'cron' ? ' active' : '' ?>" data-tab="cron"><i class="fas fa-clock" aria-hidden="true"></i> <?= __('settings_tab_cron') ?></button>
|
||||
<button class="tab-button" data-tab="m365"><i class="fab fa-microsoft" aria-hidden="true"></i> <?= __('settings_tab_m365') ?></button>
|
||||
<button class="tab-button" data-tab="smtp"><i class="fas fa-envelope" aria-hidden="true"></i> <?= __('settings_tab_smtp') ?></button>
|
||||
<button class="tab-button" data-tab="templates_email"><i class="fas fa-file-alt" aria-hidden="true"></i> <?= __('settings_tab_templates_email') ?></button>
|
||||
<button class="tab-button" data-tab="system"><i class="fas fa-cogs" aria-hidden="true"></i> <?= __('settings_tab_system') ?></button>
|
||||
<button class="tab-button" data-tab="password"><i class="fas fa-key" aria-hidden="true"></i> <?= __('settings_tab_password') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'smtp' ? ' active' : '' ?>" data-tab="smtp"><i class="fas fa-envelope" aria-hidden="true"></i> <?= __('settings_tab_smtp') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'templates_email' ? ' active' : '' ?>" data-tab="templates_email"><i class="fas fa-file-alt" aria-hidden="true"></i> <?= __('settings_tab_templates_email') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'system' ? ' active' : '' ?>" data-tab="system"><i class="fas fa-cogs" aria-hidden="true"></i> <?= __('settings_tab_system') ?></button>
|
||||
<button class="tab-button<?= $activeTab === 'password' ? ' active' : '' ?>" data-tab="password"><i class="fas fa-key" aria-hidden="true"></i> <?= __('settings_tab_password') ?></button>
|
||||
</div>
|
||||
|
||||
<!-- Allgemein -->
|
||||
<div id="tab-general" class="tab-content active">
|
||||
<div id="tab-general" class="tab-content<?= $activeTab === 'general' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_general') ?></h2>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
|
||||
|
|
@ -342,7 +362,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- Voucher-Standards -->
|
||||
<div id="tab-defaults" class="tab-content">
|
||||
<div id="tab-defaults" class="tab-content<?= $activeTab === 'defaults' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-sliders-h" aria-hidden="true"></i> <?= __('settings_tab_defaults') ?></h2>
|
||||
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_defaults_hint') ?></p>
|
||||
<form method="post">
|
||||
|
|
@ -378,7 +398,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- Design & Branding -->
|
||||
<div id="tab-branding" class="tab-content">
|
||||
<div id="tab-branding" class="tab-content<?= $activeTab === 'branding' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-palette" aria-hidden="true"></i> <?= __('settings_tab_branding') ?></h2>
|
||||
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_branding_intro') ?></p>
|
||||
<form method="post">
|
||||
|
|
@ -447,7 +467,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- Login-Seite -->
|
||||
<div id="tab-login" class="tab-content">
|
||||
<div id="tab-login" class="tab-content<?= $activeTab === 'login' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 8px; color: var(--text-primary);"><i class="fas fa-right-to-bracket" aria-hidden="true"></i> <?= __('settings_tab_login') ?></h2>
|
||||
<p style="color: var(--text-muted); font-size: 14px; margin-bottom: 24px;"><?= __('settings_login_intro') ?></p>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
|
|
@ -525,7 +545,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- Cron-Sync -->
|
||||
<div id="tab-cron" class="tab-content">
|
||||
<div id="tab-cron" class="tab-content<?= $activeTab === 'cron' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-clock" aria-hidden="true"></i> <?= __('settings_tab_cron') ?></h2>
|
||||
<div class="info-box">
|
||||
<h4><i class="fas fa-info-circle" aria-hidden="true"></i> <?= __('settings_cron_what') ?></h4>
|
||||
|
|
@ -589,7 +609,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- SMTP -->
|
||||
<div id="tab-smtp" class="tab-content">
|
||||
<div id="tab-smtp" class="tab-content<?= $activeTab === 'smtp' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-envelope" aria-hidden="true"></i> SMTP</h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
|
||||
|
|
@ -620,7 +640,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- E-Mail Templates -->
|
||||
<div id="tab-templates_email" class="tab-content">
|
||||
<div id="tab-templates_email" class="tab-content<?= $activeTab === 'templates_email' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-file-alt" aria-hidden="true"></i> E-Mail Templates</h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
|
||||
|
|
@ -641,7 +661,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- System -->
|
||||
<div id="tab-system" class="tab-content">
|
||||
<div id="tab-system" class="tab-content<?= $activeTab === 'system' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-cogs" aria-hidden="true"></i> System & Erweitert</h2>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
|
||||
|
|
@ -667,7 +687,7 @@ $adminBase = '';
|
|||
</div>
|
||||
|
||||
<!-- Passwort -->
|
||||
<div id="tab-password" class="tab-content">
|
||||
<div id="tab-password" class="tab-content<?= $activeTab === 'password' ? ' active' : '' ?>">
|
||||
<h2 style="margin-bottom: 20px; color: var(--text-primary);"><i class="fas fa-key" aria-hidden="true"></i> <?= __('settings_tab_password') ?></h2>
|
||||
<form method="post" style="max-width:500px;">
|
||||
<input type="hidden" name="csrf_token" value="<?= $auth->getCsrfToken() ?>">
|
||||
|
|
@ -731,7 +751,7 @@ document.querySelectorAll('.color-swatch').forEach(swatch => {
|
|||
});
|
||||
});
|
||||
|
||||
// Restore tab from hash
|
||||
// Tab aus Anker uebernehmen (der Query-Parameter wird serverseitig gesetzt)
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
const hash = location.hash.substring(1);
|
||||
if (hash) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue