Files
WebAPP/app/Http/Controllers/Admin/SettingsController.php
Rhino 8ccadbe89f Feature-Toggles, Administration, wiederkehrende Events und Event-Serien
- Administration & Rollenmanagement: Neuer Admin-Bereich mit Feature-Toggles
  und Sichtbarkeitseinstellungen pro Rolle (11 Toggles, 24 Visibility-Settings)
- AdministrationController mit eigenem Settings-Tab, aus SettingsController extrahiert
- Feature-Toggle-Guards in Controllers (Invitation, File, ListGenerator, Comment)
  und Views (events/show, events/edit, events/create)
- Setting::isFeatureEnabled() und isFeatureVisibleFor() Hilfsmethoden
- Wiederkehrende Trainings: Täglich/Wöchentlich/2-Wöchentlich mit Ende per
  Datum oder Anzahl (max. 52), Vorschau im Formular
- Event-Serien: Verknüpfung über event_series_id (UUID), Modal-Dialog beim
  Speichern und Löschen mit Optionen "nur dieses" / "alle folgenden"
- Löschen-Button direkt in der Event-Bearbeitung mit Serien-Dialog
- DemoDataSeeder: 4 Trainings als Serie mit gemeinsamer event_series_id
- Übersetzungen in allen 6 Sprachen (de, en, pl, ru, ar, tr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:38:45 +01:00

176 lines
7.1 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\FileCategory;
use App\Models\Season;
use App\Models\Setting;
use App\Services\HtmlSanitizerService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\View\View;
class SettingsController extends Controller
{
public function __construct(private HtmlSanitizerService $sanitizer) {}
public function edit(): View
{
if (!auth()->user()->isAdmin()) {
abort(403);
}
$allSettings = Setting::all()->keyBy('key');
// Event-Default-Keys separieren — immer alle liefern (auch wenn nicht in DB)
$eventDefaults = collect();
foreach (['home_game', 'away_game', 'training', 'tournament', 'meeting'] as $type) {
foreach (['players', 'catering', 'timekeepers'] as $field) {
$key = "default_min_{$field}_{$type}";
$eventDefaults[$key] = $allSettings[$key]->value ?? null;
}
}
$settings = $allSettings->filter(fn ($s) =>
!str_starts_with($s->key, 'default_min_') &&
!str_starts_with($s->key, 'visibility_') &&
!str_starts_with($s->key, 'feature_') &&
!str_starts_with($s->key, 'impressum_html_') &&
!str_starts_with($s->key, 'datenschutz_html_') &&
!str_starts_with($s->key, 'password_reset_email_')
);
$fileCategories = FileCategory::ordered()->withCount('files')->get();
// Verfügbare Sprachen und deren locale-spezifische Settings
$availableLocales = ['de', 'en', 'pl', 'ru', 'ar', 'tr'];
$localeSettings = [];
foreach ($availableLocales as $locale) {
$localeSettings[$locale] = [
'impressum_html' => $allSettings["impressum_html_{$locale}"]->value ?? '',
'datenschutz_html' => $allSettings["datenschutz_html_{$locale}"]->value ?? '',
'password_reset_email' => $allSettings["password_reset_email_{$locale}"]->value ?? '',
];
}
$seasons = Season::orderByDesc('start_date')->get();
return view('admin.settings.edit', compact(
'settings', 'eventDefaults', 'fileCategories',
'availableLocales', 'localeSettings', 'seasons'
));
}
public function update(Request $request): RedirectResponse
{
if (!auth()->user()->isAdmin()) {
abort(403, 'Nur Admins koennen Einstellungen aendern.');
}
// Bild-Uploads verarbeiten (vor der normalen Settings-Schleife)
$imageUploads = [
'favicon' => ['setting' => 'app_favicon', 'dir' => 'favicon', 'max' => 512],
'logo_login' => ['setting' => 'app_logo_login', 'dir' => 'logos', 'max' => 1024],
'logo_app' => ['setting' => 'app_logo_app', 'dir' => 'logos', 'max' => 1024],
];
foreach ($imageUploads as $field => $config) {
if ($request->hasFile($field)) {
$request->validate([
$field => 'file|mimes:ico,png,svg,jpg,jpeg,gif,webp|max:' . $config['max'],
]);
$oldFile = Setting::get($config['setting']);
if ($oldFile) {
Storage::disk('public')->delete($oldFile);
}
$file = $request->file($field);
$filename = Str::uuid() . '.' . $file->guessExtension();
$path = $file->storeAs($config['dir'], $filename, 'public');
Setting::set($config['setting'], $path);
} elseif ($request->has("remove_{$field}")) {
$oldFile = Setting::get($config['setting']);
if ($oldFile) {
Storage::disk('public')->delete($oldFile);
}
Setting::set($config['setting'], null);
}
}
$inputSettings = $request->input('settings', []);
// Whitelist: Nur erlaubte Setting-Keys akzeptieren
$allowedLocales = ['de', 'en', 'pl', 'ru', 'ar', 'tr'];
$allowedPrefixes = ['default_min_'];
$allowedLocaleKeys = [];
foreach ($allowedLocales as $loc) {
$allowedLocaleKeys[] = "impressum_html_{$loc}";
$allowedLocaleKeys[] = "datenschutz_html_{$loc}";
$allowedLocaleKeys[] = "password_reset_email_{$loc}";
}
$oldValues = Setting::whereIn('key', array_keys($inputSettings))->pluck('value', 'key')->toArray();
foreach ($inputSettings as $key => $value) {
// Whitelist-Pruefung: Nur bekannte Keys oder erlaubte Prefixe
$isExistingSetting = Setting::where('key', $key)->exists();
$isAllowedLocaleKey = in_array($key, $allowedLocaleKeys);
$isAllowedPrefix = false;
foreach ($allowedPrefixes as $prefix) {
if (str_starts_with($key, $prefix)) {
$isAllowedPrefix = true;
break;
}
}
if (!$isExistingSetting && !$isAllowedLocaleKey && !$isAllowedPrefix) {
continue; // Unbekannten Key ignorieren
}
$setting = Setting::where('key', $key)->first();
if ($setting) {
if ($setting->type === 'html' || $setting->type === 'richtext') {
$value = $this->sanitizer->sanitize($value ?? '');
} elseif ($setting->type === 'number') {
$value = $value !== null && $value !== '' ? (int) $value : null;
} else {
$value = strip_tags($value ?? '');
}
$setting->update(['value' => $value]);
} elseif ($isAllowedLocaleKey) {
// Locale-suffixed legal/email settings: upsert mit HTML-Sanitisierung
$value = $this->sanitizer->sanitize($value ?? '');
$localeSetting = Setting::where('key', $key)->first();
if ($localeSetting) {
$localeSetting->update(['value' => $value]);
} else {
$localeSetting = new Setting(['label' => $key, 'type' => 'html', 'value' => $value]);
$localeSetting->key = $key;
$localeSetting->save();
}
} elseif ($isAllowedPrefix) {
// Event-Defaults: upsert — anlegen wenn nicht vorhanden
$prefixSetting = new Setting([
'label' => $key,
'type' => 'number',
'value' => $value !== null && $value !== '' ? (int) $value : null,
]);
$prefixSetting->key = $key;
$prefixSetting->save();
}
}
Setting::clearCache();
$newValues = Setting::whereIn('key', array_keys($inputSettings))->pluck('value', 'key')->toArray();
ActivityLog::logWithChanges('updated', __('admin.log_settings_updated'), 'Setting', null, $oldValues, $newValues);
return back()->with('success', __('admin.settings_saved'));
}
}