db7094fd1e
CSRF / actions: - List action forms now send both REQUEST_TOKEN (Contao gate) and _token (Symfony, validated via isCsrfTokenValid) — mirrors the editor; fixes the 400/500 "Ungültiger CSRF-Token" on publish/toggle/duplicate/delete Public reader / URLs: - Reader & results read auto_item via Input::get (marks the route param used) so clean path URLs /<page>/<alias> no longer 404 - List controller generates path URLs via ['parameters' => '/'.$alias] - Allow re-participation in the same session after a 60s cooldown Review fixes: - Results: merge answer snapshots by questionId+type (no more split cards on label edits); seed "Vielleicht" when allowMaybe so screen matches Excel - Reader: replace generic answer hint with per-option "weiter zu Frage X" only when a jump is configured; strip HTML tags from survey description - PDF: render free-text frequency summary; drop duplicate "Minimum" label - Drag-sort: bundle SortableJS locally, drop the duplicated inline sort CSS/JS from _survey_assets, throttle the backend MutationObserver - Backend: move hardcoded tl_survey operation labels to TL_LANG (de+en) - Remove dead code: SurveyEditorVoter, SurveyPermissionService, categoryFilter/categorySummary + helpers, orphaned publishSurvey lang key Reader layout: - max-width 1000px (other views keep 1400px); back/next buttons fill the width as equal, card-sized halves; sort handle icon offset tweak Docs: update AI_HANDOVER (dual-token CSRF, clean URLs) and README Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
7.1 KiB
Twig
116 lines
7.1 KiB
Twig
{% trans_default_domain 'messages' %}
|
|
|
|
{% include '@Survey/frontend/_survey_assets.html.twig' %}
|
|
{% include '@Survey/frontend/_survey_branding.html.twig' %}
|
|
|
|
{% set moduleHeadline = (headline is iterable ? headline.text|default('') : headline|default(''))|striptags|trim %}
|
|
|
|
<section class="survey-shell survey-shell--reader survey-grid">
|
|
{% if moduleHeadline %}
|
|
<header><h2>{{ moduleHeadline }}</h2></header>
|
|
{% endif %}
|
|
|
|
{% if errorMessage %}
|
|
<div class="survey-alert error">{{ errorMessage }}</div>
|
|
{% elseif survey %}
|
|
<article class="survey-card">
|
|
<div class="survey-meta">
|
|
<h3>{{ survey.title|striptags|trim }}</h3>
|
|
{% if isPreview %}<span class="survey-badge warning">{{ 'survey.survey.preview_badge'|trans }}</span>{% endif %}
|
|
{% if progress.total > 0 %}<span class="survey-badge neutral">{{ progress.current }}/{{ progress.total }}</span>{% endif %}
|
|
</div>
|
|
<p>{{ (survey.description|striptags|trim) ?: 'survey.survey.default_description'|trans }}</p>
|
|
|
|
{% if isPreview %}
|
|
<div class="survey-alert warning">{{ 'survey.survey.preview_notice'|trans }}</div>
|
|
{% endif %}
|
|
|
|
{% if progress.total > 0 %}
|
|
<div class="survey-progress"><span style="width: {{ progress.percentage }}%"></span></div>
|
|
{% endif %}
|
|
</article>
|
|
|
|
{% if isComplete %}
|
|
<article class="survey-card">
|
|
<h3>{{ 'survey.survey.thanks_title'|trans }}</h3>
|
|
<p>{{ isPreview ? 'survey.survey.preview_thanks_text'|trans : 'survey.survey.thanks_text'|trans }}</p>
|
|
{% if answers %}
|
|
<div class="survey-grid">
|
|
{% for answer in answers %}
|
|
<article class="survey-card">
|
|
<div class="survey-badge neutral">{{ answer.questionType }}</div>
|
|
<h4>{{ answer.question|striptags|trim }}</h4>
|
|
<p>{{ answer.value|striptags|trim }}</p>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</article>
|
|
{% elseif question %}
|
|
<article class="survey-card">
|
|
<div class="survey-meta"><span class="survey-badge neutral">{{ 'survey.survey.question_progress'|trans({'%current%': progress.current}) }}</span></div>
|
|
<h3>{{ question.question|striptags|trim }}</h3>
|
|
{% if question.description|striptags|trim %}<p>{{ question.description|striptags|trim }}</p>{% endif %}
|
|
|
|
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid', id: 'survey-answer-form-' ~ question.id}}) }}
|
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
|
{% if isPreview %}
|
|
<input type="hidden" name="preview_question" value="{{ question.id }}">
|
|
<input type="hidden" name="preview_history" value="{{ historyState }}">
|
|
{% endif %}
|
|
{% if question.type == 'yes_no_maybe' %}
|
|
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
|
{% for child in surveyForm.answer %}
|
|
<label class="survey-card" style="cursor: pointer;">
|
|
<div class="survey-meta"><strong>{{ child.vars.label|striptags|trim }}</strong>{{ form_widget(child) }}</div>
|
|
{% set jumpHint = jumpHints|default({})[child.vars.value]|default(null) %}
|
|
{% if jumpHint %}
|
|
<div class="survey-jump-hint">{{ 'survey.survey.jump_to'|trans({'%position%': jumpHint.position, '%label%': jumpHint.label|striptags|trim}) }}</div>
|
|
{% endif %}
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
{% elseif question.type == 'choice' %}
|
|
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
|
|
{% for child in surveyForm.answer %}
|
|
<label class="survey-card" style="cursor: pointer;">
|
|
<div class="survey-meta"><strong>{{ child.vars.label|striptags|trim }}</strong>{{ form_widget(child) }}</div>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
{% elseif question.type == 'range' %}
|
|
<div class="survey-grid survey-range-block">
|
|
<div class="survey-range-value-card">
|
|
<div class="survey-range-value-label">{{ 'survey.survey.current_value'|trans }}</div>
|
|
<div id="survey-range-output-{{ question.id }}" class="survey-range-output">{{ question.rangeMin }}</div>
|
|
</div>
|
|
{{ form_widget(surveyForm.answer, {attr: {'data-survey-range': '1', 'data-survey-range-target': 'survey-range-output-' ~ question.id}}) }}
|
|
<div class="survey-range-scale"><span class="survey-range-boundary">{{ question.rangeMin }}</span><span class="survey-range-boundary">{{ question.rangeMax }}</span></div>
|
|
</div>
|
|
{% else %}
|
|
{{ form_widget(surveyForm.answer) }}
|
|
{% endif %}
|
|
|
|
{{ form_errors(surveyForm.answer) }}
|
|
{{ form_end(surveyForm) }}
|
|
|
|
<div class="survey-button-row survey-button-row--navigation{{ canGoBack ? ' has-back' : '' }}">
|
|
{% if canGoBack %}
|
|
<form method="post" class="survey-navigation-form survey-navigation-form--back">
|
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
|
<input type="hidden" name="_survey_navigation" value="back">
|
|
<input type="hidden" name="_navigation_token" value="{{ csrf_token((isPreview ? 'survey-preview-back-' : 'survey-back-') ~ survey.id) }}">
|
|
{% if isPreview %}
|
|
<input type="hidden" name="preview_question" value="{{ question.id }}">
|
|
<input type="hidden" name="preview_history" value="{{ historyState }}">
|
|
{% endif %}
|
|
<button type="submit" class="survey-button secondary">{{ 'survey.survey.back'|trans }}</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<button type="submit" form="{{ 'survey-answer-form-' ~ question.id }}" class="survey-button primary survey-navigation-submit">{{ 'survey.survey.next'|trans }}</button>
|
|
</div>
|
|
</article>
|
|
{% endif %}
|
|
{% endif %}
|
|
</section> |