buildSessionKey((int) $survey->id); $token = (string) $session->get($sessionKey, ''); if ('' !== $token) { $existing = $this->surveySubmissionRepository->findBySurveyAndToken((int) $survey->id, $token); // Offene Durchläufe werden fortgesetzt; abgeschlossene Durchläufe halten // die Danke-Seite, bis der Cooldown abgelaufen ist (dann neuer Durchlauf). if ($existing instanceof SurveySubmissionModel && !$this->isRestartAllowed($existing)) { return $existing; } } $token = (string) new Ulid(); $submission = $this->surveySubmissionRepository->create((int) $survey->id, $token, $firstQuestion?->id ? (int) $firstQuestion->id : null); $session->set($sessionKey, $token); $session->set($this->buildHistoryKey((int) $survey->id), []); return $submission; } private function isRestartAllowed(SurveySubmissionModel $submission): bool { if ('1' !== (string) $submission->isFinished) { return false; } $completedAt = (int) ($submission->completedAt ?? 0); if ($completedAt <= 0) { return true; } return (time() - $completedAt) >= self::RESTART_COOLDOWN_SECONDS; } public function storeAnswer(SurveyModel $survey, SurveySubmissionModel $submission, SurveyContentModel $question, mixed $rawAnswer): string { $normalizedAnswer = $this->normalizeAnswerForQuestion($question, $rawAnswer); $surveyHadAnswers = $this->surveyAnswerRepository->hasAnswersForSurvey((int) $survey->id); $sectionMap = $this->surveyStructureService->buildSectionMap($this->surveyQuestionRepository->findAllBySurvey((int) $survey->id)); $this->surveyAnswerRepository->saveAnswer( (int) $submission->id, (int) $question->id, (string) $question->type, trim((string) $question->question), trim((string) ($question->description ?? '')), (int) ($question->sorting ?? 0), $normalizedAnswer, trim((string) ($sectionMap[(int) $question->id]['title'] ?? '')), ); if (!$surveyHadAnswers && '1' !== (string) $survey->isLocked) { $this->surveyRepository->lock((int) $survey->id); } return $normalizedAnswer; } public function normalizeAnswerForQuestion(SurveyContentModel $question, mixed $rawAnswer): string { return $this->questionTypeRegistry->get((string) $question->type)->normalizeAnswer($rawAnswer, $question); } public function createAnswerData(SurveyContentModel $question, mixed $storedValue = null): SurveyAnswerData { $answerData = new SurveyAnswerData(); if (null === $storedValue || '' === trim((string) $storedValue)) { return $answerData; } $normalizedValue = (string) $storedValue; $answerData->answer = match ((string) $question->type) { 'choice' => '1' === (string) $question->allowMultiple ? array_values(array_filter(array_map('trim', explode(' | ', $normalizedValue)), static fn (string $entry): bool => '' !== $entry)) : $normalizedValue, 'range' => (int) $normalizedValue, default => $normalizedValue, }; return $answerData; } public function getStoredAnswerValue(SurveySubmissionModel $submission, SurveyContentModel $question): ?string { return $this->surveyAnswerRepository->findStoredAnswerValue((int) $submission->id, (int) $question->id); } public function rememberQuestion(SurveyModel $survey, SessionInterface $session, SurveyContentModel $question): void { $history = $this->getHistory($survey, $session); $questionId = (int) $question->id; if (($history[array_key_last($history)] ?? null) !== $questionId) { $history[] = $questionId; } $session->set($this->buildHistoryKey((int) $survey->id), $history); } public function canGoBack(SurveyModel $survey, SessionInterface $session): bool { return [] !== $this->getHistory($survey, $session); } public function goBack(SurveyModel $survey, SurveySubmissionModel $submission, SessionInterface $session): bool { $history = $this->getHistory($survey, $session); if ([] === $history) { return false; } $previousQuestionId = (int) array_pop($history); $this->surveySubmissionRepository->reopen((int) $submission->id, $previousQuestionId); $this->surveyAnswerRepository->deleteAnswersForSubmissionExceptQuestions( (int) $submission->id, array_merge($history, [$previousQuestionId]), ); $session->set($this->buildHistoryKey((int) $survey->id), $history); return true; } public function advance(SurveySubmissionModel $submission, ?SurveyContentModel $nextQuestion): void { if ($nextQuestion instanceof SurveyContentModel) { $this->surveySubmissionRepository->updateCurrentQuestion((int) $submission->id, (int) $nextQuestion->id); return; } $this->surveySubmissionRepository->markFinished((int) $submission->id); } /** * @return list> */ public function getAnswersForSubmission(SurveySubmissionModel $submission): array { $answers = $this->surveyAnswerRepository->findAnswersBySubmission((int) $submission->id); return array_map(fn (array $answer): array => $this->formatSubmissionAnswer($answer), $answers); } /** * @return list> */ public function getSubmissionOverview(SurveyModel $survey): array { return $this->surveySubmissionRepository->findOverviewBySurvey((int) $survey->id); } private function buildSessionKey(int $surveyId): string { return 'mummert_survey_submission_'.$surveyId; } private function buildHistoryKey(int $surveyId): string { return 'mummert_survey_history_'.$surveyId; } /** * @return list */ private function getHistory(SurveyModel $survey, SessionInterface $session): array { $history = $session->get($this->buildHistoryKey((int) $survey->id), []); if (!\is_array($history)) { return []; } return array_values(array_filter(array_map('intval', $history), static fn (int $questionId): bool => $questionId > 0)); } /** * @param array $answer * * @return array */ private function formatSubmissionAnswer(array $answer): array { $questionType = (string) ($answer['questionType'] ?? 'text'); $value = trim((string) ($answer['value'] ?? '')); $answer['questionType'] = $this->translateQuestionType($questionType); $answer['value'] = $this->translateAnswerValue($questionType, $value); return $answer; } private function translateQuestionType(string $questionType): string { return $this->translator->trans('survey.survey.question_type_'.$questionType, [], 'messages'); } private function translateAnswerValue(string $questionType, string $value): string { if ('' === $value) { return $value; } if ('yes_no_maybe' === $questionType) { return $this->translator->trans('survey.survey.answer_'.$value, [], 'messages'); } if ('choice' === $questionType) { $parts = array_values(array_filter(array_map('trim', explode(' | ', $value)), static fn (string $entry): bool => '' !== $entry)); return implode(' | ', $parts); } return $value; } }