Refine survey editor workflow and publication handling
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mummert/survey-bundle",
|
"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",
|
"type": "contao-bundle",
|
||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
"require": {
|
"require": {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
'id' => 'primary',
|
'id' => 'primary',
|
||||||
'alias' => 'unique',
|
'alias' => 'unique',
|
||||||
'published' => 'index',
|
'published' => 'index',
|
||||||
|
'isActive' => 'index',
|
||||||
|
'isTemplate' => 'index',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -33,7 +35,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
'panelLayout' => 'filter;sort,search,limit',
|
'panelLayout' => 'filter;sort,search,limit',
|
||||||
],
|
],
|
||||||
'label' => [
|
'label' => [
|
||||||
'fields' => ['title', 'categorySummary', 'assignedMembersSummary'],
|
'fields' => ['title', 'categorySummary', 'assignedMembersSummary', 'templateSummary'],
|
||||||
'showColumns' => true,
|
'showColumns' => true,
|
||||||
],
|
],
|
||||||
'global_operations' => [
|
'global_operations' => [
|
||||||
@@ -52,10 +54,22 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
'href' => 'act=edit',
|
'href' => 'act=edit',
|
||||||
'icon' => 'header.svg',
|
'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' => [
|
'delete' => [
|
||||||
'href' => 'act=delete',
|
'href' => 'act=delete',
|
||||||
'icon' => 'delete.svg',
|
'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' => [
|
'show' => [
|
||||||
'href' => 'act=show',
|
'href' => 'act=show',
|
||||||
@@ -74,7 +88,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'palettes' => [
|
'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' => [
|
'fields' => [
|
||||||
'id' => [
|
'id' => [
|
||||||
@@ -105,6 +119,7 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
],
|
],
|
||||||
'category' => [
|
'category' => [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey']['category'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey']['category'],
|
||||||
|
'filter' => true,
|
||||||
'inputType' => 'select',
|
'inputType' => 'select',
|
||||||
'foreignKey' => 'tl_survey_category.title',
|
'foreignKey' => 'tl_survey_category.title',
|
||||||
'eval' => ['multiple' => true, 'chosen' => true, 'tl_class' => 'clr'],
|
'eval' => ['multiple' => true, 'chosen' => true, 'tl_class' => 'clr'],
|
||||||
@@ -113,9 +128,8 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
],
|
],
|
||||||
'categoryFilter' => [
|
'categoryFilter' => [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'],
|
||||||
'filter' => true,
|
|
||||||
'inputType' => 'select',
|
'inputType' => 'select',
|
||||||
'options_callback' => [SurveyDcaListener::class, 'getCategoryOptions'],
|
'options_callback' => [SurveyDcaListener::class, 'getUsedCategoryFilterOptions'],
|
||||||
'eval' => ['includeBlankOption' => true, 'chosen' => true, 'findInSet' => true],
|
'eval' => ['includeBlankOption' => true, 'chosen' => true, 'findInSet' => true],
|
||||||
'sql' => "varchar(255) NOT NULL default ''",
|
'sql' => "varchar(255) NOT NULL default ''",
|
||||||
],
|
],
|
||||||
@@ -127,8 +141,33 @@ $GLOBALS['TL_DCA']['tl_survey'] = [
|
|||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey']['published'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey']['published'],
|
||||||
'inputType' => 'checkbox',
|
'inputType' => 'checkbox',
|
||||||
'eval' => ['tl_class' => 'w50 m12'],
|
'eval' => ['tl_class' => 'w50 m12'],
|
||||||
|
'save_callback' => [[SurveyDcaListener::class, 'savePublishedState']],
|
||||||
'sql' => "char(1) NOT NULL default ''",
|
'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' => [
|
'isLocked' => [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey']['isLocked'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey']['isLocked'],
|
||||||
'inputType' => 'checkbox',
|
'inputType' => 'checkbox',
|
||||||
@@ -192,7 +231,7 @@ if ('cli' !== PHP_SAPI) {
|
|||||||
System::getContainer()->get('database_connection')->fetchFirstColumn('SHOW COLUMNS FROM tl_survey'),
|
System::getContainer()->get('database_connection')->fetchFirstColumn('SHOW COLUMNS FROM tl_survey'),
|
||||||
);
|
);
|
||||||
$hasListMetadataColumns = !array_diff(
|
$hasListMetadataColumns = !array_diff(
|
||||||
['categoryfilter', 'categorysummary', 'assignedmembersfilter', 'assignedmemberssummary'],
|
['categoryfilter', 'categorysummary', 'templatefilter', 'templatesummary', 'assignedmembersfilter', 'assignedmemberssummary'],
|
||||||
$columnNames,
|
$columnNames,
|
||||||
);
|
);
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable) {
|
||||||
@@ -205,6 +244,8 @@ if ('cli' !== PHP_SAPI) {
|
|||||||
unset(
|
unset(
|
||||||
$GLOBALS['TL_DCA']['tl_survey']['fields']['categoryFilter'],
|
$GLOBALS['TL_DCA']['tl_survey']['fields']['categoryFilter'],
|
||||||
$GLOBALS['TL_DCA']['tl_survey']['fields']['categorySummary'],
|
$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']['assignedMembersFilter'],
|
||||||
$GLOBALS['TL_DCA']['tl_survey']['fields']['assignedMembersSummary'],
|
$GLOBALS['TL_DCA']['tl_survey']['fields']['assignedMembersSummary'],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ $GLOBALS['TL_DCA']['tl_survey_answer'] = [
|
|||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['questionType'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['questionType'],
|
||||||
'sql' => "varchar(32) NOT NULL default ''",
|
'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' => [
|
'value' => [
|
||||||
'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['value'],
|
'label' => &$GLOBALS['TL_LANG']['tl_survey_answer']['value'],
|
||||||
'sql' => 'text NULL',
|
'sql' => 'text NULL',
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ $GLOBALS['TL_DCA']['tl_survey_category'] = [
|
|||||||
'delete' => [
|
'delete' => [
|
||||||
'href' => 'act=delete',
|
'href' => 'act=delete',
|
||||||
'icon' => 'delete.svg',
|
'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' => [
|
'show' => [
|
||||||
'href' => 'act=show',
|
'href' => 'act=show',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ $GLOBALS['TL_DCA']['tl_survey_condition'] = [
|
|||||||
'dataContainer' => DC_Table::class,
|
'dataContainer' => DC_Table::class,
|
||||||
'ptable' => 'tl_survey',
|
'ptable' => 'tl_survey',
|
||||||
'enableVersioning' => true,
|
'enableVersioning' => true,
|
||||||
|
'onload_callback' => [
|
||||||
|
[SurveyDcaListener::class, 'restrictSurveyConditionEditing'],
|
||||||
|
],
|
||||||
'onsubmit_callback' => [
|
'onsubmit_callback' => [
|
||||||
[SurveyDcaListener::class, 'touchSurveyConditionParent'],
|
[SurveyDcaListener::class, 'touchSurveyConditionParent'],
|
||||||
],
|
],
|
||||||
@@ -51,7 +54,7 @@ $GLOBALS['TL_DCA']['tl_survey_condition'] = [
|
|||||||
'delete' => [
|
'delete' => [
|
||||||
'href' => 'act=delete',
|
'href' => 'act=delete',
|
||||||
'icon' => 'delete.svg',
|
'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' => [
|
'show' => [
|
||||||
'href' => 'act=show',
|
'href' => 'act=show',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [
|
|||||||
'dataContainer' => DC_Table::class,
|
'dataContainer' => DC_Table::class,
|
||||||
'ptable' => 'tl_survey',
|
'ptable' => 'tl_survey',
|
||||||
'enableVersioning' => true,
|
'enableVersioning' => true,
|
||||||
|
'onload_callback' => [
|
||||||
|
[SurveyDcaListener::class, 'restrictSurveyContentEditing'],
|
||||||
|
],
|
||||||
'onsubmit_callback' => [
|
'onsubmit_callback' => [
|
||||||
[SurveyDcaListener::class, 'touchSurveyContentParent'],
|
[SurveyDcaListener::class, 'touchSurveyContentParent'],
|
||||||
],
|
],
|
||||||
@@ -55,7 +58,7 @@ $GLOBALS['TL_DCA']['tl_survey_content'] = [
|
|||||||
'delete' => [
|
'delete' => [
|
||||||
'href' => 'act=delete',
|
'href' => 'act=delete',
|
||||||
'icon' => 'delete.svg',
|
'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' => [
|
'show' => [
|
||||||
'href' => 'act=show',
|
'href' => 'act=show',
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ $GLOBALS['TL_DCA']['tl_survey_editor'] = [
|
|||||||
'delete' => [
|
'delete' => [
|
||||||
'href' => 'act=delete',
|
'href' => 'act=delete',
|
||||||
'icon' => 'delete.svg',
|
'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' => [
|
'show' => [
|
||||||
'href' => 'act=show',
|
'href' => 'act=show',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$GLOBALS['TL_LANG']['MOD']['survey_bundle_surveys'] = ['Umfragen', 'Umfragen, Fragen, Bedingungen und anonyme Antworten verwalten.'];
|
$GLOBALS['TL_LANG']['MOD']['survey_bundle_surveys'] = ['Umfragen', 'Umfragen, Fragen, Bedingungen und anonyme Antworten verwalten.'];
|
||||||
$GLOBALS['TL_LANG']['MOD']['survey_bundle_categories'] = ['Umfrage-Kategorien', 'Kategorien fuer Umfragen pflegen.'];
|
$GLOBALS['TL_LANG']['MOD']['survey_bundle_categories'] = ['Umfrage-Kategorien', 'Kategorien für Umfragen pflegen.'];
|
||||||
|
|
||||||
$GLOBALS['TL_LANG']['FMD']['survey'] = 'Umfragen';
|
$GLOBALS['TL_LANG']['FMD']['survey'] = 'Umfragen';
|
||||||
$GLOBALS['TL_LANG']['FMD']['member_survey_list'] = ['Meine Umfragen', 'Zeigt alle dem Mitglied zugewiesenen Umfragen an.'];
|
$GLOBALS['TL_LANG']['FMD']['member_survey_list'] = ['Meine Umfragen', 'Zeigt alle dem Mitglied zugewiesenen Umfragen an.'];
|
||||||
$GLOBALS['TL_LANG']['FMD']['member_survey_edit'] = ['Umfrage bearbeiten', 'Bearbeitungsbereich fuer zugewiesene Umfragen im Frontend.'];
|
$GLOBALS['TL_LANG']['FMD']['member_survey_edit'] = ['Umfrage bearbeiten', 'Bearbeitungsbereich für zugewiesene Umfragen im Frontend.'];
|
||||||
$GLOBALS['TL_LANG']['FMD']['show_survey'] = ['Umfrage ausfuellen', 'Oeffentliche Umfrageansicht ueber den oeffentlichen Link.'];
|
$GLOBALS['TL_LANG']['FMD']['show_survey'] = ['Umfrage ausfüllen', 'Öffentliche Umfrageansicht über den öffentlichen Link.'];
|
||||||
$GLOBALS['TL_LANG']['FMD']['survey_results'] = ['Ergebnisse', 'Zeigt die Ergebnisse einer Umfrage ueber den konfigurierten Ergebnis-Link an.'];
|
$GLOBALS['TL_LANG']['FMD']['survey_results'] = ['Ergebnisse', 'Zeigt die Ergebnisse einer Umfrage über den konfigurierten Ergebnis-Link an.'];
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
$GLOBALS['TL_LANG']['tl_member']['survey_legend'] = 'Umfrage-Zuordnung';
|
$GLOBALS['TL_LANG']['tl_member']['survey_legend'] = 'Umfrage-Zuordnung';
|
||||||
$GLOBALS['TL_LANG']['tl_member']['surveyCategories'] = ['Umfrage-Kategorien', 'Diese Kategorien werden neuen und bereits zugewiesenen Umfragen dieses Mitglieds automatisch zugeordnet.'];
|
$GLOBALS['TL_LANG']['tl_member']['surveyCategories'] = ['Umfrage-Kategorien', 'Diese Kategorien werden neuen und bereits zugewiesenen Umfragen dieses Mitglieds automatisch zugeordnet.'];
|
||||||
$GLOBALS['TL_LANG']['tl_member']['assignedSurveys'] = ['Zugewiesene Umfragen', 'Waehlen Sie die Umfragen aus, die dieses Mitglied bearbeiten darf. Das Suchfeld in der Auswahl hilft bei vielen Eintraegen.'];
|
$GLOBALS['TL_LANG']['tl_member']['assignedSurveys'] = ['Zugewiesene Umfragen', 'Wählen Sie die Umfragen aus, die dieses Mitglied bearbeiten darf. Das Suchfeld in der Auswahl hilft bei vielen Einträgen.'];
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$GLOBALS['TL_LANG']['tl_module']['survey_navigation_legend'] = 'Verknuepfte Seiten';
|
$GLOBALS['TL_LANG']['tl_module']['survey_navigation_legend'] = 'Verknüpfte Seiten';
|
||||||
$GLOBALS['TL_LANG']['tl_module']['surveyEditPage'] = ['Bearbeitungsseite', 'Seite mit dem Frontendmodul zur Bearbeitung von Umfragen.'];
|
$GLOBALS['TL_LANG']['tl_module']['surveyEditPage'] = ['Bearbeitungsseite', 'Seite mit dem Frontendmodul zur Bearbeitung von Umfragen.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['surveyListPage'] = ['Uebersichtsseite', 'Seite mit der Uebersicht der Umfragen.'];
|
$GLOBALS['TL_LANG']['tl_module']['surveyListPage'] = ['Übersichtsseite', 'Seite mit der Übersicht der Umfragen.'];
|
||||||
$GLOBALS['TL_LANG']['tl_module']['surveyReaderPage'] = ['Anzeigeseite', 'Oeffentliche Seite zum Aufrufen einer Umfrage ueber den oeffentlichen Link.'];
|
$GLOBALS['TL_LANG']['tl_module']['surveyReaderPage'] = ['Anzeigeseite', 'Öffentliche Seite zum Aufrufen einer Umfrage über den öffentlichen Link.'];
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$GLOBALS['TL_LANG']['tl_settings']['survey_settings_legend'] = 'Umfrage-Einstellungen';
|
$GLOBALS['TL_LANG']['tl_settings']['survey_settings_legend'] = 'Umfrage-Einstellungen';
|
||||||
$GLOBALS['TL_LANG']['tl_settings']['surveyReaderPage'] = ['Reader-Seite', 'Diese Seite wird global fuer den oeffentlichen Umfrage-Link verwendet.'];
|
$GLOBALS['TL_LANG']['tl_settings']['surveyReaderPage'] = ['Reader-Seite', 'Diese Seite wird global für den öffentlichen Umfrage-Link verwendet.'];
|
||||||
$GLOBALS['TL_LANG']['tl_settings']['surveyResultsPage'] = ['Ergebnisseite', 'Diese Seite wird global fuer die Anzeige der Umfrage-Ergebnisse verwendet.'];
|
$GLOBALS['TL_LANG']['tl_settings']['surveyResultsPage'] = ['Ergebnisseite', 'Diese Seite wird global für die Anzeige der Umfrage-Ergebnisse verwendet.'];
|
||||||
$GLOBALS['TL_LANG']['tl_settings']['surveyGotenbergUrl'] = ['Gotenberg-URL', 'Basis-URL der Gotenberg-Instanz fuer den serverseitigen PDF-Export, z. B. https://gotenberg.mummert.media'];
|
$GLOBALS['TL_LANG']['tl_settings']['surveyGotenbergUrl'] = ['Gotenberg-URL', 'Basis-URL der Gotenberg-Instanz für den serverseitigen PDF-Export, z. B. https://gotenberg.mummert.media'];
|
||||||
@@ -1,21 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['title'] = ['Titel', 'Titel der Umfrage.'];
|
$GLOBALS['TL_LANG']['tl_survey']['title'] = ['Titel', 'Titel der Umfrage.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['alias'] = ['Oeffentlicher Link-Schluessel', 'Wird automatisch als 20-stelliger Link-Schluessel erzeugt.'];
|
$GLOBALS['TL_LANG']['tl_survey']['alias'] = ['Öffentlicher Link-Schlüssel', 'Wird automatisch als 20-stelliger Link-Schlüssel erzeugt.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['category'] = ['Kategorien', 'Ordnet die Umfrage einer oder mehreren Kategorien zu.'];
|
$GLOBALS['TL_LANG']['tl_survey']['category'] = ['Kategorien', 'Ordnet die Umfrage einer oder mehreren Kategorien zu.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'] = ['Kategorien', 'Filtert die Liste nach einer Umfrage-Kategorie.'];
|
$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'] = ['Kategorien', 'Filtert die Liste nach einer Umfrage-Kategorie.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['categorySummary'] = ['Kategorien', 'Zugeordnete Kategorien der Umfrage.'];
|
$GLOBALS['TL_LANG']['tl_survey']['categorySummary'] = ['Kategorien', 'Zugeordnete Kategorien der Umfrage.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['description'] = ['Beschreibung', 'Einleitung fuer den Editor und das Frontend.'];
|
$GLOBALS['TL_LANG']['tl_survey']['isTemplate'] = ['Umfrage ist eine Vorlage', 'Wird den zugewiesenen Nutzern als Vorlage angezeigt.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['templateFilter'] = ['Vorlage', 'Filtert die Liste nach Vorlagenstatus.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['templateSummary'] = ['Vorlage', 'Zeigt an, ob die Umfrage als Vorlage markiert ist.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['template_yes'] = 'Ja';
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['template_no'] = 'Nein';
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['description'] = ['Beschreibung', 'Einleitung für den Editor und das Frontend.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Grunddaten';
|
$GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Grunddaten';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Bearbeitende Mitglieder';
|
$GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Bearbeitende Mitglieder';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Sichtbarkeit';
|
$GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Sichtbarkeit';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['meta_legend'] = 'Verwaltungsdaten';
|
$GLOBALS['TL_LANG']['tl_survey']['meta_legend'] = 'Verwaltungsdaten';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['published'] = ['Veroeffentlicht', 'Die Umfrage oeffentlich freischalten.'];
|
$GLOBALS['TL_LANG']['tl_survey']['published'] = ['Veröffentlicht', 'Die Umfrage öffentlich freischalten.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['isActive'] = ['Vorübergehend aktiv', 'Schaltet eine bereits veröffentlichte Umfrage öffentlich an oder aus.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['isLocked'] = ['Gesperrt', 'Struktur ist wegen vorhandener Antworten gesperrt.'];
|
$GLOBALS['TL_LANG']['tl_survey']['isLocked'] = ['Gesperrt', 'Struktur ist wegen vorhandener Antworten gesperrt.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembers'] = ['Zugewiesene Mitglieder', 'Waehlen Sie die Mitglieder aus, die diese Umfrage bearbeiten duerfen.'];
|
$GLOBALS['TL_LANG']['tl_survey']['publishSurvey'] = ['Umfrage veröffentlichen', 'Veröffentlicht die Umfrage endgültig.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['toggleActive'] = ['Sichtbarkeit umschalten', 'Schaltet die veröffentlichte Umfrage vorübergehend an oder aus.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembers'] = ['Zugewiesene Mitglieder', 'Wählen Sie die Mitglieder aus, die diese Umfrage bearbeiten dürfen.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersFilter'] = ['Zugewiesene Benutzer', 'Filtert die Liste nach einem zugewiesenen Benutzer.'];
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersFilter'] = ['Zugewiesene Benutzer', 'Filtert die Liste nach einem zugewiesenen Benutzer.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersSummary'] = ['Zugewiesene Benutzer', 'Kommagetrennte Liste der zugewiesenen Benutzer.'];
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersSummary'] = ['Zugewiesene Benutzer', 'Kommagetrennte Liste der zugewiesenen Benutzer.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['copyPublicLink'] = ['Link kopieren', 'Den oeffentlichen Link in die Zwischenablage kopieren'];
|
$GLOBALS['TL_LANG']['tl_survey']['copyPublicLink'] = ['Link kopieren', 'Den öffentlichen Link in die Zwischenablage kopieren'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['showResults'] = ['Ergebnisse anzeigen', 'Die Ergebnisse dieser Umfrage im Frontend anzeigen'];
|
$GLOBALS['TL_LANG']['tl_survey']['showResults'] = ['Ergebnisse anzeigen', 'Die Ergebnisse dieser Umfrage im Frontend anzeigen'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['createdBy'] = ['Angelegt von', 'ID des erstellenden Mitglieds.'];
|
$GLOBALS['TL_LANG']['tl_survey']['createdBy'] = ['Angelegt von', 'ID des erstellenden Mitglieds.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['updatedBy'] = ['Aktualisiert von', 'ID des zuletzt bearbeitenden Mitglieds.'];
|
$GLOBALS['TL_LANG']['tl_survey']['updatedBy'] = ['Aktualisiert von', 'ID des zuletzt bearbeitenden Mitglieds.'];
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
$GLOBALS['TL_LANG']['tl_survey_category']['title'] = ['Titel', 'Titel der Kategorie.'];
|
$GLOBALS['TL_LANG']['tl_survey_category']['title'] = ['Titel', 'Titel der Kategorie.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_category']['alias'] = ['Alias', 'Optionaler Alias.'];
|
$GLOBALS['TL_LANG']['tl_survey_category']['alias'] = ['Alias', 'Optionaler Alias.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_category']['title_legend'] = 'Grunddaten';
|
$GLOBALS['TL_LANG']['tl_survey_category']['title_legend'] = 'Grunddaten';
|
||||||
$GLOBALS['TL_LANG']['tl_survey_category']['sorting'] = ['Sortierung', 'Sortierwert fuer die Backend-Liste.'];
|
$GLOBALS['TL_LANG']['tl_survey_category']['sorting'] = ['Sortierung', 'Sortierwert für die Backend-Liste.'];
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
$GLOBALS['TL_LANG']['tl_survey_condition']['logic_legend'] = 'Sprungregel';
|
$GLOBALS['TL_LANG']['tl_survey_condition']['logic_legend'] = 'Sprungregel';
|
||||||
$GLOBALS['TL_LANG']['tl_survey_condition']['sourceQuestion'] = ['Ausgangsfrage', 'Frage, deren Antwort ausgewertet wird.'];
|
$GLOBALS['TL_LANG']['tl_survey_condition']['sourceQuestion'] = ['Ausgangsfrage', 'Frage, deren Antwort ausgewertet wird.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_condition']['answerValue'] = ['Antwort', 'Antwortwert, bei dem zu einer anderen Frage gesprungen wird.'];
|
$GLOBALS['TL_LANG']['tl_survey_condition']['answerValue'] = ['Antwort', 'Antwortwert, bei dem zu einer anderen Frage gesprungen wird.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_condition']['targetQuestion'] = ['Naechste Frage', 'Zu dieser Frage wird bei passender Antwort gewechselt.'];
|
$GLOBALS['TL_LANG']['tl_survey_condition']['targetQuestion'] = ['Nächste Frage', 'Zu dieser Frage wird bei passender Antwort gewechselt.'];
|
||||||
@@ -13,20 +13,20 @@ $GLOBALS['TL_LANG']['tl_survey_content']['question_legend'] = 'Frage';
|
|||||||
$GLOBALS['TL_LANG']['tl_survey_content']['settings_legend'] = 'Einstellungen';
|
$GLOBALS['TL_LANG']['tl_survey_content']['settings_legend'] = 'Einstellungen';
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['mandatory'] = ['Pflichtfrage', 'Diese Frage muss beantwortet werden.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['mandatory'] = ['Pflichtfrage', 'Diese Frage muss beantwortet werden.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['published'] = ['Aktiv', 'Die Frage wird in der Umfrage angezeigt.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['published'] = ['Aktiv', 'Die Frage wird in der Umfrage angezeigt.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['allowMaybe'] = ['Vielleicht anbieten', 'Ergaenzt die Ja-Nein-Frage um die Antwortmoeglichkeit Vielleicht.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['allowMaybe'] = ['Vielleicht anbieten', 'Ergänzt die Ja-Nein-Frage um die Antwortmöglichkeit Vielleicht.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['jumpOnYes'] = ['Zur Frage springen bei Auswahl ja', 'Springt bei der Antwort Ja direkt zu dieser Frage.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['jumpOnYes'] = ['Zur Frage springen bei Auswahl ja', 'Springt bei der Antwort Ja direkt zu dieser Frage.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['jumpOnNo'] = ['Zur Frage springen bei Auswahl nein', 'Springt bei der Antwort Nein direkt zu dieser Frage.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['jumpOnNo'] = ['Zur Frage springen bei Auswahl nein', 'Springt bei der Antwort Nein direkt zu dieser Frage.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['allowMultiple'] = ['Multiple-Choice aktivieren', 'Erlaubt mehrere Antworten statt einer einzelnen Auswahl.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['allowMultiple'] = ['Multiple-Choice aktivieren', 'Erlaubt mehrere Antworten statt einer einzelnen Auswahl.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption1'] = ['Antwort 1', 'Erste moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption1'] = ['Antwort 1', 'Erste mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption2'] = ['Antwort 2', 'Zweite moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption2'] = ['Antwort 2', 'Zweite mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption3'] = ['Antwort 3', 'Dritte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption3'] = ['Antwort 3', 'Dritte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption4'] = ['Antwort 4', 'Vierte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption4'] = ['Antwort 4', 'Vierte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption5'] = ['Antwort 5', 'Fuenfte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption5'] = ['Antwort 5', 'Fünfte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption6'] = ['Antwort 6', 'Sechste moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption6'] = ['Antwort 6', 'Sechste mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption7'] = ['Antwort 7', 'Siebte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption7'] = ['Antwort 7', 'Siebte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption8'] = ['Antwort 8', 'Achte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption8'] = ['Antwort 8', 'Achte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption9'] = ['Antwort 9', 'Neunte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption9'] = ['Antwort 9', 'Neunte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption10'] = ['Antwort 10', 'Zehnte moegliche Antwort.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['answerOption10'] = ['Antwort 10', 'Zehnte mögliche Antwort.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['rangeMin'] = ['Kleinster Wert', 'Kleinster Wert des Schiebereglers.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['rangeMin'] = ['Kleinster Wert', 'Kleinster Wert des Schiebereglers.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['rangeMax'] = ['Groesster Wert', 'Groesster Wert des Schiebereglers.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['rangeMax'] = ['Größter Wert', 'Größter Wert des Schiebereglers.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_content']['rangeStep'] = ['Schrittweite', 'Abstand zwischen den moeglichen Werten des Schiebereglers.'];
|
$GLOBALS['TL_LANG']['tl_survey_content']['rangeStep'] = ['Schrittweite', 'Abstand zwischen den möglichen Werten des Schiebereglers.'];
|
||||||
@@ -4,4 +4,4 @@ $GLOBALS['TL_LANG']['tl_survey_submission']['token'] = ['Teilnahme-Code', 'Anony
|
|||||||
$GLOBALS['TL_LANG']['tl_survey_submission']['startedAt'] = ['Begonnen am', 'Zeitpunkt, an dem die Umfrage begonnen wurde.'];
|
$GLOBALS['TL_LANG']['tl_survey_submission']['startedAt'] = ['Begonnen am', 'Zeitpunkt, an dem die Umfrage begonnen wurde.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_submission']['completedAt'] = ['Beendet am', 'Zeitpunkt, an dem die Umfrage abgeschlossen wurde.'];
|
$GLOBALS['TL_LANG']['tl_survey_submission']['completedAt'] = ['Beendet am', 'Zeitpunkt, an dem die Umfrage abgeschlossen wurde.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_submission']['currentQuestion'] = ['Aktuelle Frage', 'Interne Kennung der aktuell offenen Frage.'];
|
$GLOBALS['TL_LANG']['tl_survey_submission']['currentQuestion'] = ['Aktuelle Frage', 'Interne Kennung der aktuell offenen Frage.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey_submission']['isFinished'] = ['Abgeschlossen', 'Kennzeichnet abgeschlossene Umfrage-Durchlaeufe.'];
|
$GLOBALS['TL_LANG']['tl_survey_submission']['isFinished'] = ['Abgeschlossen', 'Kennzeichnet abgeschlossene Umfrage-Durchläufe.'];
|
||||||
@@ -5,13 +5,21 @@ $GLOBALS['TL_LANG']['tl_survey']['alias'] = ['Public link key', 'Automatically g
|
|||||||
$GLOBALS['TL_LANG']['tl_survey']['category'] = ['Categories', 'Assigns the survey to one or more categories.'];
|
$GLOBALS['TL_LANG']['tl_survey']['category'] = ['Categories', 'Assigns the survey to one or more categories.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'] = ['Categories', 'Filters the list by a survey category.'];
|
$GLOBALS['TL_LANG']['tl_survey']['categoryFilter'] = ['Categories', 'Filters the list by a survey category.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['categorySummary'] = ['Categories', 'Assigned categories of the survey.'];
|
$GLOBALS['TL_LANG']['tl_survey']['categorySummary'] = ['Categories', 'Assigned categories of the survey.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['isTemplate'] = ['Survey is a template', 'Shown to assigned users as a template.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['templateFilter'] = ['Template', 'Filters the list by template status.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['templateSummary'] = ['Template', 'Shows whether the survey is marked as a template.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['template_yes'] = 'Yes';
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['template_no'] = 'No';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['description'] = ['Description', 'Introduction for the editor and the frontend.'];
|
$GLOBALS['TL_LANG']['tl_survey']['description'] = ['Description', 'Introduction for the editor and the frontend.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Basic data';
|
$GLOBALS['TL_LANG']['tl_survey']['title_legend'] = 'Basic data';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Editing members';
|
$GLOBALS['TL_LANG']['tl_survey']['assignment_legend'] = 'Editing members';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Visibility';
|
$GLOBALS['TL_LANG']['tl_survey']['publishing_legend'] = 'Visibility';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['meta_legend'] = 'Administrative data';
|
$GLOBALS['TL_LANG']['tl_survey']['meta_legend'] = 'Administrative data';
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['published'] = ['Published', 'Make the survey publicly accessible.'];
|
$GLOBALS['TL_LANG']['tl_survey']['published'] = ['Published', 'Make the survey publicly accessible.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['isActive'] = ['Temporarily active', 'Turns an already published survey on or off publicly.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['isLocked'] = ['Locked', 'Structure is locked because answers already exist.'];
|
$GLOBALS['TL_LANG']['tl_survey']['isLocked'] = ['Locked', 'Structure is locked because answers already exist.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['publishSurvey'] = ['Publish survey', 'Publishes the survey permanently.'];
|
||||||
|
$GLOBALS['TL_LANG']['tl_survey']['toggleActive'] = ['Toggle visibility', 'Temporarily switches the published survey on or off.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembers'] = ['Assigned members', 'Choose the members who are allowed to edit this survey.'];
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembers'] = ['Assigned members', 'Choose the members who are allowed to edit this survey.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersFilter'] = ['Assigned users', 'Filters the list by an assigned user.'];
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersFilter'] = ['Assigned users', 'Filters the list by an assigned user.'];
|
||||||
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersSummary'] = ['Assigned users', 'Comma-separated list of assigned users.'];
|
$GLOBALS['TL_LANG']['tl_survey']['assignedMembersSummary'] = ['Assigned users', 'Comma-separated list of assigned users.'];
|
||||||
|
|||||||
@@ -189,12 +189,50 @@
|
|||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.survey-text-summary-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.9rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-text-summary-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.6rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 1.2rem;
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
border: 1px solid rgba(162, 168, 180, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-text-response-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.survey-response-card {
|
.survey-response-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.7rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
background: rgba(255, 255, 255, 0.72);
|
background: rgba(255, 255, 255, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.survey-response-card blockquote {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-response-card__index {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(0, 100, 173, 0.1);
|
||||||
|
color: var(--survey-blue);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.survey-metric-grid {
|
.survey-metric-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.85rem;
|
gap: 0.85rem;
|
||||||
@@ -338,6 +376,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.survey-action-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.survey-action-help {
|
||||||
|
color: var(--survey-gray-dark);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-width: 28rem;
|
||||||
|
}
|
||||||
|
|
||||||
.survey-publish-toggle-form {
|
.survey-publish-toggle-form {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -427,6 +477,12 @@
|
|||||||
.survey-button.primary { background: var(--survey-blue); color: #fff; }
|
.survey-button.primary { background: var(--survey-blue); color: #fff; }
|
||||||
.survey-button.secondary { background: rgba(255,255,255,0.86); color: var(--survey-blue); border-color: rgba(0,100,173,0.18); }
|
.survey-button.secondary { background: rgba(255,255,255,0.86); color: var(--survey-blue); border-color: rgba(0,100,173,0.18); }
|
||||||
.survey-button.danger { background: rgba(157, 82, 118, 0.12); color: var(--survey-purple); border-color: rgba(157, 82, 118, 0.22); }
|
.survey-button.danger { background: rgba(157, 82, 118, 0.12); color: var(--survey-purple); border-color: rgba(157, 82, 118, 0.22); }
|
||||||
|
.survey-button.is-disabled,
|
||||||
|
.survey-button[disabled] {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.survey-button-compact {
|
.survey-button-compact {
|
||||||
padding: 0.65rem 0.9rem;
|
padding: 0.65rem 0.9rem;
|
||||||
|
|||||||
@@ -46,41 +46,32 @@
|
|||||||
{{ form_end(surveyForm, {render_rest: false}) }}
|
{{ form_end(surveyForm, {render_rest: false}) }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set locked = survey.isLocked == '1' %}
|
|
||||||
<div class="survey-button-row survey-button-row--editor-actions">
|
<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 %}
|
{% if backUrl %}<a class="survey-button secondary" href="{{ backUrl }}">{{ 'survey.edit.back_to_list'|trans }}</a>{% endif %}
|
||||||
{% if publicUrl %}<a class="survey-button primary" href="{{ publicUrl }}" target="_blank" rel="noreferrer">{{ 'survey.edit.public_view_open'|trans }}</a>{% endif %}
|
<div class="survey-action-stack">
|
||||||
{% if publicUrl %}
|
{% if not survey.published %}
|
||||||
<button
|
<form method="post" class="survey-action-form">
|
||||||
type="button"
|
|
||||||
class="survey-button secondary"
|
|
||||||
data-copy-text="{{ publicUrl|e('html_attr') }}"
|
|
||||||
data-copy-label="{{ 'survey.edit.copy_public_link'|trans|e('html_attr') }}"
|
|
||||||
data-copy-success="{{ 'survey.edit.copy_public_link_success'|trans|e('html_attr') }}"
|
|
||||||
>{{ 'survey.edit.copy_public_link'|trans }}</button>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post" class="survey-publish-toggle-form">
|
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
||||||
<input type="hidden" name="_survey_action" value="toggle-published">
|
<input type="hidden" name="_survey_action" value="publish-survey">
|
||||||
<input type="hidden" name="item_id" value="{{ survey.id }}">
|
<input type="hidden" name="item_id" value="{{ survey.id }}">
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token('toggle-published-' ~ survey.id) }}">
|
<input type="hidden" name="_token" value="{{ csrf_token('publish-survey-' ~ survey.id) }}">
|
||||||
<button
|
<button class="survey-button primary" onclick="return confirm('{{ 'survey.edit.publish_confirm'|trans|e('js') }}');">{{ 'survey.edit.publish_survey'|trans }}</button>
|
||||||
type="submit"
|
|
||||||
class="survey-publish-toggle{{ survey.published ? ' is-on' : '' }}"
|
|
||||||
aria-pressed="{{ survey.published ? 'true' : 'false' }}"
|
|
||||||
>
|
|
||||||
<span class="survey-publish-toggle__track" aria-hidden="true">
|
|
||||||
<span class="survey-publish-toggle__thumb"></span>
|
|
||||||
</span>
|
|
||||||
<span class="survey-publish-toggle__copy">
|
|
||||||
<span class="survey-publish-toggle__label">{{ 'survey.edit.status'|trans }}</span>
|
|
||||||
<span class="survey-publish-toggle__value">{{ survey.published ? 'survey.edit.published'|trans : 'survey.edit.draft'|trans }}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
<div class="survey-action-help">{{ 'survey.edit.publish_help'|trans }}</div>
|
||||||
|
{% else %}
|
||||||
|
<form method="post" class="survey-action-form">
|
||||||
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
||||||
|
<input type="hidden" name="_survey_action" value="toggle-active">
|
||||||
|
<input type="hidden" name="item_id" value="{{ survey.id }}">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('toggle-active-' ~ survey.id) }}">
|
||||||
|
<button class="survey-button secondary">{{ survey.isActive ? 'survey.edit.deactivate_survey'|trans : 'survey.edit.activate_survey'|trans }}</button>
|
||||||
|
</form>
|
||||||
|
<div class="survey-action-help">{{ survey.isActive ? 'survey.edit.deactivate_help'|trans : 'survey.edit.activate_help'|trans }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if locked %}
|
{% if structureLocked|default(false) %}
|
||||||
<div class="survey-alert warning">{{ 'survey.edit.structure_locked'|trans }}</div>
|
<div class="survey-alert warning">{{ 'survey.edit.structure_locked'|trans }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -147,7 +138,15 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<div class="survey-accordion-panel handorgel__content">
|
<div class="survey-accordion-panel handorgel__content">
|
||||||
<div class="handorgel__content__inner" data-survey-question-editor>
|
<div class="handorgel__content__inner" data-survey-question-editor>
|
||||||
{% if questionItemForm %}
|
{% if structureLocked|default(false) %}
|
||||||
|
<div class="survey-grid">
|
||||||
|
<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 %}
|
||||||
|
</div>
|
||||||
|
{% if question.description|striptags|trim %}<p>{{ question.description|striptags|trim }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% elseif questionItemForm %}
|
||||||
{% set questionType = questionItemForm.type.vars.value ?: question.type ?: 'yes_no_maybe' %}
|
{% set questionType = questionItemForm.type.vars.value ?: question.type ?: 'yes_no_maybe' %}
|
||||||
{{ form_start(questionItemForm, {attr: {class: 'survey-form-grid', id: questionFormId, 'data-survey-dirty-form': '1'}}) }}
|
{{ form_start(questionItemForm, {attr: {class: 'survey-form-grid', id: questionFormId, 'data-survey-dirty-form': '1'}}) }}
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
||||||
@@ -186,7 +185,6 @@
|
|||||||
|
|
||||||
<div class="survey-button-row survey-button-row--form-actions survey-button-row--question-actions">
|
<div class="survey-button-row survey-button-row--form-actions survey-button-row--question-actions">
|
||||||
<button class="survey-button primary" type="submit" form="{{ questionFormId }}">{{ 'survey.edit.save_this_question'|trans }}</button>
|
<button class="survey-button primary" type="submit" form="{{ questionFormId }}">{{ 'survey.edit.save_this_question'|trans }}</button>
|
||||||
{% if not locked %}
|
|
||||||
<form method="post" class="survey-question-action-form">
|
<form method="post" class="survey-question-action-form">
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
||||||
<input type="hidden" name="_survey_action" value="delete-question">
|
<input type="hidden" name="_survey_action" value="delete-question">
|
||||||
@@ -194,7 +192,6 @@
|
|||||||
<input type="hidden" name="_token" value="{{ csrf_token('delete-question-' ~ question.id) }}">
|
<input type="hidden" name="_token" value="{{ csrf_token('delete-question-' ~ question.id) }}">
|
||||||
<button class="survey-button danger" onclick="return confirm('{{ 'survey.edit.delete_question_confirm'|trans|e('js') }}');">{{ 'survey.edit.delete'|trans }}</button>
|
<button class="survey-button danger" onclick="return confirm('{{ 'survey.edit.delete_question_confirm'|trans|e('js') }}');">{{ 'survey.edit.delete'|trans }}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
|
||||||
<div class="survey-dirty-hint" data-survey-dirty-hint-for="{{ questionFormId }}" hidden>{{ 'survey.edit.dirty_question'|trans }}</div>
|
<div class="survey-dirty-hint" data-survey-dirty-hint-for="{{ questionFormId }}" hidden>{{ 'survey.edit.dirty_question'|trans }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -205,7 +202,7 @@
|
|||||||
<div>{{ 'survey.edit.no_questions'|trans }}</div>
|
<div>{{ 'survey.edit.no_questions'|trans }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if not locked %}
|
{% if not (structureLocked|default(false)) %}
|
||||||
<article id="survey-question-new" class="survey-accordion-item">
|
<article id="survey-question-new" class="survey-accordion-item">
|
||||||
<h4 class="survey-accordion-header handorgel__header">
|
<h4 class="survey-accordion-header handorgel__header">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -35,31 +35,15 @@
|
|||||||
<div class="survey-list-card__head">
|
<div class="survey-list-card__head">
|
||||||
<div class="survey-list-card__title-wrap">
|
<div class="survey-list-card__title-wrap">
|
||||||
<h3>{{ survey.title|striptags|trim }}</h3>
|
<h3>{{ survey.title|striptags|trim }}</h3>
|
||||||
{% if survey.isLocked %}
|
|
||||||
<div class="survey-meta">
|
<div class="survey-meta">
|
||||||
|
<span class="survey-badge {{ survey.published ? 'success' : 'warning' }}">{{ survey.published ? 'survey.list.published'|trans : 'survey.list.draft'|trans }}</span>
|
||||||
|
{% if survey.published %}
|
||||||
|
<span class="survey-badge {{ survey.isActive ? 'success' : 'warning' }}">{{ survey.isActive ? 'survey.list.active'|trans : 'survey.list.inactive'|trans }}</span>
|
||||||
|
{% elseif survey.isLocked %}
|
||||||
<span class="survey-badge warning">{{ 'survey.list.locked'|trans }}</span>
|
<span class="survey-badge warning">{{ 'survey.list.locked'|trans }}</span>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<form method="post" class="survey-publish-toggle-form survey-list-card__toggle-form">
|
</div>
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
|
||||||
<input type="hidden" name="_survey_action" value="toggle-published">
|
|
||||||
<input type="hidden" name="item_id" value="{{ survey.id }}">
|
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token('toggle-published-' ~ survey.id) }}">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="survey-publish-toggle survey-publish-toggle--compact{{ survey.published ? ' is-on' : '' }}"
|
|
||||||
aria-pressed="{{ survey.published ? 'true' : 'false' }}"
|
|
||||||
>
|
|
||||||
<span class="survey-publish-toggle__track" aria-hidden="true">
|
|
||||||
<span class="survey-publish-toggle__thumb"></span>
|
|
||||||
</span>
|
|
||||||
<span class="survey-publish-toggle__copy">
|
|
||||||
<span class="survey-publish-toggle__label">{{ 'survey.edit.status'|trans }}</span>
|
|
||||||
<span class="survey-publish-toggle__value">{{ survey.published ? 'survey.list.published'|trans : 'survey.list.draft'|trans }}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="survey-meta survey-list-card__stats">
|
<div class="survey-meta survey-list-card__stats">
|
||||||
<span class="survey-badge neutral">{{ 'survey.list.questions'|trans({'%count%': survey.questionCount}) }}</span>
|
<span class="survey-badge neutral">{{ 'survey.list.questions'|trans({'%count%': survey.questionCount}) }}</span>
|
||||||
@@ -69,8 +53,13 @@
|
|||||||
{% if survey.editUrl %}
|
{% if survey.editUrl %}
|
||||||
<a class="survey-button primary" href="{{ survey.editUrl }}">{{ 'survey.list.edit'|trans }}</a>
|
<a class="survey-button primary" href="{{ survey.editUrl }}">{{ 'survey.list.edit'|trans }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if survey.publicUrl %}
|
{% if survey.publicUrl and survey.published and survey.isActive %}
|
||||||
<a class="survey-button secondary" href="{{ survey.publicUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.public_view'|trans }}</a>
|
<a class="survey-button secondary" href="{{ survey.publicUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.public_view'|trans }}</a>
|
||||||
|
{% elseif survey.publicUrl %}
|
||||||
|
<span class="survey-button secondary is-disabled" aria-disabled="true">{{ 'survey.list.public_view'|trans }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if survey.draftUrl and not survey.published %}
|
||||||
|
<a class="survey-button secondary" href="{{ survey.draftUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.draft_view'|trans }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if survey.resultsUrl %}
|
{% if survey.resultsUrl %}
|
||||||
<a class="survey-button secondary" href="{{ survey.resultsUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.show_results'|trans }}</a>
|
<a class="survey-button secondary" href="{{ survey.resultsUrl }}" target="_blank" rel="noreferrer">{{ 'survey.list.show_results'|trans }}</a>
|
||||||
|
|||||||
@@ -16,10 +16,15 @@
|
|||||||
<article class="survey-card">
|
<article class="survey-card">
|
||||||
<div class="survey-meta">
|
<div class="survey-meta">
|
||||||
<h3>{{ survey.title|striptags|trim }}</h3>
|
<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 %}
|
{% if progress.total > 0 %}<span class="survey-badge neutral">{{ progress.current }}/{{ progress.total }}</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p>{{ (survey.description|striptags|trim) ?: 'survey.survey.default_description'|trans }}</p>
|
<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 %}
|
{% if progress.total > 0 %}
|
||||||
<div class="survey-progress"><span style="width: {{ progress.percentage }}%"></span></div>
|
<div class="survey-progress"><span style="width: {{ progress.percentage }}%"></span></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -28,7 +33,7 @@
|
|||||||
{% if isComplete %}
|
{% if isComplete %}
|
||||||
<article class="survey-card">
|
<article class="survey-card">
|
||||||
<h3>{{ 'survey.survey.thanks_title'|trans }}</h3>
|
<h3>{{ 'survey.survey.thanks_title'|trans }}</h3>
|
||||||
<p>{{ 'survey.survey.thanks_text'|trans }}</p>
|
<p>{{ isPreview ? 'survey.survey.preview_thanks_text'|trans : 'survey.survey.thanks_text'|trans }}</p>
|
||||||
{% if answers %}
|
{% if answers %}
|
||||||
<div class="survey-grid">
|
<div class="survey-grid">
|
||||||
{% for answer in answers %}
|
{% for answer in answers %}
|
||||||
@@ -47,8 +52,25 @@
|
|||||||
<h3>{{ question.question|striptags|trim }}</h3>
|
<h3>{{ question.question|striptags|trim }}</h3>
|
||||||
{% if question.description|striptags|trim %}<p>{{ question.description|striptags|trim }}</p>{% endif %}
|
{% if question.description|striptags|trim %}<p>{{ question.description|striptags|trim }}</p>{% endif %}
|
||||||
|
|
||||||
|
{% if canGoBack %}
|
||||||
|
<form method="post" class="survey-button-row survey-button-row--navigation">
|
||||||
|
<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 %}
|
||||||
|
|
||||||
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
|
{{ form_start(surveyForm, {attr: {class: 'survey-form-grid'}}) }}
|
||||||
<input type="hidden" name="REQUEST_TOKEN" value="{{ contao.request_token }}">
|
<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' %}
|
{% if question.type == 'yes_no_maybe' %}
|
||||||
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
<div class="survey-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
||||||
{% for child in surveyForm.answer %}
|
{% for child in surveyForm.answer %}
|
||||||
|
|||||||
@@ -108,25 +108,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if question.responseSummary is defined and question.responseSummary %}
|
{% if question.responseSummary is defined and question.responseSummary %}
|
||||||
<div class="survey-results-list">
|
<div class="survey-text-summary-grid">
|
||||||
{% for option in question.responseSummary %}
|
{% for option in question.responseSummary %}
|
||||||
<div class="survey-results-row">
|
<article class="survey-text-summary-card">
|
||||||
<div class="survey-meta">
|
|
||||||
<strong>{{ option.label|striptags|trim }}</strong>
|
<strong>{{ option.label|striptags|trim }}</strong>
|
||||||
|
<div class="survey-meta">
|
||||||
<span class="survey-badge neutral">{{ option.count }}</span>
|
<span class="survey-badge neutral">{{ option.count }}</span>
|
||||||
<span class="survey-badge neutral">{{ option.percentage }}%</span>
|
<span class="survey-badge neutral">{{ option.percentage }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="survey-progress">
|
</article>
|
||||||
<span class="survey-progress-bar" style="width: {{ option.percentage }}%"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="survey-results-list survey-results-responses">
|
<div class="survey-text-response-grid survey-results-responses">
|
||||||
{% for response in question.responses %}
|
{% for response in question.responses %}
|
||||||
<blockquote class="survey-card survey-response-card">{{ response|striptags|trim|nl2br }}</blockquote>
|
<article class="survey-card survey-response-card">
|
||||||
|
<span class="survey-response-card__index">{{ loop.index }}</span>
|
||||||
|
<blockquote>{{ response|striptags|trim|nl2br }}</blockquote>
|
||||||
|
</article>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{{ 'survey.results.no_text_responses'|trans }}</p>
|
<p>{{ 'survey.results.no_text_responses'|trans }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
$template->set('loginRequired', false);
|
$template->set('loginRequired', false);
|
||||||
$template->set('entryNotAllowed', false);
|
$template->set('entryNotAllowed', false);
|
||||||
$template->set('createMode', true);
|
$template->set('createMode', true);
|
||||||
|
$template->set('structureLocked', false);
|
||||||
$template->set('surveyForm', $surveyForm->createView());
|
$template->set('surveyForm', $surveyForm->createView());
|
||||||
$template->set('backUrl', $this->resolveListUrl($model));
|
$template->set('backUrl', $this->resolveListUrl($model));
|
||||||
$template->set('publicUrl', null);
|
$template->set('publicUrl', null);
|
||||||
@@ -112,11 +113,15 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
return $template->getResponse();
|
return $template->getResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$structureLocked = $this->surveyEditorService->isStructureLocked($survey);
|
||||||
|
|
||||||
$questions = $this->surveyQuestionRepository->findAllBySurvey((int) $survey->id);
|
$questions = $this->surveyQuestionRepository->findAllBySurvey((int) $survey->id);
|
||||||
$activeQuestion = $this->resolveActiveQuestion($request, (int) $survey->id);
|
$activeQuestion = $this->resolveActiveQuestion($request, (int) $survey->id);
|
||||||
$activeQuestionId = $activeQuestion ? (int) $activeQuestion->id : 0;
|
$activeQuestionId = $activeQuestion ? (int) $activeQuestion->id : 0;
|
||||||
$questionFormViews = [];
|
$questionFormViews = [];
|
||||||
|
$newQuestionFormView = null;
|
||||||
|
|
||||||
|
if (!$structureLocked) {
|
||||||
foreach ($questions as $question) {
|
foreach ($questions as $question) {
|
||||||
$questionId = (int) $question->id;
|
$questionId = (int) $question->id;
|
||||||
$questionForm = $this->formFactory->createNamed('survey_question_'.$questionId, SurveyQuestionEditorType::class, SurveyQuestionData::fromModel($question), [
|
$questionForm = $this->formFactory->createNamed('survey_question_'.$questionId, SurveyQuestionEditorType::class, SurveyQuestionData::fromModel($question), [
|
||||||
@@ -130,7 +135,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
|
|
||||||
if ($questionForm->isValid()) {
|
if ($questionForm->isValid()) {
|
||||||
try {
|
try {
|
||||||
$savedQuestionId = $this->surveyEditorService->saveQuestion($survey, $questionForm->getData(), $questionId);
|
$this->surveyEditorService->saveQuestion($survey, $questionForm->getData(), $questionId);
|
||||||
$this->addFlash('success', 'Die Frage wurde aktualisiert.');
|
$this->addFlash('success', 'Die Frage wurde aktualisiert.');
|
||||||
|
|
||||||
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
|
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
|
||||||
@@ -156,7 +161,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
|
|
||||||
if ($newQuestionForm->isValid()) {
|
if ($newQuestionForm->isValid()) {
|
||||||
try {
|
try {
|
||||||
$savedQuestionId = $this->surveyEditorService->saveQuestion($survey, $newQuestionForm->getData(), 0);
|
$this->surveyEditorService->saveQuestion($survey, $newQuestionForm->getData(), 0);
|
||||||
$this->addFlash('success', 'Die Frage wurde angelegt.');
|
$this->addFlash('success', 'Die Frage wurde angelegt.');
|
||||||
|
|
||||||
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
|
return new RedirectResponse($this->buildEditUrl($model, $request, (int) $survey->id));
|
||||||
@@ -168,14 +173,18 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$newQuestionFormView = $newQuestionForm->createView();
|
||||||
|
}
|
||||||
|
|
||||||
$template->set('loginRequired', false);
|
$template->set('loginRequired', false);
|
||||||
$template->set('entryNotAllowed', false);
|
$template->set('entryNotAllowed', false);
|
||||||
$template->set('createMode', false);
|
$template->set('createMode', false);
|
||||||
|
$template->set('structureLocked', $structureLocked);
|
||||||
$template->set('createQuestionMode', $createQuestionMode);
|
$template->set('createQuestionMode', $createQuestionMode);
|
||||||
$template->set('survey', $survey);
|
$template->set('survey', $survey);
|
||||||
$template->set('surveyForm', $surveyForm->createView());
|
$template->set('surveyForm', $surveyForm->createView());
|
||||||
$template->set('questionForms', $questionFormViews);
|
$template->set('questionForms', $questionFormViews);
|
||||||
$template->set('newQuestionForm', $newQuestionForm->createView());
|
$template->set('newQuestionForm', $newQuestionFormView);
|
||||||
$template->set('questions', $questions);
|
$template->set('questions', $questions);
|
||||||
$conditionOverview = $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id);
|
$conditionOverview = $this->surveyConditionRepository->findOverviewBySurvey((int) $survey->id);
|
||||||
|
|
||||||
@@ -205,7 +214,7 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->surveyEditorRepository->isEditor((int) $survey->id, $memberId)) {
|
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;
|
return $survey;
|
||||||
@@ -259,23 +268,28 @@ final class MemberSurveyEditController extends AbstractFrontendModuleController
|
|||||||
$token = (string) $request->request->get('_token');
|
$token = (string) $request->request->get('_token');
|
||||||
|
|
||||||
if (!$this->isCsrfTokenValid($action.'-'.$itemId, $token)) {
|
if (!$this->isCsrfTokenValid($action.'-'.$itemId, $token)) {
|
||||||
throw new \RuntimeException('Ungueltiger CSRF-Token.');
|
throw new \RuntimeException('Ungültiger CSRF-Token.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ('toggle-published' === $action) {
|
if ('publish-survey' === $action) {
|
||||||
$isPublished = $this->surveyEditorService->toggleSurveyPublished($survey, $memberId);
|
$this->surveyEditorService->publishSurvey($survey, $memberId);
|
||||||
$this->addFlash('success', $isPublished ? 'Die Umfrage ist jetzt veroeffentlicht.' : 'Die Umfrage ist jetzt nicht mehr veroeffentlicht.');
|
$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) {
|
if ('delete-question' === $action) {
|
||||||
$this->surveyEditorService->deleteQuestion($survey, $itemId);
|
$this->surveyEditorService->deleteQuestion($survey, $itemId);
|
||||||
$this->addFlash('success', 'Die Frage wurde geloescht.');
|
$this->addFlash('success', 'Die Frage wurde gelöscht.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('delete-condition' === $action) {
|
if ('delete-condition' === $action) {
|
||||||
$this->surveyEditorService->deleteCondition($survey, $itemId);
|
$this->surveyEditorService->deleteCondition($survey, $itemId);
|
||||||
$this->addFlash('success', 'Die Bedingung wurde geloescht.');
|
$this->addFlash('success', 'Die Bedingung wurde gelöscht.');
|
||||||
}
|
}
|
||||||
} catch (\Throwable $exception) {
|
} catch (\Throwable $exception) {
|
||||||
$this->addFlash('error', $exception->getMessage());
|
$this->addFlash('error', $exception->getMessage());
|
||||||
|
|||||||
@@ -52,18 +52,21 @@ final class MemberSurveyListController extends AbstractFrontendModuleController
|
|||||||
|
|
||||||
foreach ($this->surveyRepository->findEditableByMember((int) $user->id) as $survey) {
|
foreach ($this->surveyRepository->findEditableByMember((int) $user->id) as $survey) {
|
||||||
$surveyId = (int) $survey['id'];
|
$surveyId = (int) $survey['id'];
|
||||||
|
$alias = (string) $survey['alias'];
|
||||||
|
|
||||||
$items[] = [
|
$items[] = [
|
||||||
'id' => $surveyId,
|
'id' => $surveyId,
|
||||||
'title' => (string) $survey['title'],
|
'title' => (string) $survey['title'],
|
||||||
'description' => (string) ($survey['description'] ?? ''),
|
'description' => (string) ($survey['description'] ?? ''),
|
||||||
|
'isActive' => !empty($survey['isActive']),
|
||||||
'isLocked' => !empty($survey['isLocked']),
|
'isLocked' => !empty($survey['isLocked']),
|
||||||
'published' => !empty($survey['published']),
|
'published' => !empty($survey['published']),
|
||||||
'questionCount' => (int) ($survey['questionCount'] ?? 0),
|
'questionCount' => (int) ($survey['questionCount'] ?? 0),
|
||||||
'participationCount' => (int) ($survey['participationCount'] ?? 0),
|
'participationCount' => (int) ($survey['participationCount'] ?? 0),
|
||||||
'editUrl' => $editPage instanceof PageModel ? $this->generateContentUrl($editPage, ['survey' => $surveyId]) : null,
|
'editUrl' => $editPage instanceof PageModel ? $this->generateContentUrl($editPage, ['survey' => $surveyId]) : null,
|
||||||
'publicUrl' => $readerPage instanceof PageModel ? $this->generateContentUrl($readerPage, ['auto_item' => (string) $survey['alias']]) : null,
|
'publicUrl' => $readerPage instanceof PageModel ? $this->generateContentUrl($readerPage, ['auto_item' => $alias]) : null,
|
||||||
'resultsUrl' => $resultsPage instanceof PageModel ? $this->generateContentUrl($resultsPage, ['auto_item' => (string) $survey['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');
|
$token = (string) $request->request->get('_token');
|
||||||
|
|
||||||
if ($surveyId <= 0 || !$this->isCsrfTokenValid($action.'-'.$surveyId, $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);
|
$survey = $this->resolveEditableSurvey($surveyId, $memberId);
|
||||||
|
|
||||||
try {
|
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) {
|
if ('delete-survey' === $action) {
|
||||||
$this->surveyEditorService->deleteSurvey($survey);
|
$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) {
|
} catch (\Throwable $exception) {
|
||||||
$this->addFlash('error', $exception->getMessage());
|
$this->addFlash('error', $exception->getMessage());
|
||||||
@@ -108,7 +106,7 @@ final class MemberSurveyListController extends AbstractFrontendModuleController
|
|||||||
$survey = $this->surveyRepository->findById($surveyId);
|
$survey = $this->surveyRepository->findById($surveyId);
|
||||||
|
|
||||||
if (!$survey instanceof SurveyModel || !$this->surveyEditorRepository->isEditor($surveyId, $memberId)) {
|
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;
|
return $survey;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController
|
|||||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
|
||||||
use Contao\CoreBundle\Twig\FragmentTemplate;
|
use Contao\CoreBundle\Twig\FragmentTemplate;
|
||||||
use Contao\ModuleModel;
|
use Contao\ModuleModel;
|
||||||
|
use Mummert\SurveyBundle\Model\SurveyContentModel;
|
||||||
|
use Mummert\SurveyBundle\Model\SurveyModel;
|
||||||
use Mummert\SurveyBundle\Form\Model\SurveyAnswerData;
|
use Mummert\SurveyBundle\Form\Model\SurveyAnswerData;
|
||||||
use Mummert\SurveyBundle\Form\SurveyQuestionAnswerType;
|
use Mummert\SurveyBundle\Form\SurveyQuestionAnswerType;
|
||||||
use Mummert\SurveyBundle\Repository\SurveyRepository;
|
use Mummert\SurveyBundle\Repository\SurveyRepository;
|
||||||
@@ -30,48 +32,57 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
|||||||
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
|
||||||
{
|
{
|
||||||
$publicAlias = $this->resolvePublicAlias($request);
|
$publicAlias = $this->resolvePublicAlias($request);
|
||||||
|
$previewMode = $request->query->getBoolean('preview');
|
||||||
|
|
||||||
if ('' === $publicAlias) {
|
if ('' === $publicAlias) {
|
||||||
$template->set('errorMessage', 'Es wurde kein oeffentlicher Link auf der Anzeigeseite gefunden.');
|
$this->applyEmptyState($template, 'Es wurde kein öffentlicher Link auf der Anzeigeseite gefunden.', $previewMode);
|
||||||
$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]);
|
|
||||||
|
|
||||||
return $template->getResponse();
|
return $template->getResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
$survey = $this->surveyRepository->findByPublicAlias($publicAlias, true);
|
$survey = $this->surveyRepository->findByPublicAlias($publicAlias, !$previewMode);
|
||||||
|
|
||||||
if (null === $survey) {
|
if (null === $survey) {
|
||||||
$template->set('errorMessage', 'Die Umfrage wurde nicht gefunden oder ist nicht veroeffentlicht.');
|
$this->applyEmptyState($template, 'Die Umfrage wurde nicht gefunden, ist nicht veröffentlicht oder aktuell deaktiviert.', $previewMode);
|
||||||
$template->set('question', null);
|
|
||||||
$template->set('surveyForm', null);
|
return $template->getResponse();
|
||||||
$template->set('isComplete', false);
|
}
|
||||||
$template->set('answers', []);
|
|
||||||
$template->set('survey', null);
|
if ($previewMode && '1' === (string) $survey->published) {
|
||||||
$template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]);
|
$this->applyEmptyState($template, 'Die Entwurfsansicht ist für bereits veröffentlichte Umfragen nicht verfügbar.', true, $survey);
|
||||||
|
|
||||||
return $template->getResponse();
|
return $template->getResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($htmlHeadBag = $this->getHtmlHeadBag()) {
|
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);
|
$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);
|
$question = $this->surveyFlowService->resolveCurrentQuestion($survey, $submission);
|
||||||
|
|
||||||
if (null === $question && '1' !== (string) $submission->isFinished) {
|
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('question', null);
|
||||||
$template->set('surveyForm', null);
|
$template->set('surveyForm', null);
|
||||||
$template->set('isComplete', false);
|
$template->set('isComplete', false);
|
||||||
$template->set('answers', []);
|
$template->set('answers', []);
|
||||||
$template->set('survey', $survey);
|
$template->set('survey', $survey);
|
||||||
$template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]);
|
$template->set('progress', ['current' => 0, 'total' => 0, 'percentage' => 0]);
|
||||||
|
$template->set('isPreview', false);
|
||||||
|
|
||||||
return $template->getResponse();
|
return $template->getResponse();
|
||||||
}
|
}
|
||||||
@@ -84,17 +95,28 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
|||||||
$template->set('surveyForm', null);
|
$template->set('surveyForm', null);
|
||||||
$template->set('isComplete', true);
|
$template->set('isComplete', true);
|
||||||
$template->set('answers', $this->surveySubmissionService->getAnswersForSubmission($submission));
|
$template->set('answers', $this->surveySubmissionService->getAnswersForSubmission($submission));
|
||||||
|
$template->set('isPreview', false);
|
||||||
|
$template->set('canGoBack', false);
|
||||||
|
$template->set('historyState', '');
|
||||||
|
|
||||||
return $template->getResponse();
|
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 = $this->createForm(SurveyQuestionAnswerType::class, $answerData, ['question' => $question]);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$normalizedAnswer = $this->surveySubmissionService->storeAnswer($survey, $submission, $question, $answerData->answer);
|
$normalizedAnswer = $this->surveySubmissionService->storeAnswer($survey, $submission, $question, $answerData->answer);
|
||||||
$nextQuestion = $this->surveyFlowService->determineNextQuestion($survey, $question, $normalizedAnswer);
|
$nextQuestion = $this->surveyFlowService->determineNextQuestion($survey, $question, $normalizedAnswer);
|
||||||
|
|
||||||
|
if ($nextQuestion instanceof SurveyContentModel) {
|
||||||
|
$this->surveySubmissionService->rememberQuestion($survey, $request->getSession(), $question);
|
||||||
|
}
|
||||||
|
|
||||||
$this->surveySubmissionService->advance($submission, $nextQuestion);
|
$this->surveySubmissionService->advance($submission, $nextQuestion);
|
||||||
|
|
||||||
return new RedirectResponse($request->getUri());
|
return new RedirectResponse($request->getUri());
|
||||||
@@ -107,10 +129,140 @@ final class ShowSurveyController extends AbstractFrontendModuleController
|
|||||||
$template->set('surveyForm', $form->createView());
|
$template->set('surveyForm', $form->createView());
|
||||||
$template->set('isComplete', false);
|
$template->set('isComplete', false);
|
||||||
$template->set('answers', []);
|
$template->set('answers', []);
|
||||||
|
$template->set('isPreview', false);
|
||||||
|
$template->set('canGoBack', $this->surveySubmissionService->canGoBack($survey, $request->getSession()));
|
||||||
|
$template->set('historyState', '');
|
||||||
|
|
||||||
return $template->getResponse();
|
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<int>
|
||||||
|
*/
|
||||||
|
private function decodePreviewHistory(string $historyState): array
|
||||||
|
{
|
||||||
|
if ('' === trim($historyState)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_filter(
|
||||||
|
array_map(static fn (string $entry): int => (int) trim($entry), explode(',', $historyState)),
|
||||||
|
static fn (int $entry): bool => $entry > 0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<int> $history
|
||||||
|
*/
|
||||||
|
private function encodePreviewHistory(array $history): string
|
||||||
|
{
|
||||||
|
return implode(',', $history);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertNavigationTokenValid(Request $request, string $tokenId): void
|
||||||
|
{
|
||||||
|
$token = (string) $request->request->get('_navigation_token', '');
|
||||||
|
|
||||||
|
if (!$this->isCsrfTokenValid($tokenId, $token)) {
|
||||||
|
throw new \RuntimeException('Ungültiger CSRF-Token.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function resolvePublicAlias(Request $request): string
|
private function resolvePublicAlias(Request $request): string
|
||||||
{
|
{
|
||||||
$candidate = $request->attributes->get('auto_item');
|
$candidate = $request->attributes->get('auto_item');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Mummert\SurveyBundle\EventListener;
|
namespace Mummert\SurveyBundle\EventListener;
|
||||||
|
|
||||||
|
use Contao\Backend;
|
||||||
use Contao\Config;
|
use Contao\Config;
|
||||||
use Contao\CoreBundle\Routing\ContentUrlGenerator;
|
use Contao\CoreBundle\Routing\ContentUrlGenerator;
|
||||||
use Contao\CoreBundle\Framework\ContaoFramework;
|
use Contao\CoreBundle\Framework\ContaoFramework;
|
||||||
@@ -11,6 +12,7 @@ use Contao\DataContainer;
|
|||||||
use Contao\Image;
|
use Contao\Image;
|
||||||
use Contao\Input;
|
use Contao\Input;
|
||||||
use Contao\PageModel;
|
use Contao\PageModel;
|
||||||
|
use Contao\StringUtil;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Mummert\SurveyBundle\Repository\SurveyEditorRepository;
|
use Mummert\SurveyBundle\Repository\SurveyEditorRepository;
|
||||||
use Mummert\SurveyBundle\Repository\SurveyQuestionRepository;
|
use Mummert\SurveyBundle\Repository\SurveyQuestionRepository;
|
||||||
@@ -40,12 +42,12 @@ final class SurveyDcaListener
|
|||||||
{
|
{
|
||||||
$publicUrl = $this->buildSurveyPageUrl((string) ($row['alias'] ?? ''), 'surveyReaderPage');
|
$publicUrl = $this->buildSurveyPageUrl((string) ($row['alias'] ?? ''), 'surveyReaderPage');
|
||||||
$label = 'Link kopieren';
|
$label = 'Link kopieren';
|
||||||
$title = 'Den oeffentlichen Link in die Zwischenablage kopieren';
|
$title = 'Den öffentlichen Link in die Zwischenablage kopieren';
|
||||||
|
|
||||||
if (null === $publicUrl) {
|
if (null === $publicUrl) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<span title="%s">%s</span>',
|
'<span title="%s">%s</span>',
|
||||||
htmlspecialchars('In den Contao-Einstellungen ist noch keine Reader-Seite hinterlegt oder der Link-Schluessel ist ungueltig.', ENT_QUOTES),
|
htmlspecialchars('In den Contao-Einstellungen ist noch keine Reader-Seite hinterlegt oder der Link-Schlüssel ist ungültig.', ENT_QUOTES),
|
||||||
Image::getHtml($icon ?? 'copy.svg', $label)
|
Image::getHtml($icon ?? 'copy.svg', $label)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -53,8 +55,8 @@ final class SurveyDcaListener
|
|||||||
$copyScript = sprintf(
|
$copyScript = sprintf(
|
||||||
'navigator.clipboard.writeText(%s).then(function(){Contao.flashMessage(%s, %s, "confirmation");});return false;',
|
'navigator.clipboard.writeText(%s).then(function(){Contao.flashMessage(%s, %s, "confirmation");});return false;',
|
||||||
json_encode($publicUrl, JSON_THROW_ON_ERROR),
|
json_encode($publicUrl, JSON_THROW_ON_ERROR),
|
||||||
json_encode('Oeffentlicher Link wurde in die Zwischenablage kopiert.', JSON_THROW_ON_ERROR),
|
json_encode('Öffentlicher Link wurde in die Zwischenablage kopiert.', JSON_THROW_ON_ERROR),
|
||||||
json_encode('Bestaetigung', JSON_THROW_ON_ERROR),
|
json_encode('Bestätigung', JSON_THROW_ON_ERROR),
|
||||||
);
|
);
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
@@ -75,7 +77,7 @@ final class SurveyDcaListener
|
|||||||
if (null === $resultsUrl) {
|
if (null === $resultsUrl) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<span title="%s">%s</span>',
|
'<span title="%s">%s</span>',
|
||||||
htmlspecialchars('In den Contao-Einstellungen ist noch keine Ergebnisseite hinterlegt oder der Link-Schluessel ist ungueltig.', ENT_QUOTES),
|
htmlspecialchars('In den Contao-Einstellungen ist noch keine Ergebnisseite hinterlegt oder der Link-Schlüssel ist ungültig.', ENT_QUOTES),
|
||||||
Image::getHtml($icon ?? 'show.svg', $label)
|
Image::getHtml($icon ?? 'show.svg', $label)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,6 +91,88 @@ final class SurveyDcaListener
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generatePublishSurveyButton(array $row, ?string $href, string $label, string $title, ?string $icon, string $attributes): string
|
||||||
|
{
|
||||||
|
if ('1' === (string) ($row['published'] ?? '')) {
|
||||||
|
return sprintf(
|
||||||
|
'<span title="%s">%s</span>',
|
||||||
|
htmlspecialchars('Die Umfrage ist bereits veröffentlicht.', ENT_QUOTES),
|
||||||
|
Image::getHtml(str_replace('.svg', '--disabled.svg', $icon ?? 'visible.svg'), $label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s" title="%s" onclick="if(!confirm(\'%s\'))return false;Backend.getScrollOffset()"%s>%s</a>',
|
||||||
|
htmlspecialchars(Backend::addToUrl(($href ?? '').'&id='.(int) ($row['id'] ?? 0)), ENT_QUOTES),
|
||||||
|
htmlspecialchars($title, ENT_QUOTES),
|
||||||
|
StringUtil::specialchars('Wirklich sicher? Nachdem die Umfrage veröffentlicht wurde sind keine Änderungen mehr möglich!'),
|
||||||
|
$attributes,
|
||||||
|
Image::getHtml($icon ?? 'visible.svg', $label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateToggleActiveButton(array $row, ?string $href, string $label, string $title, ?string $icon, string $attributes): string
|
||||||
|
{
|
||||||
|
if ('1' !== (string) ($row['published'] ?? '')) {
|
||||||
|
return sprintf(
|
||||||
|
'<span title="%s">%s</span>',
|
||||||
|
htmlspecialchars('Die Umfrage muss zuerst veröffentlicht werden.', ENT_QUOTES),
|
||||||
|
Image::getHtml('visible--disabled.svg', $label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$isActive = '1' === (string) ($row['isActive'] ?? '');
|
||||||
|
$icon = $isActive ? 'visible.svg' : 'invisible.svg';
|
||||||
|
$title = $isActive ? 'Umfrage vorübergehend deaktivieren' : 'Umfrage wieder aktivieren';
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s" title="%s"%s>%s</a>',
|
||||||
|
htmlspecialchars(Backend::addToUrl(($href ?? '').'&id='.(int) ($row['id'] ?? 0)), ENT_QUOTES),
|
||||||
|
htmlspecialchars($title, ENT_QUOTES),
|
||||||
|
$attributes,
|
||||||
|
Image::getHtml($icon, $label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function savePublishedState(mixed $value, DataContainer $dataContainer): string
|
||||||
|
{
|
||||||
|
$surveyId = (int) ($dataContainer->id ?? 0);
|
||||||
|
$survey = $this->surveyRepository->findById($surveyId);
|
||||||
|
|
||||||
|
if (null === $survey) {
|
||||||
|
return !empty($value) ? '1' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('1' === (string) $survey->published) {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($value)) {
|
||||||
|
$now = time();
|
||||||
|
$this->connection->update('tl_survey', [
|
||||||
|
'isActive' => '1',
|
||||||
|
'isLocked' => '1',
|
||||||
|
'tstamp' => $now,
|
||||||
|
'updatedAt' => $now,
|
||||||
|
], ['id' => $surveyId]);
|
||||||
|
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveActiveState(mixed $value, DataContainer $dataContainer): string
|
||||||
|
{
|
||||||
|
$survey = $this->surveyRepository->findById((int) ($dataContainer->id ?? 0));
|
||||||
|
|
||||||
|
if (null === $survey || '1' !== (string) $survey->published) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return !empty($value) ? '1' : '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
@@ -145,6 +229,50 @@ final class SurveyDcaListener
|
|||||||
return $options;
|
return $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function getUsedCategoryFilterOptions(): array
|
||||||
|
{
|
||||||
|
$usedCategoryIds = [];
|
||||||
|
|
||||||
|
foreach ($this->connection->fetchFirstColumn('SELECT category FROM tl_survey') as $rawCategoryValue) {
|
||||||
|
foreach (StringUtil::deserialize($rawCategoryValue, true) as $categoryId) {
|
||||||
|
$categoryId = (int) $categoryId;
|
||||||
|
|
||||||
|
if ($categoryId > 0) {
|
||||||
|
$usedCategoryIds[$categoryId] = $categoryId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([] === $usedCategoryIds) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$allCategoryOptions = $this->getCategoryOptions();
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
foreach (array_values($usedCategoryIds) as $categoryId) {
|
||||||
|
if (isset($allCategoryOptions[$categoryId])) {
|
||||||
|
$options[$categoryId] = $allCategoryOptions[$categoryId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function getTemplateFilterOptions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'1' => $GLOBALS['TL_LANG']['tl_survey']['template_yes'] ?? 'Ja',
|
||||||
|
'0' => $GLOBALS['TL_LANG']['tl_survey']['template_no'] ?? 'Nein',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
@@ -190,7 +318,6 @@ final class SurveyDcaListener
|
|||||||
public function guardEditorSave(mixed $value, DataContainer $dataContainer): mixed
|
public function guardEditorSave(mixed $value, DataContainer $dataContainer): mixed
|
||||||
{
|
{
|
||||||
$surveyId = $this->resolveSurveyId('tl_survey_editor', $dataContainer);
|
$surveyId = $this->resolveSurveyId('tl_survey_editor', $dataContainer);
|
||||||
$this->assertSurveyUnlocked($surveyId);
|
|
||||||
$this->surveyCategoryAssignmentService->applyMemberCategoriesToSurvey((int) $value, $surveyId);
|
$this->surveyCategoryAssignmentService->applyMemberCategoriesToSurvey((int) $value, $surveyId);
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
@@ -239,10 +366,6 @@ final class SurveyDcaListener
|
|||||||
$previousSurveyIds = $this->surveyEditorRepository->findSurveyIdsByMember($memberId);
|
$previousSurveyIds = $this->surveyEditorRepository->findSurveyIdsByMember($memberId);
|
||||||
|
|
||||||
if ($previousSurveyIds !== $surveyIds) {
|
if ($previousSurveyIds !== $surveyIds) {
|
||||||
foreach (array_unique(array_merge($previousSurveyIds, $surveyIds)) as $surveyId) {
|
|
||||||
$this->assertSurveyUnlocked((int) $surveyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->surveyEditorRepository->syncSurveysForMember($memberId, $surveyIds);
|
$this->surveyEditorRepository->syncSurveysForMember($memberId, $surveyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +396,6 @@ final class SurveyDcaListener
|
|||||||
$previousMemberIds = $this->surveyEditorRepository->findMemberIdsBySurvey($surveyId);
|
$previousMemberIds = $this->surveyEditorRepository->findMemberIdsBySurvey($surveyId);
|
||||||
|
|
||||||
if ($previousMemberIds !== $memberIds) {
|
if ($previousMemberIds !== $memberIds) {
|
||||||
$this->assertSurveyUnlocked($surveyId);
|
|
||||||
$this->surveyEditorRepository->syncMembersForSurvey($surveyId, $memberIds);
|
$this->surveyEditorRepository->syncMembersForSurvey($surveyId, $memberIds);
|
||||||
$this->surveyCategoryAssignmentService->syncSurvey($surveyId);
|
$this->surveyCategoryAssignmentService->syncSurvey($surveyId);
|
||||||
}
|
}
|
||||||
@@ -361,13 +483,36 @@ final class SurveyDcaListener
|
|||||||
public function guardEditorDelete(DataContainer $dataContainer): void
|
public function guardEditorDelete(DataContainer $dataContainer): void
|
||||||
{
|
{
|
||||||
$surveyId = $this->resolveSurveyId('tl_survey_editor', $dataContainer);
|
$surveyId = $this->resolveSurveyId('tl_survey_editor', $dataContainer);
|
||||||
$this->assertSurveyUnlocked($surveyId);
|
|
||||||
|
|
||||||
if ($dataContainer->activeRecord?->member) {
|
if ($dataContainer->activeRecord?->member) {
|
||||||
$this->surveyCategoryAssignmentService->syncSurveyWithoutMember($surveyId, (int) $dataContainer->activeRecord->member);
|
$this->surveyCategoryAssignmentService->syncSurveyWithoutMember($surveyId, (int) $dataContainer->activeRecord->member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function restrictSurveyContentEditing(): void
|
||||||
|
{
|
||||||
|
if (!$this->isSurveyStructureLocked($this->resolveSurveyId('tl_survey_content'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_content']['config']['notCreatable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_content']['config']['notCopyable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_content']['config']['notEditable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_content']['config']['notDeletable'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restrictSurveyConditionEditing(): void
|
||||||
|
{
|
||||||
|
if (!$this->isSurveyStructureLocked($this->resolveSurveyId('tl_survey_condition'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_condition']['config']['notCreatable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_condition']['config']['notCopyable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_condition']['config']['notEditable'] = true;
|
||||||
|
$GLOBALS['TL_DCA']['tl_survey_condition']['config']['notDeletable'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
public function guardSurveyStructureSave(mixed $value, DataContainer $dataContainer): mixed
|
public function guardSurveyStructureSave(mixed $value, DataContainer $dataContainer): mixed
|
||||||
{
|
{
|
||||||
$surveyId = (int) ($dataContainer->id ?? 0);
|
$surveyId = (int) ($dataContainer->id ?? 0);
|
||||||
@@ -395,15 +540,24 @@ final class SurveyDcaListener
|
|||||||
|
|
||||||
private function assertSurveyUnlocked(int $surveyId): void
|
private function assertSurveyUnlocked(int $surveyId): void
|
||||||
{
|
{
|
||||||
|
if ($this->isSurveyStructureLocked($surveyId)) {
|
||||||
|
throw new \RuntimeException('Die Fragen dieser Umfrage können nach der Veröffentlichung oder nach ersten Teilnahmen nicht mehr geändert werden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isSurveyStructureLocked(int $surveyId): bool
|
||||||
|
{
|
||||||
|
if ($surveyId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$survey = $this->surveyRepository->findById($surveyId);
|
$survey = $this->surveyRepository->findById($surveyId);
|
||||||
|
|
||||||
if (null === $survey) {
|
if (null === $survey) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('1' === (string) $survey->isLocked) {
|
return '1' === (string) $survey->published || '1' === (string) $survey->isLocked;
|
||||||
throw new \RuntimeException('Die Umfrage ist gesperrt, weil bereits Antworten existieren.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveSurveyId(string $table, ?DataContainer $dataContainer = null): int
|
private function resolveSurveyId(string $table, ?DataContainer $dataContainer = null): int
|
||||||
@@ -430,6 +584,10 @@ final class SurveyDcaListener
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($table === (string) Input::get('table') && '' === (string) Input::get('act')) {
|
||||||
|
return $recordId;
|
||||||
|
}
|
||||||
|
|
||||||
if ('survey' === $table) {
|
if ('survey' === $table) {
|
||||||
$value = $this->connection->fetchOne(sprintf('SELECT %s FROM %s WHERE id = ?', 'id', $table), [$recordId]);
|
$value = $this->connection->fetchOne(sprintf('SELECT %s FROM %s WHERE id = ?', 'id', $table), [$recordId]);
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ final class SurveyConditionEditorType extends AbstractType
|
|||||||
'constraints' => [new NotBlank(), new Length(['max' => 255])],
|
'constraints' => [new NotBlank(), new Length(['max' => 255])],
|
||||||
])
|
])
|
||||||
->add('targetQuestion', ChoiceType::class, [
|
->add('targetQuestion', ChoiceType::class, [
|
||||||
'label' => 'Naechste Frage',
|
'label' => 'Nächste Frage',
|
||||||
'choices' => array_flip($questionChoices),
|
'choices' => array_flip($questionChoices),
|
||||||
'constraints' => [new NotBlank()],
|
'constraints' => [new NotBlank()],
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ final class SurveyEditorType extends AbstractType
|
|||||||
'attr' => ['rows' => 5],
|
'attr' => ['rows' => 5],
|
||||||
])
|
])
|
||||||
->add('published', CheckboxType::class, [
|
->add('published', CheckboxType::class, [
|
||||||
'label' => 'Veroeffentlicht',
|
'label' => 'Veröffentlicht',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ final class SurveyQuestionAnswerType extends AbstractType
|
|||||||
$question = $options['question'];
|
$question = $options['question'];
|
||||||
|
|
||||||
if (!$question instanceof SurveyContentModel) {
|
if (!$question instanceof SurveyContentModel) {
|
||||||
throw new \InvalidArgumentException('Die Frage fehlt fuer das Antwortformular.');
|
throw new \InvalidArgumentException('Die Frage fehlt für das Antwortformular.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->questionTypeRegistry->get((string) $question->type)->buildField($builder, $question);
|
$this->questionTypeRegistry->get((string) $question->type)->buildField($builder, $question);
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ final class SurveyQuestionEditorType extends AbstractType
|
|||||||
'attr' => ['data-question-editor-target' => 'rangeField'],
|
'attr' => ['data-question-editor-target' => 'rangeField'],
|
||||||
])
|
])
|
||||||
->add('rangeMax', IntegerType::class, [
|
->add('rangeMax', IntegerType::class, [
|
||||||
'label' => 'Groesster Wert',
|
'label' => 'Größter Wert',
|
||||||
'empty_data' => '10',
|
'empty_data' => '10',
|
||||||
'constraints' => [new GreaterThanOrEqual(['value' => -100000])],
|
'constraints' => [new GreaterThanOrEqual(['value' => -100000])],
|
||||||
'attr' => ['data-question-editor-target' => 'rangeField'],
|
'attr' => ['data-question-editor-target' => 'rangeField'],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Mummert\SurveyBundle\Repository;
|
namespace Mummert\SurveyBundle\Repository;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\ArrayParameterType;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
final class SurveyAnswerRepository
|
final class SurveyAnswerRepository
|
||||||
@@ -12,7 +13,15 @@ final class SurveyAnswerRepository
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveAnswer(int $submissionId, int $questionId, string $questionType, string $value): void
|
public function saveAnswer(
|
||||||
|
int $submissionId,
|
||||||
|
int $questionId,
|
||||||
|
string $questionType,
|
||||||
|
string $questionLabel,
|
||||||
|
string $questionDescription,
|
||||||
|
int $questionSorting,
|
||||||
|
string $value,
|
||||||
|
): void
|
||||||
{
|
{
|
||||||
$existingId = $this->connection->fetchOne(
|
$existingId = $this->connection->fetchOne(
|
||||||
'SELECT id FROM tl_survey_answer WHERE submission = ? AND question = ? LIMIT 1',
|
'SELECT id FROM tl_survey_answer WHERE submission = ? AND question = ? LIMIT 1',
|
||||||
@@ -22,6 +31,9 @@ final class SurveyAnswerRepository
|
|||||||
if (false !== $existingId) {
|
if (false !== $existingId) {
|
||||||
$this->connection->update('tl_survey_answer', [
|
$this->connection->update('tl_survey_answer', [
|
||||||
'questionType' => $questionType,
|
'questionType' => $questionType,
|
||||||
|
'questionLabel' => $questionLabel,
|
||||||
|
'questionDescription' => $questionDescription,
|
||||||
|
'questionSorting' => $questionSorting,
|
||||||
'value' => $value,
|
'value' => $value,
|
||||||
], [
|
], [
|
||||||
'id' => (int) $existingId,
|
'id' => (int) $existingId,
|
||||||
@@ -35,6 +47,9 @@ final class SurveyAnswerRepository
|
|||||||
'submission' => $submissionId,
|
'submission' => $submissionId,
|
||||||
'question' => $questionId,
|
'question' => $questionId,
|
||||||
'questionType' => $questionType,
|
'questionType' => $questionType,
|
||||||
|
'questionLabel' => $questionLabel,
|
||||||
|
'questionDescription' => $questionDescription,
|
||||||
|
'questionSorting' => $questionSorting,
|
||||||
'value' => $value,
|
'value' => $value,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -53,8 +68,36 @@ final class SurveyAnswerRepository
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findStoredAnswerValue(int $submissionId, int $questionId): ?string
|
||||||
|
{
|
||||||
|
$value = $this->connection->fetchOne(
|
||||||
|
'SELECT value FROM tl_survey_answer WHERE submission = ? AND question = ? LIMIT 1',
|
||||||
|
[$submissionId, $questionId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return false === $value ? null : (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array{questionId:int,value:string}>
|
* @param list<int> $questionIds
|
||||||
|
*/
|
||||||
|
public function deleteAnswersForSubmissionExceptQuestions(int $submissionId, array $questionIds): void
|
||||||
|
{
|
||||||
|
if ([] === $questionIds) {
|
||||||
|
$this->connection->delete('tl_survey_answer', ['submission' => $submissionId]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'DELETE FROM tl_survey_answer WHERE submission = ? AND question NOT IN (?)',
|
||||||
|
[$submissionId, array_values(array_unique(array_map('intval', $questionIds)))],
|
||||||
|
[\PDO::PARAM_INT, ArrayParameterType::INTEGER],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{questionId:int,questionType:string,questionLabel:string,questionDescription:string,questionSorting:int,value:string}>
|
||||||
*/
|
*/
|
||||||
public function findFinishedAnswersBySurvey(int $surveyId): array
|
public function findFinishedAnswersBySurvey(int $surveyId): array
|
||||||
{
|
{
|
||||||
@@ -62,11 +105,15 @@ final class SurveyAnswerRepository
|
|||||||
<<<'SQL'
|
<<<'SQL'
|
||||||
SELECT
|
SELECT
|
||||||
a.question AS questionId,
|
a.question AS questionId,
|
||||||
|
a.questionType,
|
||||||
|
a.questionLabel,
|
||||||
|
a.questionDescription,
|
||||||
|
a.questionSorting,
|
||||||
a.value
|
a.value
|
||||||
FROM tl_survey_answer a
|
FROM tl_survey_answer a
|
||||||
INNER JOIN tl_survey_submission sub ON sub.id = a.submission
|
INNER JOIN tl_survey_submission sub ON sub.id = a.submission
|
||||||
WHERE sub.survey = :survey AND sub.isFinished = 1
|
WHERE sub.survey = :survey AND sub.isFinished = 1
|
||||||
ORDER BY a.question ASC, a.id ASC
|
ORDER BY a.questionSorting ASC, a.question ASC, a.id ASC
|
||||||
SQL,
|
SQL,
|
||||||
['survey' => $surveyId],
|
['survey' => $surveyId],
|
||||||
);
|
);
|
||||||
@@ -74,6 +121,10 @@ final class SurveyAnswerRepository
|
|||||||
return array_map(
|
return array_map(
|
||||||
static fn (array $row): array => [
|
static fn (array $row): array => [
|
||||||
'questionId' => (int) $row['questionId'],
|
'questionId' => (int) $row['questionId'],
|
||||||
|
'questionType' => (string) ($row['questionType'] ?? ''),
|
||||||
|
'questionLabel' => (string) ($row['questionLabel'] ?? ''),
|
||||||
|
'questionDescription' => (string) ($row['questionDescription'] ?? ''),
|
||||||
|
'questionSorting' => (int) ($row['questionSorting'] ?? 0),
|
||||||
'value' => (string) $row['value'],
|
'value' => (string) $row['value'],
|
||||||
],
|
],
|
||||||
$rows,
|
$rows,
|
||||||
@@ -92,11 +143,12 @@ final class SurveyAnswerRepository
|
|||||||
a.question AS questionId,
|
a.question AS questionId,
|
||||||
a.value,
|
a.value,
|
||||||
a.questionType,
|
a.questionType,
|
||||||
q.question
|
COALESCE(NULLIF(a.questionLabel, ''), q.question, CONCAT('Frage #', a.question)) AS question,
|
||||||
|
COALESCE(a.questionDescription, q.description, '') AS questionDescription
|
||||||
FROM tl_survey_answer a
|
FROM tl_survey_answer a
|
||||||
INNER JOIN tl_survey_content q ON q.id = a.question
|
LEFT JOIN tl_survey_content q ON q.id = a.question
|
||||||
WHERE a.submission = :submission
|
WHERE a.submission = :submission
|
||||||
ORDER BY q.sorting ASC
|
ORDER BY COALESCE(NULLIF(a.questionSorting, 0), q.sorting, 0) ASC, a.id ASC
|
||||||
SQL,
|
SQL,
|
||||||
['submission' => $submissionId],
|
['submission' => $submissionId],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ final class SurveyRepository
|
|||||||
|
|
||||||
if ($publishedOnly) {
|
if ($publishedOnly) {
|
||||||
$qb->andWhere('published = 1');
|
$qb->andWhere('published = 1');
|
||||||
|
$qb->andWhere('isActive = 1');
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $qb->executeQuery()->fetchOne();
|
$id = $qb->executeQuery()->fetchOne();
|
||||||
@@ -65,6 +66,7 @@ final class SurveyRepository
|
|||||||
s.alias,
|
s.alias,
|
||||||
s.description,
|
s.description,
|
||||||
s.published,
|
s.published,
|
||||||
|
s.isActive,
|
||||||
s.isLocked,
|
s.isLocked,
|
||||||
s.updatedAt,
|
s.updatedAt,
|
||||||
COUNT(DISTINCT q.id) AS questionCount,
|
COUNT(DISTINCT q.id) AS questionCount,
|
||||||
@@ -92,6 +94,7 @@ final class SurveyRepository
|
|||||||
'category' => serialize($this->normalizeCategoryIds($data['category'] ?? [])),
|
'category' => serialize($this->normalizeCategoryIds($data['category'] ?? [])),
|
||||||
'description' => (string) ($data['description'] ?? ''),
|
'description' => (string) ($data['description'] ?? ''),
|
||||||
'published' => '',
|
'published' => '',
|
||||||
|
'isActive' => '',
|
||||||
'isLocked' => '',
|
'isLocked' => '',
|
||||||
'createdBy' => $memberId,
|
'createdBy' => $memberId,
|
||||||
'updatedBy' => $memberId,
|
'updatedBy' => $memberId,
|
||||||
@@ -115,7 +118,8 @@ final class SurveyRepository
|
|||||||
'alias' => $this->ensurePublicAlias((string) $survey->alias, (int) $survey->id),
|
'alias' => $this->ensurePublicAlias((string) $survey->alias, (int) $survey->id),
|
||||||
'category' => serialize($this->normalizeCategoryIds($data['category'] ?? StringUtil::deserialize($survey->category, true))),
|
'category' => serialize($this->normalizeCategoryIds($data['category'] ?? StringUtil::deserialize($survey->category, true))),
|
||||||
'description' => (string) ($data['description'] ?? $survey->description),
|
'description' => (string) ($data['description'] ?? $survey->description),
|
||||||
'published' => !empty($data['published']) ? '1' : '',
|
'published' => array_key_exists('published', $data) ? (!empty($data['published']) ? '1' : '') : (string) $survey->published,
|
||||||
|
'isActive' => array_key_exists('isActive', $data) ? (!empty($data['isActive']) ? '1' : '') : (string) ($survey->isActive ?? ''),
|
||||||
'updatedBy' => $memberId,
|
'updatedBy' => $memberId,
|
||||||
'updatedAt' => time(),
|
'updatedAt' => time(),
|
||||||
], [
|
], [
|
||||||
@@ -182,6 +186,8 @@ final class SurveyRepository
|
|||||||
$this->connection->update('tl_survey', [
|
$this->connection->update('tl_survey', [
|
||||||
'categoryFilter' => implode(',', $categoryIds),
|
'categoryFilter' => implode(',', $categoryIds),
|
||||||
'categorySummary' => implode(', ', $categoryTitles),
|
'categorySummary' => implode(', ', $categoryTitles),
|
||||||
|
'templateFilter' => '1' === (string) $survey->isTemplate ? '1' : '0',
|
||||||
|
'templateSummary' => '1' === (string) $survey->isTemplate ? 'Ja' : 'Nein',
|
||||||
'assignedMembersFilter' => implode(',', $memberIds),
|
'assignedMembersFilter' => implode(',', $memberIds),
|
||||||
'assignedMembersSummary' => implode(', ', $memberNames),
|
'assignedMembersSummary' => implode(', ', $memberNames),
|
||||||
], [
|
], [
|
||||||
@@ -212,6 +218,36 @@ final class SurveyRepository
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function publish(int $surveyId, int $memberId): void
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
$this->connection->update('tl_survey', [
|
||||||
|
'published' => '1',
|
||||||
|
'isActive' => '1',
|
||||||
|
'isLocked' => '1',
|
||||||
|
'tstamp' => $now,
|
||||||
|
'updatedBy' => $memberId,
|
||||||
|
'updatedAt' => $now,
|
||||||
|
], [
|
||||||
|
'id' => $surveyId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setActive(int $surveyId, int $memberId, bool $isActive): void
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
$this->connection->update('tl_survey', [
|
||||||
|
'isActive' => $isActive ? '1' : '',
|
||||||
|
'tstamp' => $now,
|
||||||
|
'updatedBy' => $memberId,
|
||||||
|
'updatedAt' => $now,
|
||||||
|
], [
|
||||||
|
'id' => $surveyId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function deleteCascade(int $surveyId): void
|
public function deleteCascade(int $surveyId): void
|
||||||
{
|
{
|
||||||
$this->connection->transactional(function () use ($surveyId): void {
|
$this->connection->transactional(function () use ($surveyId): void {
|
||||||
@@ -347,7 +383,7 @@ final class SurveyRepository
|
|||||||
);
|
);
|
||||||
|
|
||||||
return $this->hasListMetadataColumns = !array_diff(
|
return $this->hasListMetadataColumns = !array_diff(
|
||||||
['categoryfilter', 'categorysummary', 'assignedmembersfilter', 'assignedmemberssummary'],
|
['categoryfilter', 'categorysummary', 'templatefilter', 'templatesummary', 'assignedmembersfilter', 'assignedmemberssummary'],
|
||||||
$columnNames,
|
$columnNames,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,17 @@ final class SurveySubmissionRepository
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reopen(int $submissionId, int $questionId): void
|
||||||
|
{
|
||||||
|
$this->connection->update('tl_survey_submission', [
|
||||||
|
'isFinished' => '',
|
||||||
|
'completedAt' => 0,
|
||||||
|
'currentQuestion' => $questionId,
|
||||||
|
], [
|
||||||
|
'id' => $submissionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<array<string, mixed>>
|
* @return list<array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -41,14 +41,30 @@ final class SurveyEditorService
|
|||||||
$this->surveyRepository->update($survey, $memberId, $data->toArray());
|
$this->surveyRepository->update($survey, $memberId, $data->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggleSurveyPublished(SurveyModel $survey, int $memberId): bool
|
public function publishSurvey(SurveyModel $survey, int $memberId): void
|
||||||
{
|
{
|
||||||
$data = SurveyEditorData::fromModel($survey);
|
if ('1' === (string) $survey->published) {
|
||||||
$data->published = !$data->published;
|
throw new \RuntimeException('Die Umfrage ist bereits veröffentlicht.');
|
||||||
|
}
|
||||||
|
|
||||||
$this->surveyRepository->update($survey, $memberId, $data->toArray());
|
$this->surveyRepository->publish((int) $survey->id, $memberId);
|
||||||
|
}
|
||||||
|
|
||||||
return $data->published;
|
public function toggleSurveyActive(SurveyModel $survey, int $memberId): bool
|
||||||
|
{
|
||||||
|
if ('1' !== (string) $survey->published) {
|
||||||
|
throw new \RuntimeException('Die Umfrage muss zuerst veröffentlicht werden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$isActive = '1' !== (string) ($survey->isActive ?? '');
|
||||||
|
$this->surveyRepository->setActive((int) $survey->id, $memberId, $isActive);
|
||||||
|
|
||||||
|
return $isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isStructureLocked(SurveyModel $survey): bool
|
||||||
|
{
|
||||||
|
return '1' === (string) $survey->published || '1' === (string) $survey->isLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveQuestion(SurveyModel $survey, SurveyQuestionData $data, int $questionId = 0): int
|
public function saveQuestion(SurveyModel $survey, SurveyQuestionData $data, int $questionId = 0): int
|
||||||
@@ -117,8 +133,8 @@ final class SurveyEditorService
|
|||||||
|
|
||||||
private function assertUnlocked(SurveyModel $survey): void
|
private function assertUnlocked(SurveyModel $survey): void
|
||||||
{
|
{
|
||||||
if ('1' === (string) $survey->isLocked) {
|
if ($this->isStructureLocked($survey)) {
|
||||||
throw new \RuntimeException('Die Umfrage ist gesperrt, weil bereits Antworten existieren.');
|
throw new \RuntimeException('Die Fragen dieser Umfrage können nach der Veröffentlichung oder nach ersten Teilnahmen nicht mehr geändert werden.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,28 @@ final class SurveyFlowService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFirstQuestion(SurveyModel $survey): ?SurveyContentModel
|
||||||
|
{
|
||||||
|
$questions = $this->getOrderedQuestions($survey);
|
||||||
|
|
||||||
|
return $questions[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveQuestionById(SurveyModel $survey, int $questionId): ?SurveyContentModel
|
||||||
|
{
|
||||||
|
if ($questionId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->getOrderedQuestions($survey) as $question) {
|
||||||
|
if ((int) $question->id === $questionId) {
|
||||||
|
return $question;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{current:int,total:int,percentage:int}
|
* @return array{current:int,total:int,percentage:int}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ final class SurveyPermissionService
|
|||||||
public function assertEditorAccess(SurveyModel $survey, FrontendUser $member): void
|
public function assertEditorAccess(SurveyModel $survey, FrontendUser $member): void
|
||||||
{
|
{
|
||||||
if (!$this->canEditSurvey($survey, $member)) {
|
if (!$this->canEditSurvey($survey, $member)) {
|
||||||
throw new AccessDeniedHttpException('Sie duerfen diese Umfrage nicht bearbeiten.');
|
throw new AccessDeniedHttpException('Sie dürfen diese Umfrage nicht bearbeiten.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,47 +41,111 @@ final class SurveyResultsViewService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<SurveyContentModel> $questions
|
* @param list<SurveyContentModel> $questions
|
||||||
* @param list<array{questionId:int,value:string}> $answers
|
* @param list<array{questionId:int,questionType:string,questionLabel:string,questionDescription:string,questionSorting:int,value:string}> $answers
|
||||||
*
|
*
|
||||||
* @return list<array<string, mixed>>
|
* @return list<array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
private function buildQuestionResults(array $questions, array $answers, int $completedSubmissionCount): array
|
private function buildQuestionResults(array $questions, array $answers, int $completedSubmissionCount): array
|
||||||
{
|
{
|
||||||
$answersByQuestion = [];
|
|
||||||
|
|
||||||
foreach ($answers as $answer) {
|
|
||||||
$answersByQuestion[$answer['questionId']][] = $answer['value'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
foreach ($questions as $question) {
|
foreach ($questions as $question) {
|
||||||
$questionId = (int) $question->id;
|
$key = $this->buildQuestionResultKey(
|
||||||
$values = $answersByQuestion[$questionId] ?? [];
|
(int) $question->id,
|
||||||
$totalAnswers = count($values);
|
(string) $question->type,
|
||||||
$responseRate = $completedSubmissionCount > 0 ? (int) round(($totalAnswers / $completedSubmissionCount) * 100) : 0;
|
(string) $question->question,
|
||||||
|
(string) ($question->description ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
$result = [
|
$results[$key] = $this->buildResultSkeleton(
|
||||||
'id' => $questionId,
|
(int) $question->id,
|
||||||
'question' => (string) $question->question,
|
(string) $question->type,
|
||||||
'description' => (string) ($question->description ?? ''),
|
(string) $question->question,
|
||||||
'type' => (string) $question->type,
|
(string) ($question->description ?? ''),
|
||||||
'totalAnswers' => $totalAnswers,
|
(int) ($question->sorting ?? 0),
|
||||||
'responseRate' => $responseRate,
|
);
|
||||||
'chart' => null,
|
$results[$key]['_choiceOptions'] = $this->getChoiceOptions($question);
|
||||||
];
|
|
||||||
|
|
||||||
switch ((string) $question->type) {
|
|
||||||
case 'yes_no_maybe':
|
|
||||||
$optionCounts = [
|
|
||||||
'Ja' => 0,
|
|
||||||
'Nein' => 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ('1' === (string) $question->allowMaybe) {
|
|
||||||
$optionCounts['Vielleicht'] = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($answers as $answer) {
|
||||||
|
$questionId = (int) ($answer['questionId'] ?? 0);
|
||||||
|
$questionType = (string) ($answer['questionType'] ?? 'text');
|
||||||
|
$questionLabel = trim((string) ($answer['questionLabel'] ?? '')) ?: 'Frage #'.$questionId;
|
||||||
|
$questionDescription = (string) ($answer['questionDescription'] ?? '');
|
||||||
|
$key = $this->buildQuestionResultKey($questionId, $questionType, $questionLabel, $questionDescription);
|
||||||
|
|
||||||
|
if (!isset($results[$key])) {
|
||||||
|
$results[$key] = $this->buildResultSkeleton(
|
||||||
|
$questionId,
|
||||||
|
$questionType,
|
||||||
|
$questionLabel,
|
||||||
|
$questionDescription,
|
||||||
|
(int) ($answer['questionSorting'] ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[$key]['_values'][] = (string) ($answer['value'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($results as $key => $result) {
|
||||||
|
$results[$key] = $this->finalizeQuestionResult($result, $completedSubmissionCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
uasort($results, static function (array $left, array $right): int {
|
||||||
|
$sortingComparison = ((int) ($left['_sorting'] ?? 0)) <=> ((int) ($right['_sorting'] ?? 0));
|
||||||
|
|
||||||
|
if (0 !== $sortingComparison) {
|
||||||
|
return $sortingComparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcasecmp((string) ($left['question'] ?? ''), (string) ($right['question'] ?? ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
return array_values($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQuestionResultKey(int $questionId, string $questionType, string $questionLabel, string $questionDescription): string
|
||||||
|
{
|
||||||
|
return implode('|', [$questionId, $questionType, md5($questionLabel), md5($questionDescription)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function buildResultSkeleton(int $questionId, string $questionType, string $questionLabel, string $questionDescription, int $sorting): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $questionId,
|
||||||
|
'question' => $questionLabel,
|
||||||
|
'description' => $questionDescription,
|
||||||
|
'type' => $questionType,
|
||||||
|
'totalAnswers' => 0,
|
||||||
|
'responseRate' => 0,
|
||||||
|
'chart' => null,
|
||||||
|
'_sorting' => $sorting,
|
||||||
|
'_values' => [],
|
||||||
|
'_choiceOptions' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $result
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function finalizeQuestionResult(array $result, int $completedSubmissionCount): array
|
||||||
|
{
|
||||||
|
$values = $result['_values'] ?? [];
|
||||||
|
$values = \is_array($values) ? array_values(array_map('strval', $values)) : [];
|
||||||
|
$totalAnswers = count($values);
|
||||||
|
|
||||||
|
$result['totalAnswers'] = $totalAnswers;
|
||||||
|
$result['responseRate'] = $completedSubmissionCount > 0 ? (int) round(($totalAnswers / $completedSubmissionCount) * 100) : 0;
|
||||||
|
|
||||||
|
switch ((string) ($result['type'] ?? 'text')) {
|
||||||
|
case 'yes_no_maybe':
|
||||||
|
$optionCounts = ['Ja' => 0, 'Nein' => 0];
|
||||||
|
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
$normalizedValue = mb_strtolower(trim($value));
|
$normalizedValue = mb_strtolower(trim($value));
|
||||||
|
|
||||||
@@ -89,8 +153,8 @@ final class SurveyResultsViewService
|
|||||||
++$optionCounts['Ja'];
|
++$optionCounts['Ja'];
|
||||||
} elseif ('no' === $normalizedValue) {
|
} elseif ('no' === $normalizedValue) {
|
||||||
++$optionCounts['Nein'];
|
++$optionCounts['Nein'];
|
||||||
} elseif ('maybe' === $normalizedValue && isset($optionCounts['Vielleicht'])) {
|
} elseif ('maybe' === $normalizedValue) {
|
||||||
++$optionCounts['Vielleicht'];
|
$optionCounts['Vielleicht'] = ($optionCounts['Vielleicht'] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,21 +165,13 @@ final class SurveyResultsViewService
|
|||||||
case 'choice':
|
case 'choice':
|
||||||
$optionCounts = [];
|
$optionCounts = [];
|
||||||
|
|
||||||
for ($index = 1; $index <= 10; ++$index) {
|
foreach (($result['_choiceOptions'] ?? []) as $choiceOption) {
|
||||||
$option = trim((string) $question->{'answerOption'.$index});
|
$optionCounts[(string) $choiceOption] = 0;
|
||||||
|
|
||||||
if ('' !== $option) {
|
|
||||||
$optionCounts[$option] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
foreach (array_filter(array_map('trim', explode(' | ', $value)), static fn (string $entry): bool => '' !== $entry) as $selectedOption) {
|
foreach (array_filter(array_map('trim', explode(' | ', $value)), static fn (string $entry): bool => '' !== $entry) as $selectedOption) {
|
||||||
if (!array_key_exists($selectedOption, $optionCounts)) {
|
$optionCounts[$selectedOption] = ($optionCounts[$selectedOption] ?? 0) + 1;
|
||||||
$optionCounts[$selectedOption] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
++$optionCounts[$selectedOption];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,14 +201,31 @@ final class SurveyResultsViewService
|
|||||||
default:
|
default:
|
||||||
$result['responses'] = array_values(array_filter($values, static fn (string $value): bool => '' !== trim($value)));
|
$result['responses'] = array_values(array_filter($values, static fn (string $value): bool => '' !== trim($value)));
|
||||||
$result['responseSummary'] = $this->buildTextResponseSummary($result['responses']);
|
$result['responseSummary'] = $this->buildTextResponseSummary($result['responses']);
|
||||||
$result['chart'] = $this->buildPieChartConfig($result['responseSummary']);
|
$result['chart'] = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$results[] = $result;
|
unset($result['_sorting'], $result['_values'], $result['_choiceOptions']);
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $results;
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function getChoiceOptions(SurveyContentModel $question): array
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
for ($index = 1; $index <= 10; ++$index) {
|
||||||
|
$option = trim((string) $question->{'answerOption'.$index});
|
||||||
|
|
||||||
|
if ('' !== $option) {
|
||||||
|
$options[] = $option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Mummert\SurveyBundle\Service;
|
namespace Mummert\SurveyBundle\Service;
|
||||||
|
|
||||||
use Mummert\SurveyBundle\Model\SurveyContentModel;
|
use Mummert\SurveyBundle\Model\SurveyContentModel;
|
||||||
|
use Mummert\SurveyBundle\Form\Model\SurveyAnswerData;
|
||||||
use Mummert\SurveyBundle\Model\SurveyModel;
|
use Mummert\SurveyBundle\Model\SurveyModel;
|
||||||
use Mummert\SurveyBundle\Model\SurveySubmissionModel;
|
use Mummert\SurveyBundle\Model\SurveySubmissionModel;
|
||||||
use Mummert\SurveyBundle\QuestionType\QuestionTypeRegistry;
|
use Mummert\SurveyBundle\QuestionType\QuestionTypeRegistry;
|
||||||
@@ -42,20 +43,23 @@ final class SurveySubmissionService
|
|||||||
$token = (string) new Ulid();
|
$token = (string) new Ulid();
|
||||||
$submission = $this->surveySubmissionRepository->create((int) $survey->id, $token, $firstQuestion?->id ? (int) $firstQuestion->id : null);
|
$submission = $this->surveySubmissionRepository->create((int) $survey->id, $token, $firstQuestion?->id ? (int) $firstQuestion->id : null);
|
||||||
$session->set($sessionKey, $token);
|
$session->set($sessionKey, $token);
|
||||||
|
$session->set($this->buildHistoryKey((int) $survey->id), []);
|
||||||
|
|
||||||
return $submission;
|
return $submission;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeAnswer(SurveyModel $survey, SurveySubmissionModel $submission, SurveyContentModel $question, mixed $rawAnswer): string
|
public function storeAnswer(SurveyModel $survey, SurveySubmissionModel $submission, SurveyContentModel $question, mixed $rawAnswer): string
|
||||||
{
|
{
|
||||||
$questionType = $this->questionTypeRegistry->get((string) $question->type);
|
$normalizedAnswer = $this->normalizeAnswerForQuestion($question, $rawAnswer);
|
||||||
$normalizedAnswer = $questionType->normalizeAnswer($rawAnswer, $question);
|
|
||||||
$surveyHadAnswers = $this->surveyAnswerRepository->hasAnswersForSurvey((int) $survey->id);
|
$surveyHadAnswers = $this->surveyAnswerRepository->hasAnswersForSurvey((int) $survey->id);
|
||||||
|
|
||||||
$this->surveyAnswerRepository->saveAnswer(
|
$this->surveyAnswerRepository->saveAnswer(
|
||||||
(int) $submission->id,
|
(int) $submission->id,
|
||||||
(int) $question->id,
|
(int) $question->id,
|
||||||
(string) $question->type,
|
(string) $question->type,
|
||||||
|
trim((string) $question->question),
|
||||||
|
trim((string) ($question->description ?? '')),
|
||||||
|
(int) ($question->sorting ?? 0),
|
||||||
$normalizedAnswer,
|
$normalizedAnswer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -66,6 +70,73 @@ final class SurveySubmissionService
|
|||||||
return $normalizedAnswer;
|
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
|
public function advance(SurveySubmissionModel $submission, ?SurveyContentModel $nextQuestion): void
|
||||||
{
|
{
|
||||||
if ($nextQuestion instanceof SurveyContentModel) {
|
if ($nextQuestion instanceof SurveyContentModel) {
|
||||||
@@ -100,6 +171,25 @@ final class SurveySubmissionService
|
|||||||
return 'mummert_survey_submission_'.$surveyId;
|
return 'mummert_survey_submission_'.$surveyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildHistoryKey(int $surveyId): string
|
||||||
|
{
|
||||||
|
return 'mummert_survey_history_'.$surveyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<int>
|
||||||
|
*/
|
||||||
|
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<string, mixed> $answer
|
* @param array<string, mixed> $answer
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ survey:
|
|||||||
login_required: "Dieser Bereich verwendet den normalen Contao-Mitgliederlogin. Bitte binden Sie das Modul auf einer geschützten Seite oder mit einer separaten Login-Seite ein."
|
login_required: "Dieser Bereich verwendet den normalen Contao-Mitgliederlogin. Bitte binden Sie das Modul auf einer geschützten Seite oder mit einer separaten Login-Seite ein."
|
||||||
create: "Neue Umfrage anlegen"
|
create: "Neue Umfrage anlegen"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
delete_confirm: "Umfrage wirklich löschen? Damit werden auch alle Fragen, Antworten und Ergebnisse endgültig gelöscht. Alternativ können Sie die Umfrage auch einfach auf nicht veröffentlicht stellen."
|
delete_confirm: "Umfrage wirklich löschen? Damit werden auch alle Fragen, Antworten und Ergebnisse endgültig gelöscht. Alternativ können Sie die Umfrage auch einfach vorübergehend deaktivieren."
|
||||||
delete_confirm_second: "Wirklich sicher?"
|
delete_confirm_second: "Wirklich sicher?"
|
||||||
published: "veröffentlicht"
|
published: "veröffentlicht"
|
||||||
draft: "Entwurf"
|
draft: "Entwurf"
|
||||||
locked: "gesperrt"
|
locked: "gesperrt"
|
||||||
|
active: "aktiv"
|
||||||
|
inactive: "deaktiviert"
|
||||||
questions: "%count% Fragen"
|
questions: "%count% Fragen"
|
||||||
participations: "%count% Teilnahmen"
|
participations: "%count% Teilnahmen"
|
||||||
edit: "Bearbeiten"
|
edit: "Bearbeiten"
|
||||||
public_view: "Öffentliche Ansicht"
|
public_view: "Öffentliche Ansicht"
|
||||||
|
draft_view: "Entwurfs-Ansicht"
|
||||||
show_results: "Ergebnisse anzeigen"
|
show_results: "Ergebnisse anzeigen"
|
||||||
empty: "Dem eingeloggten Mitglied ist aktuell keine Umfrage zugewiesen."
|
empty: "Dem eingeloggten Mitglied ist aktuell keine Umfrage zugewiesen."
|
||||||
edit:
|
edit:
|
||||||
@@ -36,9 +39,16 @@ survey:
|
|||||||
published: "veröffentlicht"
|
published: "veröffentlicht"
|
||||||
draft: "Entwurf"
|
draft: "Entwurf"
|
||||||
locked: "gesperrt"
|
locked: "gesperrt"
|
||||||
structure_locked: "Die Umfrage ist strukturell gesperrt, weil bereits Antworten vorliegen. Stammdaten können weiter gepflegt werden, der Ablauf und die Fragen jedoch nicht."
|
structure_locked: "Die Fragen dieser Umfrage sind gesperrt, weil die Umfrage veröffentlicht wurde oder bereits erste Teilnahmen vorliegen. Die Metadaten können weiter gepflegt werden."
|
||||||
|
published_readonly: "Diese Umfrage ist veröffentlicht. Die Bearbeitung im Frontend ist deshalb deaktiviert. Änderungen sind weiterhin im Backend möglich."
|
||||||
metadata: "Umfrage-Metadaten"
|
metadata: "Umfrage-Metadaten"
|
||||||
publish_survey: "Umfrage veröffentlichen"
|
publish_survey: "Umfrage veröffentlichen"
|
||||||
|
publish_help: "Umfrage veröffentlichen. Danach sind keine Änderungen mehr möglich."
|
||||||
|
publish_confirm: "Sind Sie wirklich sicher? Nachdem die Umfrage veröffentlicht wurde sind keine Änderungen mehr möglich!"
|
||||||
|
deactivate_survey: "Umfrage vorübergehend deaktivieren"
|
||||||
|
activate_survey: "Umfrage wieder aktivieren"
|
||||||
|
deactivate_help: "Die veröffentlichte Umfrage wird vorübergehend offline genommen. Fragen bleiben weiter gesperrt."
|
||||||
|
activate_help: "Die veröffentlichte Umfrage wird wieder öffentlich erreichbar gemacht. Fragen bleiben weiter gesperrt."
|
||||||
save: "Speichern"
|
save: "Speichern"
|
||||||
save_metadata: "Metadaten speichern"
|
save_metadata: "Metadaten speichern"
|
||||||
save_this_question: "Diese Frage speichern"
|
save_this_question: "Diese Frage speichern"
|
||||||
@@ -107,7 +117,11 @@ survey:
|
|||||||
question_progress: "Frage %current%"
|
question_progress: "Frage %current%"
|
||||||
answer_flow_hint: "Diese Antwort wird direkt für den weiteren Ablauf verwendet."
|
answer_flow_hint: "Diese Antwort wird direkt für den weiteren Ablauf verwendet."
|
||||||
current_value: "Aktueller Wert"
|
current_value: "Aktueller Wert"
|
||||||
|
back: "Zurück"
|
||||||
next: "Weiter"
|
next: "Weiter"
|
||||||
|
preview_badge: "Entwurf"
|
||||||
|
preview_notice: "Dies ist eine Entwurfsansicht. Antworten können getestet werden, werden aber nicht gespeichert."
|
||||||
|
preview_thanks_text: "Der Testlauf ist abgeschlossen. Antworten wurden nicht gespeichert."
|
||||||
results:
|
results:
|
||||||
no_description: "Keine Beschreibung hinterlegt."
|
no_description: "Keine Beschreibung hinterlegt."
|
||||||
completed_submissions: "%count% abgeschlossene Teilnahmen"
|
completed_submissions: "%count% abgeschlossene Teilnahmen"
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ survey:
|
|||||||
login_required: "This area uses the regular Contao member login. Please place the module on a protected page or provide a separate login page."
|
login_required: "This area uses the regular Contao member login. Please place the module on a protected page or provide a separate login page."
|
||||||
create: "Create new survey"
|
create: "Create new survey"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete_confirm: "Delete this survey? This will permanently remove all questions, answers, and results as well. Alternatively, you can simply switch the survey to unpublished."
|
delete_confirm: "Delete this survey? This will permanently remove all questions, answers, and results as well. Alternatively, you can simply deactivate the survey temporarily."
|
||||||
delete_confirm_second: "Are you really sure?"
|
delete_confirm_second: "Are you really sure?"
|
||||||
published: "published"
|
published: "published"
|
||||||
draft: "Draft"
|
draft: "Draft"
|
||||||
locked: "locked"
|
locked: "locked"
|
||||||
|
active: "active"
|
||||||
|
inactive: "inactive"
|
||||||
questions: "%count% questions"
|
questions: "%count% questions"
|
||||||
participations: "%count% participations"
|
participations: "%count% participations"
|
||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
public_view: "Public view"
|
public_view: "Public view"
|
||||||
|
draft_view: "Draft view"
|
||||||
show_results: "Show results"
|
show_results: "Show results"
|
||||||
empty: "There is currently no survey assigned to the logged-in member."
|
empty: "There is currently no survey assigned to the logged-in member."
|
||||||
edit:
|
edit:
|
||||||
@@ -36,9 +39,16 @@ survey:
|
|||||||
published: "published"
|
published: "published"
|
||||||
draft: "draft"
|
draft: "draft"
|
||||||
locked: "locked"
|
locked: "locked"
|
||||||
structure_locked: "The survey structure is locked because answers already exist. Master data can still be maintained, but the flow and questions can no longer be changed."
|
structure_locked: "The questions of this survey are locked because the survey was published or first participations already exist. The metadata can still be maintained."
|
||||||
|
published_readonly: "This survey is published. Frontend editing is therefore disabled. Changes are still possible in the backend."
|
||||||
metadata: "Survey metadata"
|
metadata: "Survey metadata"
|
||||||
publish_survey: "Publish survey"
|
publish_survey: "Publish survey"
|
||||||
|
publish_help: "Publish survey. No further changes will be possible afterwards."
|
||||||
|
publish_confirm: "Are you really sure? After the survey has been published, no further changes will be possible!"
|
||||||
|
deactivate_survey: "Temporarily deactivate survey"
|
||||||
|
activate_survey: "Reactivate survey"
|
||||||
|
deactivate_help: "The published survey is temporarily taken offline. Questions remain locked."
|
||||||
|
activate_help: "The published survey is made publicly reachable again. Questions remain locked."
|
||||||
save: "Save"
|
save: "Save"
|
||||||
save_metadata: "Save metadata"
|
save_metadata: "Save metadata"
|
||||||
save_this_question: "Save this question"
|
save_this_question: "Save this question"
|
||||||
@@ -107,7 +117,11 @@ survey:
|
|||||||
question_progress: "Question %current%"
|
question_progress: "Question %current%"
|
||||||
answer_flow_hint: "This answer is used directly for the further flow."
|
answer_flow_hint: "This answer is used directly for the further flow."
|
||||||
current_value: "Current value"
|
current_value: "Current value"
|
||||||
|
back: "Back"
|
||||||
next: "Next"
|
next: "Next"
|
||||||
|
preview_badge: "Draft"
|
||||||
|
preview_notice: "This is a draft view. Answers can be tested, but they will not be stored."
|
||||||
|
preview_thanks_text: "The test run is complete. Answers were not stored."
|
||||||
results:
|
results:
|
||||||
no_description: "No description available."
|
no_description: "No description available."
|
||||||
completed_submissions: "%count% completed participations"
|
completed_submissions: "%count% completed participations"
|
||||||
|
|||||||
Reference in New Issue
Block a user