# Survey Bundle AI Handover Diese Datei beschreibt den aktuell gewünschten und umgesetzten Stand des Bundles. Ziel ist, dass ein KI-Modell oder ein neuer Bearbeiter die Logik, die Zustände und die wichtigsten Einstiegspunkte schnell versteht und darauf weiterarbeiten kann. ## Zweck des Bundles Das Bundle stellt ein Contao-Umfragesystem bereit mit: - Frontend-Liste für Redakteure - Frontend-Frageneditor - Öffentlicher Umfrageansicht über Alias-Link - Ergebnisseite im Frontend - Excel-Export der Ergebnisse - PDF-Export der Ergebnisse über Gotenberg - Backend-Verwaltung in Contao-DCA-Listen und Editmasken ## Zentrale Zustandsfelder Die Umfrage arbeitet mit drei relevanten Zuständen in `tl_survey`: - `published` - Fachlich: Die Umfrage wurde veröffentlicht. - Frontend: Ab Veröffentlichung darf der Redakteur im Frontend weder Fragen noch Metadaten bearbeiten. - Backend: Reversibel erlaubt. Der Admin darf den Status im Backend ändern. - `isActive` - Fachlich: Öffentliche Sichtbarkeit an/aus. - Gilt unabhängig vom Veröffentlichungsstatus als technischer Sichtbarkeitszustand. - Der öffentliche Reader liefert nur Umfragen aus, die `published = 1` und `isActive = 1` haben. - `isLocked` - Fachlich: Struktur wurde bereits benutzt oder veröffentlicht. - Wird automatisch gesetzt, wenn die erste Antwort gespeichert wird. - Wird beim Veröffentlichen ebenfalls gesetzt. - Im Backend gibt es bewusst keine harte Sperre mehr. Das Feld dient vor allem der internen Logik und Historie. ## Gewollte Produktentscheidung - Frontend ist streng: - Keine Fragenbearbeitung nach Veröffentlichung - Keine Metadatenbearbeitung nach Veröffentlichung - Keine Fragenbearbeitung nach ersten Teilnahmen - Die Frontend-Sperre für veröffentlichte Umfragen soll bewusst nicht tief im System verankert sein. - Für Redakteure genügt das Ausblenden bzw. Deaktivieren der relevanten Buttons und Formulare im Frontend. - Es ist ausdrücklich keine zusätzliche harte Systembarriere gewünscht, solange die Redakteursoberfläche den Bearbeitungspfad sauber schließt. - Backend ist bewusst toleranter: - Ein einzelner Admin darf im Notfall weiter arbeiten - Statt harter Sperre gibt es nur einen Warnhinweis in der Liste bei veröffentlichten Umfragen - `published` ist im Backend editierbar - `isActive` ist im Backend per Contao-Toggle schaltbar - Bewusster Trade-off zwischen Frontend und Backend: - Ein veröffentlichter Survey soll für Frontend-Redakteure praktisch nicht mehr bearbeitbar sein. - Im Backend darf der Admin trotz Veröffentlichung weiterhin Änderungen vornehmen, zum Beispiel für Rechtschreibfehler oder kleine Korrekturen. - Dadurch bleibt bewusst das Risiko bestehen, dass der Admin auch Fragen oder Strukturen ändert, obwohl bereits Antworten vorliegen. - Mögliche Folgen sind verwässerte oder fachlich falsche Ergebnisseiten sowie Inkonsistenzen, wenn bestehende Antworten nicht mehr sauber zur später geänderten Struktur passen. - Dieses Risiko wird bewusst akzeptiert und organisatorisch über Briefing/Disziplin des Admins bzw. der zuständigen Redakteure abgefangen, nicht über eine harte technische Sperre im Backend. Diese Entscheidung ist absichtlich so getroffen und kein Bug. ## Datenmodell und wichtige Tabellen - `tl_survey` - Umfrage-Stammdaten und Statusfelder - Enthält zusätzlich die backend-only gepflegte `internalNote` - `tl_survey_content` - Fragen der Umfrage - `tl_survey_condition` - Generische Sprungregeln - `tl_survey_editor` - Zugewiesene Frontend-Redakteure - `tl_survey_submission` - Umfrage-Durchläufe - `tl_survey_answer` - Gespeicherte Antworten inklusive Snapshot-Feldern zur Ergebnisstabilität Wichtige Snapshot-Felder in `tl_survey_answer`: - `questionType` - `questionLabel` - `questionDescription` - `questionSorting` Diese Felder sorgen dafür, dass Ergebnisse auch dann stabil bleiben, wenn Fragen später im Backend verändert oder gelöscht wurden. ## Frontend-Module und Controller ### 1. Redakteursliste Controller: - `src/Controller/FrontendModule/MemberSurveyListController.php` Template: - `contao/templates/frontend/member_survey_list.html.twig` Zweck: - Zeigt alle dem eingeloggten Frontend-Mitglied zugewiesenen Umfragen - Sortierung ist bewusst fest auf `Titel ASC` - Hier liegen die Redakteursaktionen zur Veröffentlichung und Aktivierung Aktuelle Button- und Sichtbarkeitslogik pro Umfrage: - `Bearbeiten` - Nur klickbar, solange `published = 0` - Nach Veröffentlichung disabled - `Öffentliche Ansicht` - Nur sichtbar, wenn `published = 1` - `Entwurfs-Ansicht` - Nur sichtbar, wenn `published = 0` - `Ergebnisse anzeigen` - Immer verfügbar, sofern Results-Seite konfiguriert ist - `Umfrage veröffentlichen` - Eigene Aktion in der Liste - Nach Veröffentlichung disabled und textlich als veröffentlicht markiert - `Duplizieren` - Legt eine Kopie der Umfrage für den Redakteur an - Titelvergabe ist fortlaufend: `Titel - Kopie`, danach `Titel - Kopie 2`, `Titel - Kopie 3` usw. - Bestehende `- Kopie`-Suffixe werden fachlich normalisiert, damit keine Titel wie `... - Kopie - Kopie` mehr neu entstehen - Kreis grau/grün - Schaltet `isActive` - Ist bewusst kein Auge mehr Wichtiger technischer Punkt (CSRF): - Jedes Frontend-Aktionsformular (Liste **und** Editor) sendet bewusst **zwei** Tokens: - `REQUEST_TOKEN` (`{{ contao.request_token }}`) – nötig für Contaos globalen Frontend-POST-Gate; fehlt er, lehnt Contao den POST schon vor dem Controller mit HTTP 400 ab. - `_token` (`{{ csrf_token('-' ~ id) }}`) – wird im Controller mit `isCsrfTokenValid('-'.$id, $token)` geprüft (session-basierter Symfony-Token). - Beide sind erforderlich. Frühere Stände hatten nur einen davon – das führte zu „Ungültiger CSRF-Token" (500) bzw. Bad Request (400). Der Listen-Controller spiegelt jetzt exakt die Token-Mechanik des Editors. - Der Contao-`REQUEST_TOKEN` allein reicht **nicht** als Controller-Prüfung: Er ist cookie-gebunden und bei anonymen/fragmentierten Frontend-Requests nicht stabil (Contao setzt das CSRF-Cookie dort teils gar nicht). Deshalb die zusätzliche session-basierte `_token`-Prüfung. ### 2. Frontend-Editor Controller: - `src/Controller/FrontendModule/MemberSurveyEditController.php` Template: - `contao/templates/frontend/member_survey_edit.html.twig` Zweck: - Anlegen neuer Umfragen - Bearbeiten der Metadaten vor Veröffentlichung - Bearbeiten der Fragenstruktur Aktueller Aufbau: - Bei neuer Umfrage: - Titel und Beschreibung - Bei bestehender, nicht veröffentlichter Umfrage: - Metadatenformular oberhalb des Frageneditors - Frage-für-Frage-Editor im Akkordeon - Neue Frage anlegen - Sprungregelübersicht rechts/unten je nach Breakpoint - Bei veröffentlichter Umfrage oder bei `isLocked = 1`: - Fragen im Frontend nicht mehr änderbar - Metadaten im Frontend ebenfalls nicht mehr bearbeitbar Wichtige Entscheidung: - Die Header-Badges im Fragen-Akkordeon wurden bewusst entfernt. - Typ, Pflicht usw. sollen nicht schon im Header gezeigt werden. - Die Details sieht man erst nach dem Aufklappen. - Die Sprungziel-Dropdowns im Frontend zeigen Positionsnummern der Umfrage (`1. ...`, `2. ...`) statt globaler Datenbank-IDs. - Die aktuell bearbeitete Frage wird dort nicht als eigenes Sprungziel angeboten. ### 3. Öffentliche Umfrageansicht Controller: - `src/Controller/FrontendModule/ShowSurveyController.php` Services: - `src/Service/SurveyFlowService.php` - `src/Service/SurveySubmissionService.php` Zweck: - Öffentliche Teilnahme an Umfragen - Zusätzlich Entwurfsansicht per `?preview=1` Wichtige Regeln: - Öffentliche Ansicht findet eine Umfrage nur, wenn: - `published = 1` - `isActive = 1` - Entwurfsansicht arbeitet ohne Speicherung - Öffentliche Ansicht speichert Antworten und erstellt/führt Submission fort - Rückwärtsnavigation ist implementiert - Nach Abschluss zeigt der gleiche Browser-Token für eine kurze Cooldown-Zeit (`RESTART_COOLDOWN_SECONDS = 60`) die Danke-Seite; danach darf dieselbe Session erneut teilnehmen (`SurveySubmissionService::isRestartAllowed`). Saubere Link-URLs (Reader und Ergebnisseite): - Die Umfrage wird über den Alias als **Pfad-Segment** adressiert: `//` statt `?auto_item=`. - URL-Erzeugung: per Route-Platzhalter `['parameters' => '/'.$alias]` (Frontend-Liste `MemberSurveyListController`, Backend `SurveyDcaListener::buildSurveyPageUrl`), nicht `['auto_item' => $alias]`. - Auslesen im Controller: `$this->getContaoAdapter(Input::class)->get('auto_item')`. Wichtig: `Input::get` markiert den Route-Parameter als **benutzt** – sonst wirft Contaos `LegacyRouteParametersListener` eine 404 wegen „unbenutzter Parameter". Ein reines `$request->query->get('auto_item')` reicht für Pfad-URLs nicht. ### 4. Ergebnisseite Controller: - `src/Controller/FrontendModule/ShowSurveyResultsController.php` Service: - `src/Service/SurveyResultsViewService.php` Template: - `contao/templates/frontend/show_survey_results.html.twig` Zweck: - Aggregierte Auswertung pro Frage - Anzeige von Anzahl Teilnahmen, Rücklauf und Frageergebnissen Aktuelle Ergebnislogik: - `yes_no_maybe` - Optionsstatistik plus Kreisdiagramm - `choice` - Optionsstatistik plus Kreisdiagramm - `range` - Minimum, Maximum, Durchschnitt plus Verteilung und Linienchart - `text` - Kein Pie-Chart - Stattdessen Response-Summary und Liste der Freitextantworten Wichtig: - Die Ergebnisse werden aus den Antwort-Snapshots aufgebaut, nicht nur aus den aktuellen Fragen. - Dadurch bleiben historische Ergebnisse stabil. ## Sprunglogik Relevante Services/Repos: - `src/Service/SurveyFlowService.php` - `src/Repository/SurveyConditionRepository.php` - `src/Controller/FrontendModule/MemberSurveyEditController.php` Reihenfolge der Navigation: 1. Direkter Ja/Nein-Sprung über `jumpOnYes` / `jumpOnNo` 2. Generische Bedingung aus `tl_survey_condition` 3. Nächste Frage nach Sortierung Wichtiger Detailpunkt: - In der Sprungregelübersicht im Frontend-Editor werden bewusst die Positionsnummern innerhalb der Umfrage angezeigt, nicht die globalen Datenbank-IDs der Fragen. - Gleiches gilt für die Sprungziel-Dropdowns in Backend und Frontend. Wichtige technische Eigenschaft: - `jumpOnYes`, `jumpOnNo` und generische Bedingungen referenzieren Ziel-Fragen intern immer über die Frage-ID, nicht über die Position. - Dadurch bleiben Sprünge bei einer reinen Umordnung der Fragen fachlich stabil an dieselbe Frage gebunden. - Wenn eine Ziel-Frage an eine andere Position verschoben wird, zeigt die Oberfläche danach automatisch die neue Positionsnummer an. ## Excel-Export Controller: - `src/Controller/SurveyResultsExportController.php` Service: - `src/Service/SurveyResultsExportService.php` Bibliothek: - `phpoffice/phpspreadsheet` Zweck: - Export aller abgeschlossenen Teilnahmen in eine XLSX-Datei Aktuelle Spaltenlogik: - Ja/Nein/Vielleicht: - pro Antwortoption eigene Spalte - Choice: - pro Antwortoption eigene Spalte - Range: - eine Wertespalte - Text: - eine Textspalte Wichtige Eigenschaft: - Die Spalten orientieren sich an den aktuell vorhandenen Fragen im Survey. - Die Zeilen enthalten nur abgeschlossene Submissions. ## PDF-Export Controller: - `src/Controller/SurveyResultsExportController.php` Service: - `src/Service/SurveyResultsPdfService.php` Template: - `src/Resources/views/pdf/survey_results.html.twig` Externe Abhängigkeit: - Gotenberg über `surveyGotenbergUrl` in den Contao-Einstellungen Zweck: - Serverseitiger PDF-Export der Ergebnisse Aktuelle Logik: - HTML wird serverseitig mit Twig gerendert - Diagramme werden als SVG/Data-URI vorbereitet - Gotenberg rendert daraus das PDF - Ist keine Gotenberg-URL gesetzt, wird im Frontend kein PDF-Download angeboten ## Backend-Aufbau ### tl_survey Dateien: - `contao/dca/tl_survey.php` - `contao/languages/de/tl_survey.php` - `contao/languages/en/tl_survey.php` Wichtige Listenoperationen: - `Fragen bearbeiten` - `Umfrage bearbeiten` - `isActive` als Contao-Toggle - Warnsymbol bei veröffentlichten Umfragen - `Link kopieren` - `Ergebnisse anzeigen` Aktuelle Listen- und Filterlogik: - Die mehrspaltige Umfragenliste zeigt aktuell: - `Titel` - `Zugewiesene Benutzer` - `Vorlage` - `Interne Notiz` - `Kategorien` wurden bewusst aus der mehrspaltigen Liste und aus der rechten Filterspalte entfernt. - Die Spalte `Zugewiesene Benutzer` wird in der Listenmetadatenlogik gekürzt: - maximal 3 Namen direkt sichtbar - danach Zusatz wie `... und X weitere` - `internalNote` - backend-only Feld in `tl_survey` - nur im Backend bearbeitbar - in der mehrspaltigen Liste sichtbar - bei Duplikaten wird der Wert mitkopiert - In der rechten Suchspalte ist `Interne Notiz` suchbar. - `ID` wurde für die Umfragenliste bewusst aus dem Suchfeld entfernt. - Das geschieht über einen kleinen backend-spezifischen JS-Workaround, weil Contao `id` im Core für `DC_Table` hart ergänzt. Aktuelle Backend-Entscheidung: - Das Warnsymbol genügt als Hinweis bei veröffentlichten Umfragen - Es gibt bewusst keine harte Sperre für Fragen im Backend ### Child-Tabellen - `tl_survey_content` - `tl_survey_condition` - `tl_survey_editor` Hinweis: - `isLocked` wird in den Child-Headerfeldern nicht mehr angezeigt, weil der Backend-Lock bewusst keine führende Benutzerfunktion mehr ist. - In `tl_survey_content` ist der Fragenlisten-Header zusätzlich reduziert: - nur noch `Titel` und `Veröffentlicht` - kein öffentlicher Link-Schlüssel mehr - die rechte Header-Leiste mit Aktionen ist im Normalzustand ausgeblendet - im aktiven Verschiebe-/Clipboard-Modus wird sie wieder eingeblendet, damit `Neu oben` bzw. die Einfügepositionen erreichbar bleiben Fragenliste `tl_survey_content`: - Direkt sichtbare Primäraktion pro Frage: - `Bearbeiten`-Stift - Verschieben: - erfolgt im Backend über den normalen Contao-Paste-/Move-Flow - die Aktion `Verschieben` liegt bewusst nur unter den drei Punkten - dort wird der Contao-Pfeil `mover.svg` verwendet, nicht die blaue Schere - nach Aktivierung des Clipboard-Zustands erscheinen die üblichen Einfügeziele wie `Nach dem Element ... einfügen` ### Systemeinstellungen Datei: - `contao/dca/tl_settings.php` Wichtige Settings: - `surveyReaderPage` - `surveyResultsPage` - `surveyGotenbergUrl` ## Aktuelle Produktregeln in Kurzform - Frontend-Liste: - Titel alphabetisch - Veröffentlichung und Aktivierung in der Liste - Frontend-Editor: - Metadaten nur vor Veröffentlichung - Fragenbearbeitung nur solange nicht veröffentlicht und keine Teilnahmen vorliegen - Öffentliche Ansicht: - Nur `published = 1` und `isActive = 1` - Backend: - `published` reversibel erlaubt - `isActive` per Standard-Contao-Toggle - nur Warnsymbol statt Backend-Sperre - Admin darf Fragen auch nach Veröffentlichung oder nach vorhandenen Antworten umsortieren ## Wichtige Dateien nach Themengebiet Frontend-Liste: - `src/Controller/FrontendModule/MemberSurveyListController.php` - `contao/templates/frontend/member_survey_list.html.twig` Frontend-Editor: - `src/Controller/FrontendModule/MemberSurveyEditController.php` - `contao/templates/frontend/member_survey_edit.html.twig` Öffentliche Umfrage: - `src/Controller/FrontendModule/ShowSurveyController.php` - `src/Service/SurveyFlowService.php` - `src/Service/SurveySubmissionService.php` Ergebnisse: - `src/Controller/FrontendModule/ShowSurveyResultsController.php` - `src/Service/SurveyResultsViewService.php` - `contao/templates/frontend/show_survey_results.html.twig` Exporte: - `src/Controller/SurveyResultsExportController.php` - `src/Service/SurveyResultsExportService.php` - `src/Service/SurveyResultsPdfService.php` Backend: - `contao/dca/tl_survey.php` - `src/EventListener/SurveyDcaListener.php` ## Technische Validierung, die in diesem Projekt gut funktioniert - `php -l` auf die geänderten PHP-Dateien - `ddev exec php bin/console lint:yaml ...` - `ddev exec php bin/console cache:clear` ## Offene technische Punkte / Deployment-Hinweise - Neue DB-Spalte in `tl_survey`: - `internalNote` - Typ aktuell: `varchar(255) NOT NULL default ''` - Für andere Umgebungen muss diese Spalte per Migration/SQL vorhanden sein, sonst fehlen Backend-Feld, Listenanzeige und Suchfunktion fachlich bzw. führen zu Inkonsistenzen. - Nach Änderungen an DCA, Backend-CSS oder Backend-JS ist in dieser Installation typischerweise nötig: - `php vendor/bin/contao-console assets:install public --symlink --relative` - `php vendor/bin/contao-console cache:clear --env=prod` - Es gibt aktuell zwei bewusst eingebaute backend-spezifische UI-Workarounds über Bundle-Assets: - In der Umfragenliste wird `ID` aus dem Suchfeld per JS entfernt, weil Contao das Feld im Core von `DC_Table` hart ergänzt. - In der Fragenliste (`table=tl_survey_content`) wird die rechte Parent-Header-Leiste per CSS/JS im Normalzustand ausgeblendet und nur im aktiven Clipboard-/Verschiebe-Modus wieder eingeblendet. - Lokale DDEV-Besonderheit, die bei Fehleranalyse relevant sein kann: - Das Bundle läuft hier als Path-Repository/Symlink. - Wenn Backend-Übersetzungen oder Assets trotz korrektem Code unerwartet fehlen, zuerst Bundle-Pfad, veröffentlichte Assets und Prod-Cache prüfen. ## Bekannte bewusste Trade-offs - Backend kann veröffentlichte Umfragen technisch weiterhin verändern. - Das ist bewusst akzeptiert, weil nur ein Admin arbeitet. - `isLocked` bleibt als internes Logikfeld bestehen, obwohl der Backend-Lock nicht mehr durchgesetzt wird. - PDF-Export hängt von Gotenberg ab und ist absichtlich optional. - Eine reine Umordnung von Fragen ist für Sprungziele logisch unkritisch, weil Sprünge an Frage-IDs hängen. - Ergebnisse vorhandener, weiterhin existierender Fragen folgen bei der Anzeige jedoch der aktuellen Sortierung der Frage. - Historische Antwort-Snapshots speichern zwar `questionSorting`, die Ergebnisansicht sortiert vorhandene Fragen aber primär nach der aktuellen Frage-Sortierung. - Das ist im toleranten Backend-Modell ein bewusst akzeptierter Side-Effect. ## Empfohlene Einstiegspunkte für künftige Änderungen - Status-/Sichtbarkeitslogik: `SurveyRepository`, `SurveyEditorService`, `MemberSurveyListController` - Fragerouting: `SurveyFlowService`, `SurveyConditionRepository` - Ergebnisdarstellung: `SurveyResultsViewService` - Exportlogik: `SurveyResultsExportService`, `SurveyResultsPdfService` - Backend-Operationen und DCA-Verhalten: `tl_survey.php`, `SurveyDcaListener`