user()->canViewActivityLog()) { abort(403); } $request->validate([ 'category' => ['nullable', 'string', 'in:auth,users,players,events,files,settings,dsgvo'], 'from' => ['nullable', 'date'], 'to' => ['nullable', 'date'], ]); $query = ActivityLog::with('user')->latest('created_at'); // Filter: Kategorie if ($request->filled('category')) { $actionMap = [ 'auth' => ['login', 'logout', 'login_failed', 'registered'], 'users' => ['updated', 'toggled_active', 'role_changed', 'password_reset', 'deleted', 'restored'], 'players' => ['created', 'updated', 'deleted', 'restored', 'parent_assigned', 'parent_removed'], 'events' => ['created', 'updated', 'deleted', 'participant_status_changed'], 'files' => ['uploaded', 'deleted'], 'settings' => ['updated'], 'dsgvo' => ['dsgvo_consent_uploaded', 'dsgvo_consent_confirmed', 'dsgvo_consent_revoked', 'dsgvo_consent_removed', 'dsgvo_consent_rejected', 'account_self_deleted', 'child_auto_deactivated'], ]; $category = $request->input('category'); if (isset($actionMap[$category])) { if ($category === 'auth' || $category === 'dsgvo') { $query->whereIn('action', $actionMap[$category]); } else { $modelTypeMap = [ 'users' => 'User', 'players' => 'Player', 'events' => 'Event', 'files' => 'File', 'settings' => 'Setting', ]; $query->where('model_type', $modelTypeMap[$category] ?? null); } } } // Filter: Datum von if ($request->filled('from')) { $query->whereDate('created_at', '>=', $request->input('from')); } // Filter: Datum bis if ($request->filled('to')) { $query->whereDate('created_at', '<=', $request->input('to')); } $logs = $query->paginate(30)->withQueryString(); return view('admin.activity-logs.index', compact('logs')); } public function revert(ActivityLog $log): RedirectResponse { if (!auth()->user()->canViewActivityLog()) { abort(403); } $old = $log->properties['old'] ?? []; $revertableActions = ['deleted', 'toggled_active', 'role_changed', 'status_changed', 'participant_status_changed']; if (!in_array($log->action, $revertableActions) || !$log->model_id) { return back()->with('error', __('admin.log_revert_not_possible')); } $success = match ($log->action) { 'deleted' => $this->revertDelete($log), 'toggled_active' => $this->revertToggleActive($log, $old), 'role_changed' => $this->revertRoleChange($log, $old), 'status_changed' => $this->revertStatusChange($log, $old), 'participant_status_changed' => $this->revertParticipantStatus($log, $old), default => false, }; if (!$success) { return back()->with('error', __('admin.log_revert_not_possible')); } ActivityLog::log('reverted', __('admin.log_reverted', ['desc' => Str::limit($log->description, 80)]), $log->model_type, $log->model_id); return back()->with('success', __('admin.log_revert_success')); } private function revertDelete(ActivityLog $log): bool { return match ($log->model_type) { 'User' => $this->restoreModel(User::class, $log->model_id), 'Player' => $this->restoreModel(Player::class, $log->model_id), 'Event' => $this->restoreEvent($log->model_id), 'Comment' => $this->restoreComment($log->model_id), default => false, }; } private function restoreModel(string $class, int $id): bool { $model = $class::onlyTrashed()->find($id); if (!$model) { return false; } $model->restore(); return true; } private function restoreEvent(int $id): bool { $event = Event::onlyTrashed()->find($id); if (!$event) { return false; } $event->deleted_by = null; $event->save(); $event->restore(); return true; } private function restoreComment(int $id): bool { $comment = Comment::find($id); if (!$comment || !$comment->isDeleted()) { return false; } $comment->deleted_at = null; $comment->deleted_by = null; $comment->save(); return true; } private function revertToggleActive(ActivityLog $log, array $old): bool { $model = match ($log->model_type) { 'User' => User::withTrashed()->find($log->model_id), 'Player' => Player::withTrashed()->find($log->model_id), default => null, }; if (!$model || !isset($old['is_active'])) { return false; } $model->is_active = filter_var($old['is_active'], FILTER_VALIDATE_BOOLEAN); $model->save(); return true; } private function revertRoleChange(ActivityLog $log, array $old): bool { if ($log->model_type !== 'User' || !isset($old['role'])) { return false; } // Enum-Validierung: Nur gültige Rollen-Werte akzeptieren (V02) $validRoles = array_column(UserRole::cases(), 'value'); if (!in_array($old['role'], $validRoles, true)) { return false; } $user = User::withTrashed()->find($log->model_id); if (!$user) { return false; } // Selbst-Rollen-Änderung verhindern if ($user->id === auth()->id()) { return false; } // Nicht-Admins dürfen keine Admin-Rolle zuweisen oder Admin-Rollen rückgängig machen if (!auth()->user()->isAdmin() && ($user->isAdmin() || $old['role'] === 'admin')) { return false; } $user->role = $old['role']; $user->save(); return true; } private function revertStatusChange(ActivityLog $log, array $old): bool { if (!isset($old['status']) || $log->model_type !== 'Event') { return false; } $userId = $log->properties['old']['user_id'] ?? $log->properties['new']['user_id'] ?? null; if (!$userId) { return false; } $catering = EventCatering::where('event_id', $log->model_id)->where('user_id', $userId)->first(); if ($catering) { $catering->update(['status' => $old['status']]); return true; } $timekeeper = EventTimekeeper::where('event_id', $log->model_id)->where('user_id', $userId)->first(); if ($timekeeper) { $timekeeper->update(['status' => $old['status']]); return true; } return false; } private function revertParticipantStatus(ActivityLog $log, array $old): bool { if (!isset($old['status']) || $log->model_type !== 'Event') { return false; } // Spieler-ID bevorzugen, Namens-Suche als Fallback (DB-agnostisch) $participant = EventParticipant::where('event_id', $log->model_id) ->when( isset($old['participant_id']), fn ($q) => $q->where('id', $old['participant_id']), fn ($q) => $q->when( isset($old['player']), fn ($q2) => $q2->whereHas('player', function ($pq) use ($old) { $parts = explode(' ', $old['player'], 2); if (count($parts) === 2) { $pq->where('first_name', $parts[0])->where('last_name', $parts[1]); } }) ) ) ->first(); if (!$participant) { return false; } $participant->status = $old['status']; $participant->set_by_user_id = auth()->id(); $participant->responded_at = now(); $participant->save(); return true; } }