diff --git a/composer.json b/composer.json index 699c356..71a40bd 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "mummert/survey-bundle", - "description": "Modernes Survey-Bundle fuer Contao 5.7 mit Symfony-Frontend.", + "description": "Modernes Survey-Bundle für Contao 5.7 mit Symfony-Frontend.", "type": "contao-bundle", "license": "proprietary", "require": { diff --git a/contao/dca/tl_survey.php b/contao/dca/tl_survey.php index ed40cf3..d99f4d7 100644 --- a/contao/dca/tl_survey.php +++ b/contao/dca/tl_survey.php @@ -22,6 +22,8 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'id' => 'primary', 'alias' => 'unique', 'published' => 'index', + 'isActive' => 'index', + 'isTemplate' => 'index', ], ], ], @@ -33,7 +35,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'panelLayout' => 'filter;sort,search,limit', ], 'label' => [ - 'fields' => ['title', 'categorySummary', 'assignedMembersSummary'], + 'fields' => ['title', 'categorySummary', 'assignedMembersSummary', 'templateSummary'], 'showColumns' => true, ], 'global_operations' => [ @@ -52,10 +54,22 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'href' => 'act=edit', 'icon' => 'header.svg', ], + 'publishSurvey' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['publishSurvey'], + 'href' => 'act=toggle&field=published', + 'icon' => 'visible.svg', + 'button_callback' => [SurveyDcaListener::class, 'generatePublishSurveyButton'], + ], + 'toggleActive' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['toggleActive'], + 'href' => 'act=toggle&field=isActive', + 'icon' => 'visible.svg', + 'button_callback' => [SurveyDcaListener::class, 'generateToggleActiveButton'], + ], 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', - 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich loeschen?\'))return false;Backend.getScrollOffset()"', + 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich löschen?\'))return false;Backend.getScrollOffset()"', ], 'show' => [ 'href' => 'act=show', @@ -74,7 +88,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], ], 'palettes' => [ - 'default' => '{title_legend},title,category,description;{assignment_legend},assignedMembers;{publishing_legend},published,isLocked;{meta_legend},createdAt,updatedAt', + 'default' => '{title_legend},title,category,description;{assignment_legend},assignedMembers;{publishing_legend},isTemplate;{meta_legend},createdAt,updatedAt', ], 'fields' => [ 'id' => [ @@ -105,6 +119,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], 'category' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['category'], + 'filter' => true, 'inputType' => 'select', 'foreignKey' => 'tl_survey_category.title', 'eval' => ['multiple' => true, 'chosen' => true, 'tl_class' => 'clr'], @@ -113,9 +128,8 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ ], 'categoryFilter' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'], - 'filter' => true, 'inputType' => 'select', - 'options_callback' => [SurveyDcaListener::class, 'getCategoryOptions'], + 'options_callback' => [SurveyDcaListener::class, 'getUsedCategoryFilterOptions'], 'eval' => ['includeBlankOption' => true, 'chosen' => true, 'findInSet' => true], 'sql' => "varchar(255) NOT NULL default ''", ], @@ -127,8 +141,33 @@ $GLOBALS['TL_DCA']['tl_survey'] = [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['published'], 'inputType' => 'checkbox', 'eval' => ['tl_class' => 'w50 m12'], + 'save_callback' => [[SurveyDcaListener::class, 'savePublishedState']], 'sql' => "char(1) NOT NULL default ''", ], + 'isActive' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['isActive'], + 'inputType' => 'checkbox', + 'save_callback' => [[SurveyDcaListener::class, 'saveActiveState']], + 'sql' => "char(1) NOT NULL default ''", + ], + 'isTemplate' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['isTemplate'], + 'inputType' => 'checkbox', + 'eval' => ['tl_class' => 'w50 m12'], + 'sql' => "char(1) NOT NULL default ''", + ], + 'templateFilter' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['templateFilter'], + 'filter' => true, + 'inputType' => 'select', + 'options_callback' => [SurveyDcaListener::class, 'getTemplateFilterOptions'], + 'eval' => ['includeBlankOption' => true, 'chosen' => true], + 'sql' => "char(1) NOT NULL default ''", + ], + 'templateSummary' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_survey']['templateSummary'], + 'sql' => "varchar(16) NOT NULL default ''", + ], 'isLocked' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey']['isLocked'], 'inputType' => 'checkbox', @@ -192,7 +231,7 @@ if ('cli' !== PHP_SAPI) { System::getContainer()->get('database_connection')->fetchFirstColumn('SHOW COLUMNS FROM tl_survey'), ); $hasListMetadataColumns = !array_diff( - ['categoryfilter', 'categorysummary', 'assignedmembersfilter', 'assignedmemberssummary'], + ['categoryfilter', 'categorysummary', 'templatefilter', 'templatesummary', 'assignedmembersfilter', 'assignedmemberssummary'], $columnNames, ); } catch (\Throwable) { @@ -205,6 +244,8 @@ if ('cli' !== PHP_SAPI) { unset( $GLOBALS['TL_DCA']['tl_survey']['fields']['categoryFilter'], $GLOBALS['TL_DCA']['tl_survey']['fields']['categorySummary'], + $GLOBALS['TL_DCA']['tl_survey']['fields']['templateFilter'], + $GLOBALS['TL_DCA']['tl_survey']['fields']['templateSummary'], $GLOBALS['TL_DCA']['tl_survey']['fields']['assignedMembersFilter'], $GLOBALS['TL_DCA']['tl_survey']['fields']['assignedMembersSummary'], ); diff --git a/contao/dca/tl_survey_answer.php b/contao/dca/tl_survey_answer.php index 01bd1b9..265f79a 100644 --- a/contao/dca/tl_survey_answer.php +++ b/contao/dca/tl_survey_answer.php @@ -52,6 +52,15 @@ $GLOBALS['TL_DCA']['tl_survey_answer'] = [ 'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['questionType'], 'sql' => "varchar(32) NOT NULL default ''", ], + 'questionLabel' => [ + 'sql' => "varchar(255) NOT NULL default ''", + ], + 'questionDescription' => [ + 'sql' => 'text NULL', + ], + 'questionSorting' => [ + 'sql' => 'int(10) unsigned NOT NULL default 0', + ], 'value' => [ 'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['value'], 'sql' => 'text NULL', diff --git a/contao/dca/tl_survey_category.php b/contao/dca/tl_survey_category.php index 0445ab3..7d4f87a 100644 --- a/contao/dca/tl_survey_category.php +++ b/contao/dca/tl_survey_category.php @@ -34,7 +34,7 @@ $GLOBALS['TL_DCA']['tl_survey_category'] = [ 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', - 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich loeschen?\'))return false;Backend.getScrollOffset()"', + 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich löschen?\'))return false;Backend.getScrollOffset()"', ], 'show' => [ 'href' => 'act=show', diff --git a/contao/dca/tl_survey_condition.php b/contao/dca/tl_survey_condition.php index cfc7fd0..549e539 100644 --- a/contao/dca/tl_survey_condition.php +++ b/contao/dca/tl_survey_condition.php @@ -12,6 +12,9 @@ $GLOBALS['TL_DCA']['tl_survey_condition'] = [ 'dataContainer' => DC_Table::class, 'ptable' => 'tl_survey', 'enableVersioning' => true, + 'onload_callback' => [ + [SurveyDcaListener::class, 'restrictSurveyConditionEditing'], + ], 'onsubmit_callback' => [ [SurveyDcaListener::class, 'touchSurveyConditionParent'], ], @@ -51,7 +54,7 @@ $GLOBALS['TL_DCA']['tl_survey_condition'] = [ 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', - 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich loeschen?\'))return false;Backend.getScrollOffset()"', + 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich löschen?\'))return false;Backend.getScrollOffset()"', ], 'show' => [ 'href' => 'act=show', diff --git a/contao/dca/tl_survey_content.php b/contao/dca/tl_survey_content.php index 7f07827..59e737e 100644 --- a/contao/dca/tl_survey_content.php +++ b/contao/dca/tl_survey_content.php @@ -12,6 +12,9 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [ 'dataContainer' => DC_Table::class, 'ptable' => 'tl_survey', 'enableVersioning' => true, + 'onload_callback' => [ + [SurveyDcaListener::class, 'restrictSurveyContentEditing'], + ], 'onsubmit_callback' => [ [SurveyDcaListener::class, 'touchSurveyContentParent'], ], @@ -55,7 +58,7 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [ 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', - 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich loeschen?\'))return false;Backend.getScrollOffset()"', + 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich löschen?\'))return false;Backend.getScrollOffset()"', ], 'show' => [ 'href' => 'act=show', diff --git a/contao/dca/tl_survey_editor.php b/contao/dca/tl_survey_editor.php index 65d4847..974b01c 100644 --- a/contao/dca/tl_survey_editor.php +++ b/contao/dca/tl_survey_editor.php @@ -47,7 +47,7 @@ $GLOBALS['TL_DCA']['tl_survey_editor'] = [ 'delete' => [ 'href' => 'act=delete', 'icon' => 'delete.svg', - 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich loeschen?\'))return false;Backend.getScrollOffset()"', + 'attributes' => 'onclick="if(!confirm(\'Eintrag wirklich löschen?\'))return false;Backend.getScrollOffset()"', ], 'show' => [ 'href' => 'act=show', diff --git a/contao/languages/de/modules.php b/contao/languages/de/modules.php index 5a4f9ca..c42f870 100644 --- a/contao/languages/de/modules.php +++ b/contao/languages/de/modules.php @@ -1,10 +1,10 @@ {% else %} - {% set locked = survey.isLocked == '1' %}
{{ question.description|striptags|trim }}
{% endif %} +{{ (survey.description|striptags|trim) ?: 'survey.survey.default_description'|trans }}
+ {% if isPreview %} +{{ 'survey.survey.thanks_text'|trans }}
+{{ isPreview ? 'survey.survey.preview_thanks_text'|trans : 'survey.survey.thanks_text'|trans }}
{% if answers %}{{ question.description|striptags|trim }}
{% endif %} + {% if canGoBack %} + + {% endif %} + {{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }} + {% if isPreview %} + + + {% endif %} {% if question.type == 'yes_no_maybe' %}{{ response|striptags|trim|nl2br }}+
{{ response|striptags|trim|nl2br }}+
{{ 'survey.results.no_text_responses'|trans }}
{% endfor %} diff --git a/src/Controller/FrontendModule/MemberSurveyEditController.php b/src/Controller/FrontendModule/MemberSurveyEditController.php index 634eb31..81c76af 100644 --- a/src/Controller/FrontendModule/MemberSurveyEditController.php +++ b/src/Controller/FrontendModule/MemberSurveyEditController.php @@ -98,6 +98,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController $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); @@ -112,70 +113,78 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController 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; - 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), + 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), ]); - $questionForm->handleRequest($request); + $newQuestionForm->handleRequest($request); - if ($questionForm->isSubmitted()) { - $activeQuestionId = $questionId; - $createQuestionMode = false; + if ($newQuestionForm->isSubmitted()) { + $activeQuestionId = 0; + $createQuestionMode = true; - if ($questionForm->isValid()) { + if ($newQuestionForm->isValid()) { try { - $savedQuestionId = $this->surveyEditorService->saveQuestion($survey, $questionForm->getData(), $questionId); - $this->addFlash('success', 'Die Frage wurde aktualisiert.'); + $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', 'Frage konnte nicht gespeichert werden: '.$this->describeFormErrors($questionForm)); + $this->addFlash('error', 'Neue Frage konnte nicht gespeichert werden: '.$this->describeFormErrors($newQuestionForm)); } } - $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 { - $savedQuestionId = $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', $newQuestionForm->createView()); + $template->set('newQuestionForm', $newQuestionFormView); $template->set('questions', $questions); $conditionOverview = $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id); @@ -205,7 +214,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController } if (!$this->surveyEditorRepository->isEditor((int) $survey->id, $memberId)) { - throw new \RuntimeException('Sie duerfen diese Umfrage nicht bearbeiten.'); + throw new \RuntimeException('Sie dürfen diese Umfrage nicht bearbeiten.'); } return $survey; @@ -259,23 +268,28 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController $token = (string) $request->request->get('_token'); if (!$this->isCsrfTokenValid($action.'-'.$itemId, $token)) { - throw new \RuntimeException('Ungueltiger CSRF-Token.'); + throw new \RuntimeException('Ungültiger CSRF-Token.'); } try { - if ('toggle-published' === $action) { - $isPublished = $this->surveyEditorService->toggleSurveyPublished($survey, $memberId); - $this->addFlash('success', $isPublished ? 'Die Umfrage ist jetzt veroeffentlicht.' : 'Die Umfrage ist jetzt nicht mehr veroeffentlicht.'); + 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 geloescht.'); + $this->addFlash('success', 'Die Frage wurde gelöscht.'); } if ('delete-condition' === $action) { $this->surveyEditorService->deleteCondition($survey, $itemId); - $this->addFlash('success', 'Die Bedingung wurde geloescht.'); + $this->addFlash('success', 'Die Bedingung wurde gelöscht.'); } } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); diff --git a/src/Controller/FrontendModule/MemberSurveyListController.php b/src/Controller/FrontendModule/MemberSurveyListController.php index b04706a..69bdcb1 100644 --- a/src/Controller/FrontendModule/MemberSurveyListController.php +++ b/src/Controller/FrontendModule/MemberSurveyListController.php @@ -52,18 +52,21 @@ final class MemberSurveyListController extends AbstractFrontendModuleController foreach ($this->surveyRepository->findEditableByMember((int) $user->id) as $survey) { $surveyId = (int) $survey['id']; + $alias = (string) $survey['alias']; $items[] = [ 'id' => $surveyId, 'title' => (string) $survey['title'], 'description' => (string) ($survey['description'] ?? ''), + 'isActive' => !empty($survey['isActive']), 'isLocked' => !empty($survey['isLocked']), 'published' => !empty($survey['published']), 'questionCount' => (int) ($survey['questionCount'] ?? 0), 'participationCount' => (int) ($survey['participationCount'] ?? 0), 'editUrl' => $editPage instanceof PageModel ? $this->generateContentUrl($editPage, ['survey' => $surveyId]) : null, - 'publicUrl' => $readerPage instanceof PageModel ? $this->generateContentUrl($readerPage, ['auto_item' => (string) $survey['alias']]) : null, - 'resultsUrl' => $resultsPage instanceof PageModel ? $this->generateContentUrl($resultsPage, ['auto_item' => (string) $survey['alias']]) : null, + 'publicUrl' => $readerPage instanceof PageModel ? $this->generateContentUrl($readerPage, ['auto_item' => $alias]) : null, + 'draftUrl' => $readerPage instanceof PageModel ? $this->generateContentUrl($readerPage, ['auto_item' => $alias, 'preview' => '1']) : null, + 'resultsUrl' => $resultsPage instanceof PageModel ? $this->generateContentUrl($resultsPage, ['auto_item' => $alias]) : null, ]; } @@ -81,20 +84,15 @@ final class MemberSurveyListController extends AbstractFrontendModuleController $token = (string) $request->request->get('_token'); if ($surveyId <= 0 || !$this->isCsrfTokenValid($action.'-'.$surveyId, $token)) { - throw new \RuntimeException('Ungueltiger CSRF-Token.'); + throw new \RuntimeException('Ungültiger CSRF-Token.'); } $survey = $this->resolveEditableSurvey($surveyId, $memberId); try { - if ('toggle-published' === $action) { - $isPublished = $this->surveyEditorService->toggleSurveyPublished($survey, $memberId); - $this->addFlash('success', $isPublished ? 'Die Umfrage ist jetzt veroeffentlicht.' : 'Die Umfrage ist jetzt nicht mehr veroeffentlicht.'); - } - if ('delete-survey' === $action) { $this->surveyEditorService->deleteSurvey($survey); - $this->addFlash('success', 'Die Umfrage mit allen Fragen, Antworten und Ergebnissen wurde geloescht.'); + $this->addFlash('success', 'Die Umfrage mit allen Fragen, Antworten und Ergebnissen wurde gelöscht.'); } } catch (\Throwable $exception) { $this->addFlash('error', $exception->getMessage()); @@ -108,7 +106,7 @@ final class MemberSurveyListController extends AbstractFrontendModuleController $survey = $this->surveyRepository->findById($surveyId); if (!$survey instanceof SurveyModel || !$this->surveyEditorRepository->isEditor($surveyId, $memberId)) { - throw new \RuntimeException('Sie duerfen diese Umfrage nicht bearbeiten.'); + throw new \RuntimeException('Sie dürfen diese Umfrage nicht bearbeiten.'); } return $survey; diff --git a/src/Controller/FrontendModule/ShowSurveyController.php b/src/Controller/FrontendModule/ShowSurveyController.php index 35db9c5..6b4e1e4 100644 --- a/src/Controller/FrontendModule/ShowSurveyController.php +++ b/src/Controller/FrontendModule/ShowSurveyController.php @@ -8,6 +8,8 @@ use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule; use Contao\CoreBundle\Twig\FragmentTemplate; use Contao\ModuleModel; +use Mummert\SurveyBundle\Model\SurveyContentModel; +use Mummert\SurveyBundle\Model\SurveyModel; use Mummert\SurveyBundle\Form\Model\SurveyAnswerData; use Mummert\SurveyBundle\Form\SurveyQuestionAnswerType; use Mummert\SurveyBundle\Repository\SurveyRepository; @@ -30,48 +32,57 @@ final class ShowSurveyController extends AbstractFrontendModuleController protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response { $publicAlias = $this->resolvePublicAlias($request); + $previewMode = $request->query->getBoolean('preview'); if ('' === $publicAlias) { - $template->set('errorMessage', 'Es wurde kein oeffentlicher Link auf der Anzeigeseite gefunden.'); - $template->set('question', null); - $template->set('surveyForm', null); - $template->set('isComplete', false); - $template->set('answers', []); - $template->set('survey', null); - $template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]); + $this->applyEmptyState($template, 'Es wurde kein öffentlicher Link auf der Anzeigeseite gefunden.', $previewMode); return $template->getResponse(); } - $survey = $this->surveyRepository->findByPublicAlias($publicAlias, true); + $survey = $this->surveyRepository->findByPublicAlias($publicAlias, !$previewMode); if (null === $survey) { - $template->set('errorMessage', 'Die Umfrage wurde nicht gefunden oder ist nicht veroeffentlicht.'); - $template->set('question', null); - $template->set('surveyForm', null); - $template->set('isComplete', false); - $template->set('answers', []); - $template->set('survey', null); - $template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]); + $this->applyEmptyState($template, 'Die Umfrage wurde nicht gefunden, ist nicht veröffentlicht oder aktuell deaktiviert.', $previewMode); + + return $template->getResponse(); + } + + if ($previewMode && '1' === (string) $survey->published) { + $this->applyEmptyState($template, 'Die Entwurfsansicht ist für bereits veröffentlichte Umfragen nicht verfügbar.', true, $survey); return $template->getResponse(); } if ($htmlHeadBag = $this->getHtmlHeadBag()) { - $htmlHeadBag->setTitle((string) $survey->title); + $htmlHeadBag->setTitle((string) $survey->title.($previewMode ? ' - Entwurfsansicht' : '')); + } + + if ($previewMode) { + return $this->buildPreviewResponse($template, $survey, $request); } $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); + + if ($this->surveySubmissionService->goBack($survey, $submission, $request->getSession())) { + return new RedirectResponse($request->getUri()); + } + } + $question = $this->surveyFlowService->resolveCurrentQuestion($survey, $submission); if (null === $question && '1' !== (string) $submission->isFinished) { - $template->set('errorMessage', 'Diese Umfrage ist noch nicht vollstaendig eingerichtet. Es fehlt eine gueltige erste Frage oder eine verknuepfte Folgefrage ist nicht mehr verfuegbar.'); + $template->set('errorMessage', 'Diese Umfrage ist noch nicht vollständig eingerichtet. Es fehlt eine gültige erste Frage oder eine verknüpfte Folgefrage ist nicht mehr verfügbar.'); $template->set('question', null); $template->set('surveyForm', null); $template->set('isComplete', false); $template->set('answers', []); $template->set('survey', $survey); $template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]); + $template->set('isPreview', false); return $template->getResponse(); } @@ -84,17 +95,28 @@ final class ShowSurveyController extends AbstractFrontendModuleController $template->set('surveyForm', null); $template->set('isComplete', true); $template->set('answers', $this->surveySubmissionService->getAnswersForSubmission($submission)); + $template->set('isPreview', false); + $template->set('canGoBack', false); + $template->set('historyState', ''); return $template->getResponse(); } - $answerData = new SurveyAnswerData(); + $answerData = $this->surveySubmissionService->createAnswerData( + $question, + $this->surveySubmissionService->getStoredAnswerValue($submission, $question), + ); $form = $this->createForm(SurveyQuestionAnswerType::class, $answerData, ['question' => $question]); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $normalizedAnswer = $this->surveySubmissionService->storeAnswer($survey, $submission, $question, $answerData->answer); $nextQuestion = $this->surveyFlowService->determineNextQuestion($survey, $question, $normalizedAnswer); + + if ($nextQuestion instanceof SurveyContentModel) { + $this->surveySubmissionService->rememberQuestion($survey, $request->getSession(), $question); + } + $this->surveySubmissionService->advance($submission, $nextQuestion); return new RedirectResponse($request->getUri()); @@ -107,10 +129,140 @@ final class ShowSurveyController extends AbstractFrontendModuleController $template->set('surveyForm', $form->createView()); $template->set('isComplete', false); $template->set('answers', []); + $template->set('isPreview', false); + $template->set('canGoBack', $this->surveySubmissionService->canGoBack($survey, $request->getSession())); + $template->set('historyState', ''); return $template->getResponse(); } + private function buildPreviewResponse(FragmentTemplate $template, SurveyModel $survey, Request $request): Response + { + $history = $this->decodePreviewHistory((string) $request->request->get('preview_history', '')); + $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); + + if ([] !== $history) { + $question = $this->resolvePreviewQuestion($survey, (int) array_pop($history)); + } + } + + $question ??= $this->surveyFlowService->getFirstQuestion($survey); + + if (!$question instanceof SurveyContentModel) { + $template->set('errorMessage', 'Diese Umfrage ist noch nicht vollständig eingerichtet. Es fehlt eine gültige erste Frage oder eine verknüpfte Folgefrage ist nicht mehr verfügbar.'); + $template->set('question', null); + $template->set('surveyForm', null); + $template->set('isComplete', false); + $template->set('answers', []); + $template->set('survey', $survey); + $template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]); + $template->set('isPreview', true); + $template->set('canGoBack', false); + $template->set('historyState', ''); + + return $template->getResponse(); + } + + $answerData = new SurveyAnswerData(); + $form = $this->createForm(SurveyQuestionAnswerType::class, $answerData, ['question' => $question]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $normalizedAnswer = $this->surveySubmissionService->normalizeAnswerForQuestion($question, $answerData->answer); + $nextQuestion = $this->surveyFlowService->determineNextQuestion($survey, $question, $normalizedAnswer); + + if ($nextQuestion instanceof SurveyContentModel) { + $history[] = (int) $question->id; + $question = $nextQuestion; + $form = $this->createForm(SurveyQuestionAnswerType::class, new SurveyAnswerData(), ['question' => $question]); + } else { + $template->set('errorMessage', null); + $template->set('survey', $survey); + $template->set('progress', $this->surveyFlowService->getProgress($survey, null)); + $template->set('question', null); + $template->set('surveyForm', null); + $template->set('isComplete', true); + $template->set('answers', []); + $template->set('isPreview', true); + $template->set('canGoBack', false); + $template->set('historyState', ''); + + return $template->getResponse(); + } + } + + $template->set('errorMessage', null); + $template->set('survey', $survey); + $template->set('progress', $this->surveyFlowService->getProgress($survey, $question)); + $template->set('question', $question); + $template->set('surveyForm', $form->createView()); + $template->set('isComplete', false); + $template->set('answers', []); + $template->set('isPreview', true); + $template->set('canGoBack', !empty($history)); + $template->set('historyState', $this->encodePreviewHistory($history)); + + return $template->getResponse(); + } + + private function applyEmptyState(FragmentTemplate $template, string $message, bool $previewMode, ?SurveyModel $survey = null): void + { + $template->set('errorMessage', $message); + $template->set('question', null); + $template->set('surveyForm', null); + $template->set('isComplete', false); + $template->set('answers', []); + $template->set('survey', $survey); + $template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]); + $template->set('isPreview', $previewMode); + $template->set('canGoBack', false); + $template->set('historyState', ''); + } + + private function resolvePreviewQuestion(SurveyModel $survey, int $questionId): ?SurveyContentModel + { + if ($questionId <= 0) { + return null; + } + + return $this->surveyFlowService->resolveQuestionById($survey, $questionId); + } + + /** + * @return list