Redirect with a flash message instead of a 500 page on stale survey actions
Actions from outdated tabs (survey deleted meanwhile, assignment revoked, session expired) hit guard clauses that threw RuntimeException and produced an HTTP 500 error page - seen 11x during the customer pretest on 2026-08-27 (duplicate on a deleted survey, edit link to a deleted survey). All frontend guards now add a clear flash message and redirect to a freshly loaded view: - list actions: distinct messages for deleted survey, revoked assignment, expired session, unknown action - editor: resolveSurvey redirects to the survey overview; editor POST with invalid token redirects back to the editor - reader/preview back button: reload the current question instead of failing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,7 +75,12 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
$survey = $this->resolveSurvey($request, $memberId);
|
||||
$survey = $this->resolveSurvey($request, $memberId, $model);
|
||||
|
||||
if ($survey instanceof Response) {
|
||||
return $survey;
|
||||
}
|
||||
|
||||
$createQuestionMode = $this->isCreateQuestionRequested($request);
|
||||
|
||||
if ($survey instanceof SurveyModel && $request->isMethod('POST') && $request->request->has('_survey_action')) {
|
||||
@@ -225,7 +230,12 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
||||
return $template->getResponse();
|
||||
}
|
||||
|
||||
private function resolveSurvey(Request $request, int $memberId): ?SurveyModel
|
||||
/**
|
||||
* Liefert die angeforderte Umfrage, null (kein survey-Parameter, z. B. Anlegen-Modus)
|
||||
* oder einen Redirect zur Übersicht: Aufrufe aus veralteten Tabs oder Lesezeichen
|
||||
* (Umfrage gelöscht, Zuweisung entzogen) dürfen keine 500er-Fehlerseite erzeugen.
|
||||
*/
|
||||
private function resolveSurvey(Request $request, int $memberId, ModuleModel $model): SurveyModel|Response|null
|
||||
{
|
||||
$surveyId = (int) $request->query->get('survey', 0);
|
||||
|
||||
@@ -236,11 +246,15 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
||||
$survey = $this->surveyRepository->findById($surveyId);
|
||||
|
||||
if (!$survey instanceof SurveyModel) {
|
||||
throw new \RuntimeException('Die Umfrage wurde nicht gefunden.');
|
||||
$this->addFlash('error', 'Die Umfrage wurde nicht gefunden – sie wurde möglicherweise inzwischen gelöscht.');
|
||||
|
||||
return new RedirectResponse($this->resolveListUrl($model));
|
||||
}
|
||||
|
||||
if (!$this->surveyEditorRepository->isEditor((int) $survey->id, $memberId)) {
|
||||
throw new \RuntimeException('Sie dürfen diese Umfrage nicht bearbeiten.');
|
||||
$this->addFlash('error', 'Sie sind dieser Umfrage nicht (mehr) zugewiesen.');
|
||||
|
||||
return new RedirectResponse($this->resolveListUrl($model));
|
||||
}
|
||||
|
||||
return $survey;
|
||||
@@ -301,7 +315,9 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
||||
$token = (string) $request->request->get('_token');
|
||||
|
||||
if (!$this->isCsrfTokenValid($action.'-'.$itemId, $token)) {
|
||||
throw new \RuntimeException('Ungültiger CSRF-Token.');
|
||||
$this->addFlash('error', 'Ihre Sitzung war zwischenzeitlich abgelaufen. Bitte versuchen Sie es erneut.');
|
||||
|
||||
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -100,18 +100,39 @@ final class MemberSurveyListController extends AbstractFrontendModuleController
|
||||
$action = (string) $request->request->get('_survey_action');
|
||||
$token = (string) $request->request->get('_token', '');
|
||||
|
||||
// Aktionen aus veralteten Tabs (Umfrage inzwischen gelöscht, Sitzung
|
||||
// abgelaufen) dürfen keine 500er-Fehlerseite erzeugen: stattdessen
|
||||
// Hinweis als Flash-Meldung und zurück zur frisch geladenen Übersicht.
|
||||
$redirect = new RedirectResponse($request->getBaseUrl().$request->getPathInfo());
|
||||
|
||||
if ($surveyId <= 0 || '' === $action) {
|
||||
throw new \RuntimeException('Ungültige Umfrage-Aktion.');
|
||||
$this->addFlash('error', 'Die Aktion konnte nicht zugeordnet werden. Bitte versuchen Sie es erneut.');
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
// Session-basierter Symfony-Token (wie im Editor): zuverlässig für
|
||||
// eingeloggte Mitglieder. Der Contao-REQUEST_TOKEN ist cookie-gebunden
|
||||
// und im Frontend-Fragment-Flow nicht stabil verfügbar.
|
||||
if (!$this->isCsrfTokenValid($action.'-'.$surveyId, $token)) {
|
||||
throw new \RuntimeException('Ungültiger CSRF-Token.');
|
||||
$this->addFlash('error', 'Ihre Sitzung war zwischenzeitlich abgelaufen. Bitte versuchen Sie es erneut.');
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
$survey = $this->resolveEditableSurvey($surveyId, $memberId);
|
||||
$survey = $this->surveyRepository->findById($surveyId);
|
||||
|
||||
if (!$survey instanceof SurveyModel) {
|
||||
$this->addFlash('error', 'Diese Umfrage existiert nicht mehr – sie wurde möglicherweise inzwischen gelöscht. Die Übersicht wurde aktualisiert.');
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
if (!$this->surveyEditorRepository->isEditor($surveyId, $memberId)) {
|
||||
$this->addFlash('error', 'Sie sind dieser Umfrage nicht mehr zugewiesen. Die Übersicht wurde aktualisiert.');
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
try {
|
||||
if ('duplicate-survey' === $action) {
|
||||
@@ -140,17 +161,6 @@ final class MemberSurveyListController extends AbstractFrontendModuleController
|
||||
return new RedirectResponse($request->getBaseUrl().$request->getPathInfo());
|
||||
}
|
||||
|
||||
private function resolveEditableSurvey(int $surveyId, int $memberId): SurveyModel
|
||||
{
|
||||
$survey = $this->surveyRepository->findById($surveyId);
|
||||
|
||||
if (!$survey instanceof SurveyModel || !$this->surveyEditorRepository->isEditor($surveyId, $memberId)) {
|
||||
throw new \RuntimeException('Sie dürfen diese Umfrage nicht bearbeiten.');
|
||||
}
|
||||
|
||||
return $survey;
|
||||
}
|
||||
|
||||
private function resolvePage(int $pageId): ?PageModel
|
||||
{
|
||||
if ($pageId <= 0) {
|
||||
|
||||
@@ -65,7 +65,11 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
||||
$submission = $this->surveySubmissionService->resolveActiveSubmission($survey, $request->getSession(), null);
|
||||
|
||||
if ($request->isMethod('POST') && 'back' === (string) $request->request->get('_survey_navigation')) {
|
||||
$this->assertNavigationTokenValid($request, 'survey-back-'.(int) $survey->id);
|
||||
// Ungültiger Token (Sitzung abgelaufen, veralteter Tab): kein 500er,
|
||||
// sondern die aktuelle Frage per Redirect neu laden.
|
||||
if (!$this->isNavigationTokenValid($request, 'survey-back-'.(int) $survey->id)) {
|
||||
return new RedirectResponse($request->getUri());
|
||||
}
|
||||
|
||||
if ($this->surveySubmissionService->goBack($survey, $submission, $request->getSession())) {
|
||||
return new RedirectResponse($request->getUri());
|
||||
@@ -170,7 +174,11 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
||||
$question = $this->resolvePreviewQuestion($survey, (int) $request->request->get('preview_question', 0));
|
||||
|
||||
if ($request->isMethod('POST') && 'back' === (string) $request->request->get('_survey_navigation')) {
|
||||
$this->assertNavigationTokenValid($request, 'survey-preview-back-'.(int) $survey->id);
|
||||
// Ungültiger Token: Entwurfsansicht ohne Fehlerseite von vorn beginnen
|
||||
// (der Zustand wandert nur durch POST-Felder, ein GET startet neu).
|
||||
if (!$this->isNavigationTokenValid($request, 'survey-preview-back-'.(int) $survey->id)) {
|
||||
return new RedirectResponse($request->getUri());
|
||||
}
|
||||
|
||||
if ([] !== $history) {
|
||||
$question = $this->resolvePreviewQuestion($survey, (int) array_pop($history));
|
||||
@@ -285,13 +293,11 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
||||
return implode(',', $history);
|
||||
}
|
||||
|
||||
private function assertNavigationTokenValid(Request $request, string $tokenId): void
|
||||
private function isNavigationTokenValid(Request $request, string $tokenId): bool
|
||||
{
|
||||
$token = (string) $request->request->get('_navigation_token', '');
|
||||
|
||||
if (!$this->isCsrfTokenValid($tokenId, $token)) {
|
||||
throw new \RuntimeException('Ungültiger CSRF-Token.');
|
||||
}
|
||||
return $this->isCsrfTokenValid($tokenId, $token);
|
||||
}
|
||||
|
||||
private function resolvePublicAlias(Request $request): string
|
||||
|
||||
Reference in New Issue
Block a user