Files
survey-bundle/contao/templates/frontend/member_survey_edit.html.twig
T
Claude Fable 5 833a8964cf Add topic sections, ordered inserts, rich-text descriptions and grouped results (Ausbaustufe 3)
- section content type with structure service, reader context and grouped results/PDF/Excel
- new questions append at the end (backend oncreate) and per-section add buttons
- rich-text descriptions: Quill 2 (frontend), reduced TinyMCE (backend), server-side whitelist sanitizer
- externalize inline assets, bundle Chart.js locally, harden debug access, validate range bounds

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 07:57:40 +02:00

261 lines
19 KiB
Twig

{% trans_default_domain 'messages' %}
{% include '@Survey/frontend/_survey_assets.html.twig' %}
{% include '@Survey/frontend/_survey_question_sort_assets.html.twig' %}
{% include '@Survey/frontend/_survey_rich_text_assets.html.twig' %}
<link rel="stylesheet" href="{{ asset('assets/handorgel/css/handorgel.min.css') }}">
{% include '@Survey/frontend/_survey_branding.html.twig' %}
{% set moduleHeadline = (headline is iterable ? headline.text|default('') : headline|default(''))|striptags|trim %}
<section class="survey-shell survey-grid">
{% for message in app.flashes('success') %}
<div class="survey-alert success">{{ message }}</div>
{% endfor %}
{% for message in app.flashes('error') %}
<div class="survey-alert error">{{ message }}</div>
{% endfor %}
{% if moduleHeadline %}
<header>
<h2>{{ moduleHeadline }}</h2>
</header>
{% endif %}
{% if loginRequired %}
<div class="survey-alert warning">{{ 'survey.edit.login_required'|trans }}</div>
{% elseif entryNotAllowed|default(false) %}
<div class="survey-card survey-grid">
<div class="survey-alert warning">{{ 'survey.edit.entry_not_allowed'|trans }}</div>
<div class="survey-button-row">
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">{{ 'survey.edit.back_to_list'|trans }}</a>{% endif %}
</div>
</div>
{% elseif createMode %}
<div class="survey-card">
<h3>{{ 'survey.edit.create_title'|trans }}</h3>
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
{{ form_widget(surveyForm._token) }}
<label class="survey-label">{{ 'survey.edit.label_survey_title'|trans }}{{ form_widget(surveyForm.title) }}{{ form_errors(surveyForm.title) }}</label>
<label class="survey-label">{{ 'survey.edit.label_description'|trans }}{{ form_widget(surveyForm.description) }}</label>
<div class="survey-button-row">
<button class="survey-button primary">{{ 'survey.edit.create_submit'|trans }}</button>
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">{{ 'survey.edit.back_to_list'|trans }}</a>{% endif %}
</div>
{{ form_end(surveyForm, {render_rest: false}) }}
</div>
{% else %}
{% set surveyMetaFormId = 'survey-meta-form' %}
{% set newQuestionFormId = 'survey-question-form-new' %}
{% set canQuestionReorder = canQuestionReorder|default(false) %}
{% set isLocked = structureLocked|default(false) %}
<div class="survey-button-row survey-button-row--editor-actions">
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">{{ 'survey.edit.back_to_list'|trans }}</a>{% endif %}
</div>
{% if not survey.published %}
<div class="survey-card survey-editor-meta-card">
{{ form_start(surveyForm, {action: metadataFormActionUrl, attr: {class: 'survey-form-grid', id: surveyMetaFormId, 'data-survey-dirty-form': '1'}}) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
{{ form_widget(surveyForm._token) }}
{{ form_errors(surveyForm) }}
<label class="survey-label">{{ 'survey.edit.label_survey_title'|trans }}{{ form_widget(surveyForm.title) }}{{ form_errors(surveyForm.title) }}</label>
<label class="survey-label">{{ 'survey.edit.label_intro'|trans }}{{ form_widget(surveyForm.description) }}</label>
<div class="survey-field-hint">{{ 'survey.edit.intro_hint'|trans }}</div>
<div class="survey-button-row survey-button-row--form-actions">
<button class="survey-button primary">{{ 'survey.edit.save_metadata'|trans }}</button>
<div class="survey-dirty-hint" data-survey-dirty-hint-for="{{ surveyMetaFormId }}" hidden>{{ 'survey.edit.dirty_metadata'|trans }}</div>
</div>
{{ form_end(surveyForm, {render_rest: false}) }}
</div>
{% endif %}
{% if isLocked and not survey.published %}
<div class="survey-alert warning">{{ 'survey.edit.structure_locked'|trans }}</div>
{% endif %}
<div class="survey-grid-columns survey-editor-layout">
<div class="survey-question-editor-shell" data-survey-question-editor>
{% if canQuestionReorder %}
<form method="post" action="{{ metadataFormActionUrl }}" class="survey-question-order-save-shell" data-survey-question-order-shell hidden>
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
<input type="hidden" name="_survey_action" value="reorder-questions">
<input type="hidden" name="item_id" value="{{ survey.id }}">
<input type="hidden" name="_token" value="{{ csrf_token('reorder-questions-' ~ survey.id) }}">
<input type="hidden" name="question_order" value="" data-survey-question-order-input>
<button class="survey-button primary survey-question-order-save">{{ 'survey.edit.save_question_order'|trans }}</button>
</form>
{% endif %}
{% if not isLocked %}
<div class="survey-button-row survey-structure-actions">
<a class="survey-button secondary" href="{{ editBaseUrl }}&amp;question=new&amp;newType=section#survey-question-new">{{ 'survey.edit.add_section'|trans }}</a>
{% if not hasSections %}
<span class="survey-field-hint">{{ 'survey.edit.sections_intro'|trans }}</span>
{% endif %}
</div>
{% endif %}
<div class="survey-accordion-list handorgel" data-survey-handorgel{% if canQuestionReorder %} data-survey-question-sortable="1"{% endif %}>
{% for entry in outline %}
{% set question = entry.model %}
{% set questionAnchor = 'survey-question-' ~ question.id %}
{% set isOpen = activeQuestionId == question.id %}
{% set questionItemForm = questionForms[question.id]|default(null) %}
{% set questionFormId = 'survey-question-form-' ~ question.id %}
{% set isSection = entry.isSection %}
<article id="{{ questionAnchor }}" class="survey-accordion-item{% if isSection %} survey-accordion-item--section{% endif %}"{% if canQuestionReorder %} data-survey-question-item data-question-id="{{ question.id }}"{% endif %}{% if isSection %} data-survey-section-item{% endif %}{% if isOpen %} data-survey-open="1"{% endif %}>
<h4 class="survey-accordion-header handorgel__header">
<div class="survey-accordion-header-row">
<button
type="button"
class="survey-accordion-toggle handorgel__header__button"
>
<span class="survey-accordion-heading">
{% if isSection %}
<span class="survey-accordion-index survey-accordion-index--section" aria-hidden="true">§</span>
<span class="survey-accordion-copy">
<span class="survey-section-kicker">{{ 'survey.edit.section_badge'|trans }}</span>
<strong>{{ question.question|striptags|trim ?: ('survey.edit.section_fallback'|trans) }}</strong>
</span>
{% else %}
<span class="survey-accordion-index">{{ entry.position }}.</span>
<span class="survey-accordion-copy">
<strong>{{ question.question|striptags|trim ?: ('survey.edit.question_fallback'|trans({'%id%': question.id})) }}</strong>
</span>
{% endif %}
</span>
</button>
{% if canQuestionReorder %}
<button type="button" class="survey-accordion-sort-handle" data-survey-question-sort-handle aria-label="{{ (isSection ? 'survey.edit.reorder_section' : 'survey.edit.reorder_question')|trans }}" title="{{ (isSection ? 'survey.edit.reorder_section' : 'survey.edit.reorder_question')|trans }}">
<span class="survey-sort-icon" aria-hidden="true">
<svg viewBox="0 0 18 24" role="presentation" focusable="false">
<path d="M9 3 11.6 5.8H6.4z" fill="currentColor"></path>
<path d="M9 21 6.4 18.2h5.2z" fill="currentColor"></path>
<path d="M4.5 12h9" stroke="currentColor" stroke-linecap="round" stroke-width="1.8"></path>
</svg>
</span>
</button>
{% endif %}
</div>
</h4>
<div class="survey-accordion-panel handorgel__content">
<div class="handorgel__content__inner" data-survey-question-editor>
{% if isLocked %}
<div class="survey-grid">
{% if isSection %}
<p class="survey-field-hint">{{ 'survey.edit.section_locked_hint'|trans }}</p>
{% else %}
<div class="survey-meta">
<span class="survey-badge neutral">{{ ('survey.survey.question_type_' ~ question.type)|trans }}</span>
{% if question.mandatory %}<span class="survey-badge warning">{{ 'survey.edit.mandatory_badge'|trans }}</span>{% endif %}
{% if entry.sectionTitle %}<span class="survey-badge neutral">{{ entry.sectionTitle }}</span>{% endif %}
</div>
{% if question.description|survey_has_text %}<div class="survey-rich-text">{{ question.description|survey_rich }}</div>{% endif %}
{% endif %}
</div>
{% elseif questionItemForm %}
{% set questionType = questionItemForm.type.vars.value ?: question.type ?: 'yes_no_maybe' %}
{% include '@Survey/frontend/_survey_question_form.html.twig' with {
form: questionItemForm,
formId: questionFormId,
questionType: questionType,
isNew: false,
dirtyHint: 'survey.edit.dirty_question'|trans,
submitLabel: 'survey.edit.save_this_question'|trans
} only %}
<div class="survey-button-row survey-button-row--form-actions survey-button-row--question-actions">
<button class="survey-button primary" type="submit" form="{{ questionFormId }}">{{ (isSection ? 'survey.edit.save_this_section' : 'survey.edit.save_this_question')|trans }}</button>
<form method="post" class="survey-question-action-form">
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
<input type="hidden" name="_survey_action" value="delete-question">
<input type="hidden" name="item_id" value="{{ question.id }}">
<input type="hidden" name="_token" value="{{ csrf_token('delete-question-' ~ question.id) }}">
<button class="survey-button danger" onclick="return confirm('{{ (isSection ? 'survey.edit.delete_section_confirm' : 'survey.edit.delete_question_confirm')|trans|e('js') }}');">{{ 'survey.edit.delete'|trans }}</button>
</form>
<div class="survey-dirty-hint" data-survey-dirty-hint-for="{{ questionFormId }}" hidden>{{ 'survey.edit.dirty_question'|trans }}</div>
</div>
{% endif %}
</div>
</div>
</article>
{% if entry.isLastInSection and not isLocked and (entry.sectionId > 0 or hasSections) %}
<div class="survey-section-add-row" data-survey-section-add>
<a class="survey-button ghost" href="{{ editBaseUrl }}&amp;question=new&amp;after={{ entry.id }}#survey-question-new">
{% if entry.sectionTitle %}
{{ 'survey.edit.add_question_to_section'|trans({'%section%': entry.sectionTitle}) }}
{% else %}
{{ 'survey.edit.add_question_before_sections'|trans }}
{% endif %}
</a>
</div>
{% endif %}
{% else %}
<div>{{ 'survey.edit.no_questions'|trans }}</div>
{% endfor %}
{% if not isLocked %}
{% set newType = newQuestionContext.type|default('yes_no_maybe') %}
<article id="survey-question-new" class="survey-accordion-item survey-accordion-item--new"{% if createQuestionMode %} data-survey-open="1"{% endif %}>
<h4 class="survey-accordion-header handorgel__header">
<button
type="button"
class="survey-accordion-toggle handorgel__header__button"
>
<span class="survey-accordion-heading">
<span class="survey-accordion-index">+</span>
<span class="survey-accordion-copy">
<strong>{{ (newType == 'section' ? 'survey.edit.new_section' : 'survey.edit.new_question')|trans }}</strong>
</span>
</span>
</button>
</h4>
<div class="survey-accordion-panel handorgel__content">
<div class="handorgel__content__inner" data-survey-question-editor>
{% if newQuestionForm %}
{% set questionType = newQuestionForm.type.vars.value ?: newType %}
{% include '@Survey/frontend/_survey_question_form.html.twig' with {
form: newQuestionForm,
formId: newQuestionFormId,
questionType: questionType,
isNew: true,
newQuestionContext: newQuestionContext,
dirtyHint: 'survey.edit.dirty_new_question'|trans,
submitLabel: (questionType == 'section' ? 'survey.edit.save_new_section' : 'survey.edit.save_new_question')|trans
} only %}
{% endif %}
</div>
</div>
</article>
{% endif %}
</div>
</div>
<div class="survey-grid survey-editor-side">
<div class="survey-card">
<div class="survey-meta survey-section-head"><h3>{{ 'survey.edit.conditions_title'|trans }}</h3></div>
<div class="survey-grid">
{% for condition in conditions %}
<article class="survey-card survey-condition-card">
{% set sourceLabel = (condition.sourceQuestionLabel|default('')|striptags|trim) ?: ('survey.edit.question_fallback'|trans({'%id%': condition.sourceQuestion})) %}
{% set targetLabel = (condition.targetQuestionLabel|default('')|striptags|trim) ?: ('survey.edit.question_fallback'|trans({'%id%': condition.targetQuestion})) %}
<div><strong>Frage {{ condition.sourcePosition ?: condition.sourceQuestion }}: {{ sourceLabel }}</strong></div>
<div>{{ 'survey.edit.condition_prefix'|trans({'%value%': condition.answerValue|default('')|striptags|trim|capitalize}) }}</div>
<div>weiter zu Frage {{ condition.targetPosition ?: condition.targetQuestion }}: {{ targetLabel }}</div>
</article>
{% else %}
<div>{{ 'survey.edit.no_conditions'|trans }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
</section>
<script src="{{ asset('assets/handorgel/js/handorgel.min.js') }}"></script>