resolveEditorMemberId($request); if ($memberId <= 0) { $template->set('loginRequired', true); $template->set('entryNotAllowed', false); $template->set('backUrl', $this->resolveListUrl($model)); $template->set('createQuestionMode', false); return $template->getResponse(); } if (!$this->isEditorEntryAllowed($request)) { $template->set('loginRequired', false); $template->set('entryNotAllowed', true); $template->set('createMode', false); $template->set('survey', null); $template->set('backUrl', $this->resolveListUrl($model)); $template->set('createQuestionMode', false); return $template->getResponse(); } $survey = $this->resolveSurvey($request, $memberId); $createQuestionMode = $this->isCreateQuestionRequested($request); if ($survey instanceof SurveyModel && $request->isMethod('POST') && $request->request->has('_survey_action')) { return $this->handleAction($survey, $request, $model, $memberId); } $surveyForm = $this->formFactory->createNamed('survey_meta', SurveyEditorType::class, $survey instanceof SurveyModel ? SurveyEditorData::fromModel($survey) : new SurveyEditorData()); $surveyForm->handleRequest($request); if ($surveyForm->isSubmitted() && $surveyForm->isValid()) { try { if ($survey instanceof SurveyModel) { $this->surveyEditorService->updateSurvey($survey, $memberId, $surveyForm->getData()); $this->addFlash('success', 'Die Umfrage wurde gespeichert.'); return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id)); } $createdSurvey = $this->surveyEditorService->createSurvey($memberId, $surveyForm->getData()); $this->addFlash('success', 'Die Umfrage wurde angelegt.'); return new RedirectResponse($this->buildEditUrl($model, $request, (int) $createdSurvey->id)); } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); } } if (!$survey instanceof SurveyModel) { $template->set('loginRequired', false); $template->set('entryNotAllowed', false); $template->set('createMode', true); $template->set('structureLocked', false); $template->set('surveyForm', $surveyForm->createView()); $template->set('backUrl', $this->resolveListUrl($model)); $template->set('publicUrl', null); $template->set('survey', null); $template->set('questions', []); $template->set('conditions', []); $template->set('editors', []); $template->set('submissions', []); $template->set('activeQuestionId', 0); $template->set('createQuestionMode', false); return $template->getResponse(); } $structureLocked = $this->surveyEditorService->isStructureLocked($survey); $questions = $this->surveyQuestionRepository->findAllBySurvey((int) $survey->id); $activeQuestion = $this->resolveActiveQuestion($request, (int) $survey->id); $activeQuestionId = $activeQuestion ? (int) $activeQuestion->id : 0; $questionFormViews = []; $newQuestionFormView = null; if (!$structureLocked) { foreach ($questions as $question) { $questionId = (int) $question->id; $questionForm = $this->formFactory->createNamed('survey_question_'.$questionId, SurveyQuestionEditorType::class, SurveyQuestionData::fromModel($question), [ 'question_choices' => $this->buildQuestionChoices((int) $survey->id, $questionId), ]); $questionForm->handleRequest($request); if ($questionForm->isSubmitted()) { $activeQuestionId = $questionId; $createQuestionMode = false; if ($questionForm->isValid()) { try { $this->surveyEditorService->saveQuestion($survey, $questionForm->getData(), $questionId); $this->addFlash('success', 'Die Frage wurde aktualisiert.'); return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id)); } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); } } else { $this->addFlash('error', 'Frage konnte nicht gespeichert werden: '.$this->describeFormErrors($questionForm)); } } $questionFormViews[$questionId] = $questionForm->createView(); } $newQuestionForm = $this->formFactory->createNamed('survey_question_new', SurveyQuestionEditorType::class, new SurveyQuestionData(), [ 'question_choices' => $this->buildQuestionChoices((int) $survey->id), ]); $newQuestionForm->handleRequest($request); if ($newQuestionForm->isSubmitted()) { $activeQuestionId = 0; $createQuestionMode = true; if ($newQuestionForm->isValid()) { try { $this->surveyEditorService->saveQuestion($survey, $newQuestionForm->getData(), 0); $this->addFlash('success', 'Die Frage wurde angelegt.'); return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id)); } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); } } else { $this->addFlash('error', 'Neue Frage konnte nicht gespeichert werden: '.$this->describeFormErrors($newQuestionForm)); } } $newQuestionFormView = $newQuestionForm->createView(); } $template->set('loginRequired', false); $template->set('entryNotAllowed', false); $template->set('createMode', false); $template->set('structureLocked', $structureLocked); $template->set('createQuestionMode', $createQuestionMode); $template->set('survey', $survey); $template->set('surveyForm', $surveyForm->createView()); $template->set('questionForms', $questionFormViews); $template->set('newQuestionForm', $newQuestionFormView); $template->set('questions', $questions); $conditionOverview = $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id); $template->set('conditions', $this->buildJumpRuleOverview($questions, $conditionOverview)); $template->set('editors', $this->surveyEditorRepository->findEditorsBySurvey((int) $survey->id)); $template->set('submissions', $this->surveySubmissionService->getSubmissionOverview($survey)); $template->set('activeQuestionId', $activeQuestionId); $template->set('backUrl', $this->resolveListUrl($model)); $template->set('publicUrl', $this->resolveReaderUrl($model, (string) $survey->alias)); $template->set('metadataFormActionUrl', $this->buildEditUrl($model, $request, (int) $survey->id)); return $template->getResponse(); } private function resolveSurvey(Request $request, int $memberId): ?SurveyModel { $surveyId = (int) $request->query->get('survey', 0); if ($surveyId <= 0) { return null; } $survey = $this->surveyRepository->findById($surveyId); if (!$survey instanceof SurveyModel) { throw new \RuntimeException('Die Umfrage wurde nicht gefunden.'); } if (!$this->surveyEditorRepository->isEditor((int) $survey->id, $memberId)) { throw new \RuntimeException('Sie dürfen diese Umfrage nicht bearbeiten.'); } return $survey; } private function resolveEditorMemberId(Request $request): int { $user = $this->getUser(); if ($user instanceof FrontendUser) { return (int) $user->id; } return $this->isDebugAccessAllowed($request) ? $this->resolveDebugMemberId($request) : 0; } private function resolveDebugMemberId(Request $request): int { $memberId = (int) $request->query->get('debugMember', 0); if ($memberId > 0) { return $memberId; } $surveyId = (int) $request->query->get('survey', 0); if ($surveyId <= 0) { return 0; } $memberIds = $this->surveyEditorRepository->findMemberIdsBySurvey($surveyId); return $memberIds[0] ?? 0; } private function isDebugAccessAllowed(Request $request): bool { $host = strtolower((string) ($request->server->get('HTTP_HOST') ?: $request->getHost())); return ( 'localhost' === $host || '127.0.0.1' === $host || str_ends_with($host, '.ddev.site') ) && $request->query->getBoolean('debugAccess'); } private function handleAction(SurveyModel $survey, Request $request, ModuleModel $model, int $memberId): Response { $action = (string) $request->request->get('_survey_action'); $itemId = (int) $request->request->get('item_id', 0); $token = (string) $request->request->get('_token'); if (!$this->isCsrfTokenValid($action.'-'.$itemId, $token)) { throw new \RuntimeException('Ungültiger CSRF-Token.'); } try { if ('publish-survey' === $action) { $this->surveyEditorService->publishSurvey($survey, $memberId); $this->addFlash('success', 'Die Umfrage ist jetzt veröffentlicht.'); } if ('toggle-active' === $action) { $isActive = $this->surveyEditorService->toggleSurveyActive($survey, $memberId); $this->addFlash('success', $isActive ? 'Die Umfrage ist jetzt wieder aktiv.' : 'Die Umfrage wurde vorübergehend deaktiviert.'); } if ('delete-question' === $action) { $this->surveyEditorService->deleteQuestion($survey, $itemId); $this->addFlash('success', 'Die Frage wurde gelöscht.'); } if ('delete-condition' === $action) { $this->surveyEditorService->deleteCondition($survey, $itemId); $this->addFlash('success', 'Die Bedingung wurde gelöscht.'); } } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); } return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id)); } private function resolveActiveQuestion(Request $request, int $surveyId): mixed { $questionId = (int) $request->query->get('question', 0); return $questionId > 0 ? $this->surveyQuestionRepository->findByIdForSurvey($surveyId, $questionId) : null; } private function isCreateQuestionRequested(Request $request): bool { return 'new' === trim((string) $request->query->get('question', '')); } private function isEditorEntryAllowed(Request $request): bool { if ((int) $request->query->get('survey', 0) > 0) { return true; } return $request->query->getBoolean('create'); } /** * @return array */ private function buildQuestionChoices(int $surveyId, int $excludeQuestionId = 0): array { $choices = []; foreach ($this->surveyQuestionRepository->findAllBySurvey($surveyId) as $question) { if ($excludeQuestionId > 0 && (int) $question->id === $excludeQuestionId) { continue; } $choices[(int) $question->id] = sprintf('#%d %s', (int) $question->id, (string) $question->question); } return $choices; } /** * @param array $questions * @param list> $conditionOverview * * @return list> */ private function buildJumpRuleOverview(array $questions, array $conditionOverview): array { $questionLabels = []; $questionSortings = []; $rules = []; foreach ($questions as $question) { $questionId = (int) ($question->id ?? 0); if ($questionId <= 0) { continue; } $questionLabels[$questionId] = (string) ($question->question ?? ''); $questionSortings[$questionId] = (int) ($question->sorting ?? 0); } foreach ($questions as $question) { $questionId = (int) ($question->id ?? 0); if ($questionId <= 0) { continue; } if ('yes_no_maybe' !== (string) ($question->type ?? '')) { continue; } foreach (['Ja' => (int) ($question->jumpOnYes ?? 0), 'Nein' => (int) ($question->jumpOnNo ?? 0)] as $answerValue => $targetQuestionId) { if ($targetQuestionId <= 0 || !isset($questionLabels[$targetQuestionId])) { continue; } $rules[] = [ 'id' => 'question-'.$questionId.'-'.$targetQuestionId.'-'.$answerValue, 'answerValue' => $answerValue, 'sourceQuestion' => $questionId, 'targetQuestion' => $targetQuestionId, 'sourceQuestionLabel' => (string) ($question->question ?? ''), 'targetQuestionLabel' => $questionLabels[$targetQuestionId] ?? '', 'sourceSorting' => $questionSortings[$questionId] ?? 0, ]; } } foreach ($conditionOverview as $condition) { $sourceQuestionId = (int) ($condition['sourceQuestion'] ?? 0); $rules[] = [ 'id' => $condition['id'] ?? 0, 'answerValue' => (string) ($condition['answerValue'] ?? ''), 'sourceQuestion' => $sourceQuestionId, 'targetQuestion' => (int) ($condition['targetQuestion'] ?? 0), 'sourceQuestionLabel' => (string) ($condition['sourceQuestionLabel'] ?? ''), 'targetQuestionLabel' => (string) ($condition['targetQuestionLabel'] ?? ''), 'sourceSorting' => $questionSortings[$sourceQuestionId] ?? 0, ]; } usort($rules, static function (array $left, array $right): int { $sortingComparison = ((int) ($left['sourceSorting'] ?? 0)) <=> ((int) ($right['sourceSorting'] ?? 0)); if (0 !== $sortingComparison) { return $sortingComparison; } return ((int) ($left['targetQuestion'] ?? 0)) <=> ((int) ($right['targetQuestion'] ?? 0)); }); return $rules; } private function resolveListUrl(ModuleModel $model): string { $page = $this->resolvePage((int) ($model->surveyListPage ?? 0)) ?? $this->getPageModel(); return $page instanceof PageModel ? $this->generateContentUrl($page) : '/'; } private function resolveReaderUrl(ModuleModel $model, string $publicAlias): ?string { $page = $this->resolvePage((int) $this->getContaoAdapter(Config::class)->get('surveyReaderPage')); if (!$page instanceof PageModel) { return null; } return $this->generateContentUrl($page, ['auto_item' => $publicAlias]); } private function buildEditUrl(ModuleModel $model, Request $request, int $surveyId): string { $parameters = ['survey' => $surveyId]; if ($request->query->getBoolean('debugAccess') && $this->isDebugAccessAllowed($request)) { $parameters['debugAccess'] = '1'; } $debugMemberId = (int) $request->query->get('debugMember', 0); if ($debugMemberId > 0) { $parameters['debugMember'] = (string) $debugMemberId; } $basePath = $request->getBaseUrl().$request->getPathInfo(); if ('' !== $basePath) { $queryString = http_build_query($parameters); $url = $basePath.('' !== $queryString ? '?'.$queryString : ''); } else { $page = $this->getPageModel(); $url = $page instanceof PageModel ? $this->generateContentUrl($page, $parameters) : $this->resolveListUrl($model); } return $url; } private function describeFormErrors(FormInterface $form): string { $messages = []; foreach ($form->getErrors(true, true) as $error) { $origin = $error->getOrigin(); $name = $origin instanceof FormInterface ? $origin->getName() : 'form'; $messages[] = sprintf('%s: %s', $name, $error->getMessage()); } return [] !== $messages ? implode(' | ', array_unique($messages)) : 'Unbekannter Formularfehler.'; } private function resolvePage(int $pageId): ?PageModel { if ($pageId <= 0) { return null; } $page = $this->getContaoAdapter(PageModel::class)->findById($pageId); return $page instanceof PageModel ? $page : null; } }