Initial survey bundle import

This commit is contained in:
Jürgen Mummert
2026-05-17 17:31:28 +02:00
commit b8475e3a57
84 changed files with 7157 additions and 0 deletions
@@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
namespace Mummert\SurveyBundle\Controller\FrontendModule;
use Contao\Config;
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Contao\FrontendUser;
use Contao\ModuleModel;
use Contao\PageModel;
use Mummert\SurveyBundle\Form\Model\SurveyConditionData;
use Mummert\SurveyBundle\Form\Model\SurveyEditorData;
use Mummert\SurveyBundle\Form\Model\SurveyQuestionData;
use Mummert\SurveyBundle\Form\SurveyConditionEditorType;
use Mummert\SurveyBundle\Form\SurveyEditorType;
use Mummert\SurveyBundle\Form\SurveyQuestionEditorType;
use Mummert\SurveyBundle\Model\SurveyModel;
use Mummert\SurveyBundle\Repository\SurveyConditionRepository;
use Mummert\SurveyBundle\Repository\SurveyEditorRepository;
use Mummert\SurveyBundle\Repository\SurveyQuestionRepository;
use Mummert\SurveyBundle\Repository\SurveyRepository;
use Mummert\SurveyBundle\Security\SurveyEditorVoter;
use Mummert\SurveyBundle\Service\SurveyEditorService;
use Mummert\SurveyBundle\Service\SurveySubmissionService;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
#[AsFrontendModule(type: 'member_survey_edit', category: 'survey', template: 'frontend/member_survey_edit')]
final class MemberSurveyEditController extends AbstractFrontendModuleController
{
public function __construct(
private readonly SurveyRepository $surveyRepository,
private readonly SurveyQuestionRepository $surveyQuestionRepository,
private readonly SurveyConditionRepository $surveyConditionRepository,
private readonly SurveyEditorRepository $surveyEditorRepository,
private readonly SurveyEditorService $surveyEditorService,
private readonly SurveySubmissionService $surveySubmissionService,
) {
}
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
{
$user = $this->getUser();
if (!$user instanceof FrontendUser) {
$template->set('loginRequired', true);
$template->set('backUrl', $this->resolveListUrl($model));
return $template->getResponse();
}
$survey = $this->resolveSurvey($request, $user);
if ($survey instanceof SurveyModel && $request->isMethod('POST') && $request->request->has('_survey_action')) {
return $this->handleAction($survey, $request, $model);
}
$questionChoices = $survey instanceof SurveyModel ? $this->buildQuestionChoices((int) $survey->id) : [];
$surveyForm = $this->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, (int) $user->id, $surveyForm->getData());
$this->addFlash('success', 'Die Umfrage wurde gespeichert.');
return new RedirectResponse($this->buildEditUrl($model, (int) $survey->id));
}
$createdSurvey = $this->surveyEditorService->createSurvey((int) $user->id, $surveyForm->getData());
$this->addFlash('success', 'Die Umfrage wurde angelegt.');
return new RedirectResponse($this->buildEditUrl($model, (int) $createdSurvey->id));
} catch (\Throwable $exception) {
$this->addFlash('error', $exception->getMessage());
}
}
if (!$survey instanceof SurveyModel) {
$template->set('loginRequired', false);
$template->set('createMode', true);
$template->set('surveyForm', $surveyForm->createView());
$template->set('backUrl', $this->resolveListUrl($model));
$template->set('publicUrl', null);
$template->set('survey', null);
$template->set('questionForm', null);
$template->set('conditionForm', null);
$template->set('questions', []);
$template->set('conditions', []);
$template->set('editors', []);
$template->set('submissions', []);
$template->set('activeQuestionId', 0);
$template->set('activeConditionId', 0);
return $template->getResponse();
}
$activeQuestion = $this->resolveActiveQuestion($request, (int) $survey->id);
$activeCondition = $this->resolveActiveCondition($request, (int) $survey->id);
$questionForm = $this->createNamed('survey_question', SurveyQuestionEditorType::class, $activeQuestion ? SurveyQuestionData::fromModel($activeQuestion) : new SurveyQuestionData(), [
'question_choices' => $this->buildQuestionChoices((int) $survey->id),
]);
$conditionForm = $this->createNamed('survey_condition', SurveyConditionEditorType::class, $activeCondition ? SurveyConditionData::fromModel($activeCondition) : new SurveyConditionData(), [
'question_choices' => $questionChoices,
]);
$questionForm->handleRequest($request);
$conditionForm->handleRequest($request);
if ($questionForm->isSubmitted() && $questionForm->isValid()) {
try {
$this->surveyEditorService->saveQuestion($survey, $questionForm->getData(), $activeQuestion ? (int) $activeQuestion->id : 0);
$this->addFlash('success', $activeQuestion ? 'Die Frage wurde aktualisiert.' : 'Die Frage wurde angelegt.');
return new RedirectResponse($this->buildEditUrl($model, (int) $survey->id));
} catch (\Throwable $exception) {
$this->addFlash('error', $exception->getMessage());
}
}
if ($conditionForm->isSubmitted() && $conditionForm->isValid()) {
try {
$this->surveyEditorService->saveCondition($survey, $conditionForm->getData(), $activeCondition ? (int) $activeCondition->id : 0);
$this->addFlash('success', $activeCondition ? 'Die Bedingung wurde aktualisiert.' : 'Die Bedingung wurde angelegt.');
return new RedirectResponse($this->buildEditUrl($model, (int) $survey->id));
} catch (\Throwable $exception) {
$this->addFlash('error', $exception->getMessage());
}
}
$template->set('loginRequired', false);
$template->set('createMode', false);
$template->set('survey', $survey);
$template->set('surveyForm', $surveyForm->createView());
$template->set('questionForm', $questionForm->createView());
$template->set('conditionForm', $conditionForm->createView());
$template->set('questions', $this->surveyQuestionRepository->findAllBySurvey((int) $survey->id));
$template->set('conditions', $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id));
$template->set('editors', $this->surveyEditorRepository->findEditorsBySurvey((int) $survey->id));
$template->set('submissions', $this->surveySubmissionService->getSubmissionOverview($survey));
$template->set('activeQuestionId', $activeQuestion ? (int) $activeQuestion->id : 0);
$template->set('activeConditionId', $activeCondition ? (int) $activeCondition->id : 0);
$template->set('backUrl', $this->resolveListUrl($model));
$template->set('publicUrl', $this->resolveReaderUrl($model, (string) $survey->alias));
return $template->getResponse();
}
private function resolveSurvey(Request $request, FrontendUser $user): ?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.');
}
$this->denyAccessUnlessGranted(SurveyEditorVoter::EDIT, $survey);
return $survey;
}
private function handleAction(SurveyModel $survey, Request $request, ModuleModel $model): 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('Ungueltiger CSRF-Token.');
}
try {
if ('delete-question' === $action) {
$this->surveyEditorService->deleteQuestion($survey, $itemId);
$this->addFlash('success', 'Die Frage wurde geloescht.');
}
if ('delete-condition' === $action) {
$this->surveyEditorService->deleteCondition($survey, $itemId);
$this->addFlash('success', 'Die Bedingung wurde geloescht.');
}
} catch (\Throwable $exception) {
$this->addFlash('error', $exception->getMessage());
}
return new RedirectResponse($this->buildEditUrl($model, (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 resolveActiveCondition(Request $request, int $surveyId): mixed
{
$conditionId = (int) $request->query->get('condition', 0);
$condition = $conditionId > 0 ? $this->surveyConditionRepository->findById($conditionId) : null;
if (null === $condition || (int) $condition->pid !== $surveyId) {
return null;
}
return $condition;
}
/**
* @return array<int, string>
*/
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;
}
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, int $surveyId): string
{
$page = $this->getPageModel();
return $page instanceof PageModel ? $this->generateContentUrl($page, ['survey' => $surveyId]) : $this->resolveListUrl($model);
}
private function resolvePage(int $pageId): ?PageModel
{
if ($pageId <= 0) {
return null;
}
$page = $this->getContaoAdapter(PageModel::class)->findById($pageId);
return $page instanceof PageModel ? $page : null;
}
}