From 4d4f21b7c8e3c6f81a984b1f7995fccaed40ca5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mummert?= Date: Sun, 27 Sep 2026 20:21:12 +0200 Subject: [PATCH] Require Contao 6 and drop the Contao 5 compatibility code - Require contao/core-bundle and calendar-bundle ^6.0 - Render the setup check and the notification help as input_field callbacks instead of a custom backend widget; the fields no longer create a virtual jsonData column - Load the backend style sheet through contao.backend.custom_css and remove the menu listener (tooltip workaround for Contao 5 included) - Tell editors when the Notification Center is not installed instead of linking to a module that does not exist - Generate category aliases with the slug settings of the calendar page so that umlauts are transliterated for German sites - Always return a RecordLabel for the category list, export stored values as they are, remove the decodeEntities options and the legacy "events" URL parameter, use array notation for the price columns Co-Authored-By: Claude Fable 5.1 --- README.md | 16 ++++--- composer.json | 6 +-- config/services.yaml | 1 - contao/config/config.php | 8 ---- contao/dca/tl_calendar_events.php | 4 +- contao/dca/tl_wk_booking.php | 4 +- contao/dca/tl_wk_category.php | 2 +- contao/dca/tl_wk_config.php | 28 +++--------- contao/dca/tl_wk_inquiry.php | 4 +- contao/languages/de/tl_wk_config.php | 2 + contao/languages/en/tl_wk_config.php | 2 + public/wk-sync-backend.css | 6 ++- public/wk-sync-menu.css | 3 -- src/Booking/BookingNotifierInterface.php | 5 +++ src/Booking/EventResolver.php | 3 +- src/Booking/FieldRules.php | 2 +- src/Booking/NotificationCenterNotifier.php | 5 +++ src/Booking/NullNotifier.php | 5 +++ src/ContaoWkSyncBundle.php | 11 +++++ .../DataContainer/CategoryAliasListener.php | 23 +++++++++- .../DataContainer/CategoryLabelListener.php | 13 +++--- .../NotificationNoticeListener.php | 41 ++++++++++------- .../DataContainer/SetupCheckListener.php | 31 ++++++------- .../Menu/BackendMenuListener.php | 45 ------------------- src/Export/BookingExporter.php | 4 -- src/Widget/Backend/ExplanationWidget.php | 32 ------------- 26 files changed, 132 insertions(+), 174 deletions(-) delete mode 100644 public/wk-sync-menu.css delete mode 100644 src/EventListener/Menu/BackendMenuListener.php delete mode 100644 src/Widget/Backend/ExplanationWidget.php diff --git a/README.md b/README.md index 2bad7c5..791727c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # W&K-Sync Bundle -Verbindet **Contao 5.7 und 6** mit der Seminarverwaltung **W&K** (Windows-Software ohne Schnittstelle) über CSV-Dateien: +Verbindet **Contao 6** mit der Seminarverwaltung **W&K** (Windows-Software ohne Schnittstelle) über CSV-Dateien: 1. **Import**: Die Redaktion exportiert in W&K die Seminarliste als CSV und lädt sie im Contao-Backend hoch. Vor dem Schreiben zeigt eine **Vorschau**, was sich ändert (neu, geändert, storniert, unverändert, übersprungen, Warnungen). Erst nach Bestätigung werden die Veranstaltungen im Kalender angelegt bzw. aktualisiert. 2. **Online-Anmeldung**: Besucher melden sich über ein Formular des **Formulargenerators** zu einer Veranstaltung an. Das Bundle blendet je nach Veranstaltung Felder ein und aus, prüft Plätze und Anmeldeschluss, speichert die Anmeldung, verschickt die passende Benachrichtigung (Notification Center 2) und leitet auf die Bestätigungsseite weiter. 3. **Posteingang**: Anmeldungen und Kontaktanfragen werden in Ordnern bearbeitet (Neu, Download-Liste, Bereits bearbeitet, Ignoriert/Spam, Exportiert). 4. **Export**: Die Download-Liste wird als CSV für den W&K-Import erzeugt (ISO-8859-1, Semikolon, feste Spalten), die Datensätze werden als exportiert markiert und die freien Plätze der Veranstaltungen reduziert. -- Contao **5.7** oder **6.0**, PHP **8.5**, Doctrine DBAL **4**, MySQL 8 oder MariaDB +- Contao **6**, PHP **8.5**, Doctrine DBAL **4**, MySQL 8 oder MariaDB - Keine Abhängigkeiten außer dem Contao-Core und dem Kalender-Bundle - **Notification Center 2** wird für Benachrichtigungen empfohlen (Typ „W&K-Anmeldung“), ist aber nicht erforderlich; ohne Notification Center werden Anmeldungen gespeichert und protokolliert, aber keine Mails verschickt (Stand September 2026 unterstützt das Notification Center Contao 6 noch nicht) - Newsletter-Anmeldung (Double-Opt-in) mit dem Newsletter-Bundle, optional @@ -168,11 +168,15 @@ Alle Unterpunkte von „W&K-Tools“ sind normale Contao-Backend-Module und werd - Verfügbarkeit wird beim Absenden erneut geprüft (Wettlauf um den letzten Platz). - Anmeldungen und Anfragen werden nur mit bekannten Spalten gespeichert. -## Contao 5.7 und 6 +## Contao 6 -Ein Stand des Bundles läuft in beiden Versionen. Contao 6 speichert Eingaben unkodiert und kodiert erst bei der Ausgabe, Contao 5 speichert sie kodiert. Die Backend-Oberfläche des Bundles ist komplett in Twig (automatisches Escaping); die wenigen Stellen, die HTML außerhalb von Twig erzeugen (Kategorienliste, Prüfung der Einrichtung, Tooltips der Navigation), kodieren selbst ohne doppeltes Kodieren und geben in Contao 6 ein `RecordLabel` zurück. Der CSV-Export dekodiert gespeicherte Werte, damit unter Contao 5 keine HTML-Entities (z. B. `(` für eine Klammer) in der W&K-Datei landen. +Das Bundle setzt Contao 6 voraus und nutzt dessen Konventionen: -Das Backend-Modul (Import, Posteingang, Exporte) ist ein klassisches `BE_MOD`-Callback-Modul und das Anmeldeformular wird über `Controller::getForm()` gerendert; beides ist in Contao 6 weiterhin unterstützt und nicht als veraltet markiert. +- **Output-Encoding:** Contao speichert Eingaben unkodiert. Import, Anmeldungen und Export arbeiten durchgehend mit Klartext, die W&K-Datei enthält also nie HTML-Entities. Die Backend-Oberfläche ist Twig (automatisches Escaping); die Kategorienliste liefert ein kodiertes `RecordLabel`. +- **Anzeigefelder:** Die Prüfung der Einrichtung, die Erklärung der Benachrichtigungslogik und der Hinweis bei fehlenden Benachrichtigungen sind `input_field_callback`-Felder ohne Spalte. Sie legen kein virtuelles Feld (`jsonData`) an. +- **Backend-Stylesheet:** `public/wk-sync-backend.css` wird über `contao.backend.custom_css` eingebunden (Icon der Navigationsgruppe, Posteingang, Einstellungen). +- **Veranstaltungsfelder:** Die 43 `wk…`-Felder in `tl_calendar_events` sind echte Spalten, keine virtuellen Felder, weil Import und Vorschau sie per SQL vergleichen und die Platzprüfung darauf rechnet. +- **Ohne Ersatz im Core:** Das Backend-Modul (Import, Posteingang, Exporte) ist ein `BE_MOD`-Callback-Modul, das Anmeldeformular wird über `Controller::getForm()` gerendert und die Veranstaltung wird über `Input::get('auto_item')` ermittelt. Alle drei sind in Contao 6 nicht als veraltet markiert. ## Entwicklung @@ -185,5 +189,5 @@ Die Tests decken CSV-Parsing, Zeilen-Zuordnung, Kategorie-Auflösung, Formularre Backend-Bausteine, die sich auch anderswo nutzen lassen: -- Widget `wk_explanation` (`inputType`) für Erklärtexte in DCA-Formularen; Text aus `eval.text` oder der Sprachdatei des Feldes, Box über `eval.boxClass` (`tl_info`, `tl_confirm`, `tl_error`). Contaos eigener Typ `explanation` existiert nur im Frontend-Formulargenerator. +- Anzeigefelder in DCA-Formularen (Prüfung der Einrichtung, Erklärtexte) sind `input_field_callback`-Felder: Feld ohne `inputType` und `sql` mit `eval.doNotShow`, gerendert von einem Listener mit `#[AsCallback(target: 'fields..input_field')]`. - `BackendMenuListener` hängt das Icon der Navigationsgruppe ein und nimmt die doppelte Escapierung zurück, mit der der Contao-Kern Modulbeschreibungen mit `&` in Tooltips ausgibt. diff --git a/composer.json b/composer.json index bf0c5c5..d8a3a12 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "mummert/wk-sync-bundle", - "description": "W&K-Sync für Contao 5.7 und 6: Kursimport aus der Seminarverwaltung W&K (CSV), Online-Anmeldungen mit Posteingang und CSV-Export zurück nach W&K", + "description": "W&K-Sync für Contao 6: Kursimport aus der Seminarverwaltung W&K (CSV), Online-Anmeldungen mit Posteingang und CSV-Export zurück nach W&K", "type": "contao-bundle", "license": "MIT", "authors": [ @@ -13,8 +13,8 @@ "php": "^8.5", "ext-iconv": "*", "ext-mbstring": "*", - "contao/core-bundle": "^5.7 || ^6.0", - "contao/calendar-bundle": "^5.7 || ^6.0", + "contao/core-bundle": "^6.0", + "contao/calendar-bundle": "^6.0", "doctrine/dbal": "^4.0", "symfony/config": "^7.4 || ^8.0", "symfony/console": "^7.4 || ^8.0", diff --git a/config/services.yaml b/config/services.yaml index 08fb794..bd4b9c5 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,7 +17,6 @@ services: - '../src/Booking/{Availability,BookingFields}.php' - '../src/Inbox/{InboxItem,InboxState}.php' - '../src/Sync/CalendarConfig.php' - - '../src/Widget/' # Ohne Notification Center werden Anmeldungen gespeichert, aber keine Mails verschickt Mummert\ContaoWkSyncBundle\Booking\BookingNotifierInterface: '@Mummert\ContaoWkSyncBundle\Booking\NullNotifier' diff --git a/contao/config/config.php b/contao/config/config.php index f3f66c2..3292641 100644 --- a/contao/config/config.php +++ b/contao/config/config.php @@ -4,18 +4,15 @@ declare(strict_types=1); use Contao\ArrayUtil; use Mummert\ContaoWkSyncBundle\Backend\WkSyncModule; -use Mummert\ContaoWkSyncBundle\Widget\Backend\ExplanationWidget; // Backend-Bereich „W&K-Tools“ direkt nach „Inhalte“ ArrayUtil::arrayInsert($GLOBALS['BE_MOD'], 1, [ 'wk_tools' => [ 'wk_import' => [ 'callback' => WkSyncModule::class, - 'stylesheet' => 'bundles/contaowksync/wk-sync-backend.css', ], 'wk_inbox' => [ 'callback' => WkSyncModule::class, - 'stylesheet' => 'bundles/contaowksync/wk-sync-backend.css', ], 'wk_bookings' => [ 'tables' => ['tl_wk_booking'], @@ -25,20 +22,15 @@ ArrayUtil::arrayInsert($GLOBALS['BE_MOD'], 1, [ ], 'wk_exports' => [ 'callback' => WkSyncModule::class, - 'stylesheet' => 'bundles/contaowksync/wk-sync-backend.css', ], 'wk_categories' => [ 'tables' => ['tl_wk_category'], ], 'wk_config' => [ 'tables' => ['tl_wk_config'], - 'stylesheet' => 'bundles/contaowksync/wk-sync-backend.css', ], 'wk_log' => [ 'tables' => ['tl_wk_sync_log'], ], ], ]); - -// Erklärungstexte in Backend-Formularen (z. B. Benachrichtigungslogik in den W&K-Einstellungen) -$GLOBALS['BE_FFL']['wk_explanation'] = ExplanationWidget::class; diff --git a/contao/dca/tl_calendar_events.php b/contao/dca/tl_calendar_events.php index c5e98d5..4378168 100644 --- a/contao/dca/tl_calendar_events.php +++ b/contao/dca/tl_calendar_events.php @@ -45,7 +45,7 @@ $price = static fn (): array => [ 'exclude' => true, 'inputType' => 'text', 'eval' => ['rgxp' => 'prcnt', 'maxlength' => 10, 'tl_class' => 'w50'], - 'sql' => "decimal(10,2) NOT NULL default '0.00'", + 'sql' => ['type' => 'decimal', 'precision' => 10, 'scale' => 2, 'default' => '0.00'], ]; $fields = [ @@ -160,7 +160,7 @@ $fields = [ 'wkOrganizerPostal' => $text(16), 'wkOrganizerCity' => $text(128), 'wkOrganizerCountryCode' => $text(16), - 'wkOrganizerEmail' => $text(255, 'w50', ['decodeEntities' => true]), + 'wkOrganizerEmail' => $text(255, 'w50'), 'wkNotification' => [ 'exclude' => true, 'inputType' => 'select', diff --git a/contao/dca/tl_wk_booking.php b/contao/dca/tl_wk_booking.php index 19a68f7..ec6d1e8 100644 --- a/contao/dca/tl_wk_booking.php +++ b/contao/dca/tl_wk_booking.php @@ -166,14 +166,14 @@ $GLOBALS['TL_DCA']['tl_wk_booking'] = [ 'sorting' => true, 'exclude' => true, 'inputType' => 'text', - 'eval' => ['mandatory' => true, 'rgxp' => 'email', 'maxlength' => 255, 'decodeEntities' => true, 'tl_class' => 'w50'], + 'eval' => ['mandatory' => true, 'rgxp' => 'email', 'maxlength' => 255, 'tl_class' => 'w50'], 'sql' => ['type' => 'string', 'length' => 255, 'default' => ''], ], 'phone' => [ 'search' => true, 'exclude' => true, 'inputType' => 'text', - 'eval' => ['rgxp' => 'phone', 'maxlength' => 64, 'decodeEntities' => true, 'tl_class' => 'w50'], + 'eval' => ['rgxp' => 'phone', 'maxlength' => 64, 'tl_class' => 'w50'], 'sql' => ['type' => 'string', 'length' => 64, 'default' => ''], ], 'position' => [ diff --git a/contao/dca/tl_wk_category.php b/contao/dca/tl_wk_category.php index 55d316e..f646e20 100644 --- a/contao/dca/tl_wk_category.php +++ b/contao/dca/tl_wk_category.php @@ -80,7 +80,7 @@ $GLOBALS['TL_DCA']['tl_wk_category'] = [ 'search' => true, 'exclude' => true, 'inputType' => 'textarea', - 'eval' => ['style' => 'height:80px', 'decodeEntities' => true, 'tl_class' => 'clr'], + 'eval' => ['style' => 'height:80px', 'tl_class' => 'clr'], 'sql' => ['type' => 'text', 'notnull' => false], ], ], diff --git a/contao/dca/tl_wk_config.php b/contao/dca/tl_wk_config.php index a2f26ae..e39c368 100644 --- a/contao/dca/tl_wk_config.php +++ b/contao/dca/tl_wk_config.php @@ -121,7 +121,7 @@ $GLOBALS['TL_DCA']['tl_wk_config'] = [ 'default' => 'TPI Moritzburg', 'exclude' => true, 'inputType' => 'textarea', - 'eval' => ['style' => 'height:60px', 'decodeEntities' => true, 'tl_class' => 'clr'], + 'eval' => ['style' => 'height:60px', 'tl_class' => 'clr'], 'sql' => ['type' => 'text', 'notnull' => false], ], 'author' => [ @@ -165,7 +165,7 @@ $GLOBALS['TL_DCA']['tl_wk_config'] = [ 'newsletterText' => [ 'exclude' => true, 'inputType' => 'textarea', - 'eval' => ['style' => 'height:120px', 'decodeEntities' => true, 'tl_class' => 'clr'], + 'eval' => ['style' => 'height:120px', 'tl_class' => 'clr'], 'sql' => ['type' => 'text', 'notnull' => false], ], 'notificationDefault' => [ @@ -177,25 +177,11 @@ $GLOBALS['TL_DCA']['tl_wk_config'] = [ ], ]; -// Text und Box setzt der SetupCheckListener beim Bearbeiten -$GLOBALS['TL_DCA']['tl_wk_config']['fields']['setupCheck'] = [ - 'label' => ['', ''], - 'inputType' => 'wk_explanation', - 'eval' => ['tl_class' => 'long clr'], -]; - -$GLOBALS['TL_DCA']['tl_wk_config']['fields']['notificationOverview'] = [ - 'label' => ['', ''], - 'inputType' => 'wk_explanation', - 'eval' => ['tl_class' => 'long clr'], -]; - -// Text und Sichtbarkeit setzt der NotificationNoticeListener (nur ohne Benachrichtigungen vom Typ „W&K-Anmeldung“) -$GLOBALS['TL_DCA']['tl_wk_config']['fields']['notificationNotice'] = [ - 'label' => ['', ''], - 'inputType' => 'wk_explanation', - 'eval' => ['tl_class' => 'long clr', 'boxClass' => 'tl_error'], -]; +// Reine Anzeigefelder ohne Spalte: Die Listener rendern sie per input_field_callback (Prüfung der Einrichtung, +// Erklärung der Benachrichtigungslogik, Hinweis bei fehlenden Benachrichtigungen) +foreach (['setupCheck', 'notificationOverview', 'notificationNotice'] as $field) { + $GLOBALS['TL_DCA']['tl_wk_config']['fields'][$field] = ['eval' => ['doNotShow' => true]]; +} // Bedingung 1 (Online) und 12 (Ausbildung) stehen allein, alle anderen paarweise eintägig/mehrtägig $leftColumn = [1, 2, 3, 4, 8, 10, 12]; diff --git a/contao/dca/tl_wk_inquiry.php b/contao/dca/tl_wk_inquiry.php index e02cbb1..3d395a4 100644 --- a/contao/dca/tl_wk_inquiry.php +++ b/contao/dca/tl_wk_inquiry.php @@ -133,13 +133,13 @@ $GLOBALS['TL_DCA']['tl_wk_inquiry'] = [ 'search' => true, 'exclude' => true, 'inputType' => 'text', - 'eval' => ['rgxp' => 'email', 'maxlength' => 255, 'decodeEntities' => true, 'tl_class' => 'w50'], + 'eval' => ['rgxp' => 'email', 'maxlength' => 255, 'tl_class' => 'w50'], 'sql' => ['type' => 'string', 'length' => 255, 'default' => ''], ], 'phone' => [ 'exclude' => true, 'inputType' => 'text', - 'eval' => ['maxlength' => 64, 'decodeEntities' => true, 'tl_class' => 'w50'], + 'eval' => ['maxlength' => 64, 'tl_class' => 'w50'], 'sql' => ['type' => 'string', 'length' => 64, 'default' => ''], ], 'subject' => [ diff --git a/contao/languages/de/tl_wk_config.php b/contao/languages/de/tl_wk_config.php index 645a578..de53119 100644 --- a/contao/languages/de/tl_wk_config.php +++ b/contao/languages/de/tl_wk_config.php @@ -56,6 +56,8 @@ $lang['notificationOverview'] = '

So wird die Benachrichtigung für ei '; +$lang['notificationUnavailable'] = 'Das Notification Center ist nicht installiert. Anmeldungen werden gespeichert und im Systemprotokoll vermerkt, es wird aber keine E-Mail verschickt. Sobald das Notification Center installiert ist, lassen sich hier Benachrichtigungen vom Typ „W&K-Anmeldung“ zuordnen.'; + $lang['notificationNotice'] = 'Es ist noch keine Benachrichtigung vom Typ „W&K-Anmeldung“ angelegt, deshalb sind die Auswahlfelder leer. Bitte zuerst unter Notification Center → Benachrichtigungen eine Benachrichtigung mit diesem Typ anlegen; danach lässt sie sich hier zuordnen.'; $lang['notification1'] = ['Bedingung 1: Online-Kurs', 'Veranstaltungsort „Online“, alle Kategorie-Arten, ein- oder mehrtägig. Leer = die Bedingung der Kategorie-Art zählt.']; diff --git a/contao/languages/en/tl_wk_config.php b/contao/languages/en/tl_wk_config.php index 9fab58e..161ce1c 100644 --- a/contao/languages/en/tl_wk_config.php +++ b/contao/languages/en/tl_wk_config.php @@ -56,6 +56,8 @@ $lang['notificationOverview'] = '

How the notification for a booking i '; +$lang['notificationUnavailable'] = 'The Notification Center is not installed. Bookings are stored and logged, but no e-mail is sent. Once the Notification Center is installed, notifications of the type "W&K booking" can be assigned here.'; + $lang['notificationNotice'] = 'There is no notification of the type "W&K booking" yet, so the selects are empty. Please create one under Notification Center → Notifications first; it can then be assigned here.'; $lang['notification1'] = ['Condition 1: online course', 'Location "Online", all category kinds, single or multi-day. Empty = the condition of the category kind applies.']; diff --git a/public/wk-sync-backend.css b/public/wk-sync-backend.css index 6425c88..426f408 100644 --- a/public/wk-sync-backend.css +++ b/public/wk-sync-backend.css @@ -1,3 +1,7 @@ +/* Icon der Navigationsgruppe „W&K-Tools“ (Lucide „graduation-cap“, wie die Gruppen-Icons des Contao-Cores) */ +#tl_navigation .group-wk_tools { background: url(graduation-cap.svg) 3px 2px no-repeat; } +[data-color-scheme=dark] #tl_navigation .group-wk_tools { background-image: url(graduation-cap--dark.svg); } + .wk-sync { padding: 18px 15px; } .wk-sync h2 { margin: 1.5rem 0 .75rem; font-size: 1.1rem; } .wk-sync > h2:first-child, .wk-sync > .tl_message + h2 { margin-top: 0; } @@ -52,7 +56,7 @@ .wk-sync-highlight td { background: #fff8e1; } .wk-sync-meta { margin: 0 0 .75rem; } -/* Erklärungen in Backend-Formularen (Widget wk_explanation) */ +/* Erklärungen in Backend-Formularen (Anzeigefelder der W&K-Einstellungen) */ .wk-sync-explanation p, .wk-sync-explanation ol { margin: 0 0 .6em; } .wk-sync-explanation ol { padding-left: 1.4em; } .wk-sync-explanation li { margin: .2em 0; } diff --git a/public/wk-sync-menu.css b/public/wk-sync-menu.css deleted file mode 100644 index f532b06..0000000 --- a/public/wk-sync-menu.css +++ /dev/null @@ -1,3 +0,0 @@ -/* Icon der Navigationsgruppe „W&K-Tools“ (Lucide „graduation-cap“, wie die Gruppen-Icons des Contao-Cores) */ -#tl_navigation .group-wk_tools { background: url(graduation-cap.svg) 3px 2px no-repeat; } -[data-color-scheme=dark] #tl_navigation .group-wk_tools { background-image: url(graduation-cap--dark.svg); } diff --git a/src/Booking/BookingNotifierInterface.php b/src/Booking/BookingNotifierInterface.php index 696904e..4688b9e 100644 --- a/src/Booking/BookingNotifierInterface.php +++ b/src/Booking/BookingNotifierInterface.php @@ -17,4 +17,9 @@ interface BookingNotifierInterface * @return array ID => Titel der verfügbaren Benachrichtigungen */ public function availableNotifications(): array; + + /** + * Ob überhaupt Benachrichtigungen verschickt werden können (Notification Center installiert). + */ + public function isAvailable(): bool; } diff --git a/src/Booking/EventResolver.php b/src/Booking/EventResolver.php index 27f2095..27daa33 100644 --- a/src/Booking/EventResolver.php +++ b/src/Booking/EventResolver.php @@ -32,7 +32,8 @@ class EventResolver $this->framework->initialize(); $input = $this->framework->getAdapter(Input::class); - $identifier = (string) ($input->get('auto_item') ?? $input->get('events') ?? $request->attributes->get('auto_item') ?? ''); + // Für den dynamischen URL-Parameter gibt es noch keinen Ersatz im Request-Objekt (Input::get('auto_item') ist nicht veraltet) + $identifier = (string) ($input->get('auto_item') ?? $request->attributes->get('auto_item') ?? ''); if ('' === $identifier) { return null; diff --git a/src/Booking/FieldRules.php b/src/Booking/FieldRules.php index 381335d..7afa997 100644 --- a/src/Booking/FieldRules.php +++ b/src/Booking/FieldRules.php @@ -51,7 +51,7 @@ final readonly class FieldRules $value = $values[$dependency['field']] ?? ''; if (\is_array($value)) { - $value = (string) reset($value); + $value = (string) array_first($value); } return \in_array((string) $value, $dependency['values'], true); diff --git a/src/Booking/NotificationCenterNotifier.php b/src/Booking/NotificationCenterNotifier.php index 63f3334..244bca0 100644 --- a/src/Booking/NotificationCenterNotifier.php +++ b/src/Booking/NotificationCenterNotifier.php @@ -51,4 +51,9 @@ class NotificationCenterNotifier implements BookingNotifierInterface return $options; } + + public function isAvailable(): bool + { + return true; + } } diff --git a/src/Booking/NullNotifier.php b/src/Booking/NullNotifier.php index 26b0fd0..9a00ea4 100644 --- a/src/Booking/NullNotifier.php +++ b/src/Booking/NullNotifier.php @@ -29,4 +29,9 @@ class NullNotifier implements BookingNotifierInterface { return []; } + + public function isAvailable(): bool + { + return false; + } } diff --git a/src/ContaoWkSyncBundle.php b/src/ContaoWkSyncBundle.php index 1bf637b..1c401b3 100644 --- a/src/ContaoWkSyncBundle.php +++ b/src/ContaoWkSyncBundle.php @@ -11,6 +11,17 @@ use Terminal42\NotificationCenterBundle\Terminal42NotificationCenterBundle; class ContaoWkSyncBundle extends AbstractBundle { + /** + * Das Backend-Stylesheet (Icon der Navigationsgruppe, Posteingang, Einstellungen) wird über die + * Core-Konfiguration contao.backend.custom_css auf allen Backend-Seiten eingebunden. + */ + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + $builder->prependExtensionConfig('contao', [ + 'backend' => ['custom_css' => ['bundles/contaowksync/wk-sync-backend.css']], + ]); + } + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { $container->import('../config/services.yaml'); diff --git a/src/EventListener/DataContainer/CategoryAliasListener.php b/src/EventListener/DataContainer/CategoryAliasListener.php index 6f81662..69b4072 100644 --- a/src/EventListener/DataContainer/CategoryAliasListener.php +++ b/src/EventListener/DataContainer/CategoryAliasListener.php @@ -8,6 +8,8 @@ use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; use Contao\CoreBundle\Slug\Slug; use Contao\DataContainer; use Doctrine\DBAL\Connection; +use Mummert\ContaoWkSyncBundle\Sync\CalendarConfigRepository; +use Symfony\Component\HttpFoundation\RequestStack; #[AsCallback(table: 'tl_wk_category', target: 'fields.alias.save')] class CategoryAliasListener @@ -15,6 +17,8 @@ class CategoryAliasListener public function __construct( private readonly Slug $slug, private readonly Connection $connection, + private readonly CalendarConfigRepository $calendars, + private readonly RequestStack $requestStack, ) { } @@ -24,7 +28,7 @@ class CategoryAliasListener $exists = fn (string $alias): bool => (int) $this->connection->fetchOne('SELECT COUNT(*) FROM tl_wk_category WHERE alias = ? AND id != ?', [$alias, (int) $dc->id]) > 0; if ('' === $value) { - return $this->slug->generate((string) $dc->getCurrentRecord()['title'], [], $exists); + return $this->slug->generate((string) ($dc->getCurrentRecord()['title'] ?? ''), $this->slugOptions(), $exists); } if ($exists($value)) { @@ -33,4 +37,21 @@ class CategoryAliasListener return $value; } + + /** + * Die Weiterleitungsseite des W&K-Kalenders bestimmt Sprache und erlaubte Zeichen des Alias (deutsche + * Seiten: „ö“ wird „oe“). Ohne Kalender zählt die Sprache des Backend-Benutzers. + * + * @return int|array + */ + private function slugOptions(): int|array + { + foreach ($this->calendars->findEnabled() as $calendar) { + if ($calendar->jumpTo > 0) { + return $calendar->jumpTo; + } + } + + return ['locale' => $this->requestStack->getCurrentRequest()?->getLocale() ?? 'de']; + } } diff --git a/src/EventListener/DataContainer/CategoryLabelListener.php b/src/EventListener/DataContainer/CategoryLabelListener.php index 04e1121..18202e9 100644 --- a/src/EventListener/DataContainer/CategoryLabelListener.php +++ b/src/EventListener/DataContainer/CategoryLabelListener.php @@ -12,23 +12,20 @@ use Contao\StringUtil; /** * Kategorienliste: die W&K-Bezeichnungen (eine pro Zeile) lesbar getrennt anzeigen. * - * Contao 6 behandelt Spaltenwerte aus dem label_callback als Text; HTML muss dort als RecordLabel - * zurückgegeben werden. Contao 5.7 kennt die Klasse nicht und gibt die Spalten unverändert aus. + * Contao behandelt Spaltenwerte aus dem label_callback als Text; HTML wird als RecordLabel zurückgegeben. */ #[AsCallback(table: 'tl_wk_category', target: 'list.label.label')] class CategoryLabelListener { /** - * @param list $args Spaltenwerte in der Reihenfolge von list.label.fields - * - * @return list|RecordLabel + * @param list $args kodierte Spaltenwerte in der Reihenfolge von list.label.fields */ - public function __invoke(array $row, string $label, DataContainer $dc, array $args): array|RecordLabel + public function __invoke(array $row, string $label, DataContainer $dc, array $args): RecordLabel { $names = array_filter(array_map(trim(...), explode("\n", str_replace("\r", '', (string) ($row['wkNames'] ?? ''))))); - $args[2] = implode(' · ', array_map(static fn (string $name): string => StringUtil::specialchars($name, false, false), $names)); + $args[2] = implode(' · ', array_map(StringUtil::specialchars(...), $names)); - return class_exists(RecordLabel::class) ? RecordLabel::fromHtml($args) : $args; + return RecordLabel::fromHtml($args); } } diff --git a/src/EventListener/DataContainer/NotificationNoticeListener.php b/src/EventListener/DataContainer/NotificationNoticeListener.php index efe89c6..9f8b142 100644 --- a/src/EventListener/DataContainer/NotificationNoticeListener.php +++ b/src/EventListener/DataContainer/NotificationNoticeListener.php @@ -4,17 +4,17 @@ declare(strict_types=1); namespace Mummert\ContaoWkSyncBundle\EventListener\DataContainer; -use Contao\CoreBundle\DataContainer\PaletteManipulator; use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; +use Contao\StringUtil; use Mummert\ContaoWkSyncBundle\Booking\BookingNotifierInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** - * Zeigt in den W&K-Einstellungen einen Hinweis, solange es keine Benachrichtigung vom Typ „W&K-Anmeldung“ - * gibt; sonst wären die Auswahlfelder leer, ohne dass man erfährt, warum. + * Anzeigefelder der Legende „Benachrichtigungen“ in den W&K-Einstellungen (input_field_callback, keine + * Spalten): die Erklärung der fest hinterlegten Logik und ein Hinweis, solange keine Benachrichtigung + * zugeordnet werden kann – sonst wären die Auswahlfelder leer, ohne dass man erfährt, warum. */ -#[AsCallback(table: 'tl_wk_config', target: 'config.onload')] class NotificationNoticeListener { public function __construct( @@ -24,23 +24,34 @@ class NotificationNoticeListener ) { } - public function __invoke(): void + #[AsCallback(table: 'tl_wk_config', target: 'fields.notificationOverview.input_field')] + public function renderOverview(): string { - if ([] !== $this->notifier->availableNotifications()) { - PaletteManipulator::create() - ->removeField('notificationNotice') - ->applyToPalette('default', 'tl_wk_config'); + return $this->box('tl_info', $this->translator->trans('tl_wk_config.notificationOverview', [], 'contao_tl_wk_config')); + } - return; + #[AsCallback(table: 'tl_wk_config', target: 'fields.notificationNotice.input_field')] + public function renderNotice(): string + { + if (!$this->notifier->isAvailable()) { + return $this->box('tl_error', $this->translator->trans('tl_wk_config.notificationUnavailable', [], 'contao_tl_wk_config')); + } + + if ([] !== $this->notifier->availableNotifications()) { + return ''; } $url = $this->router->generate('contao_backend', ['do' => 'nc_notifications']); // Contao-Domänen werden per sprintf ersetzt, daher %s statt benannter Platzhalter - $GLOBALS['TL_DCA']['tl_wk_config']['fields']['notificationNotice']['eval']['text'] = $this->translator->trans( - 'tl_wk_config.notificationNotice', - [htmlspecialchars($url, ENT_QUOTES)], - 'contao_tl_wk_config', - ); + return $this->box('tl_error', $this->translator->trans('tl_wk_config.notificationNotice', [StringUtil::specialchars($url)], 'contao_tl_wk_config')); + } + + /** + * Die Texte aus der Sprachdatei sind HTML. + */ + private function box(string $class, string $html): string + { + return \sprintf('

%s
', $class, $html); } } diff --git a/src/EventListener/DataContainer/SetupCheckListener.php b/src/EventListener/DataContainer/SetupCheckListener.php index 786cc23..be313ec 100644 --- a/src/EventListener/DataContainer/SetupCheckListener.php +++ b/src/EventListener/DataContainer/SetupCheckListener.php @@ -4,22 +4,24 @@ declare(strict_types=1); namespace Mummert\ContaoWkSyncBundle\EventListener\DataContainer; -use Contao\CoreBundle\DataContainer\PaletteManipulator; use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; +use Contao\DataContainer; use Contao\StringUtil; use Doctrine\DBAL\Connection; use Mummert\ContaoWkSyncBundle\Booking\BookingFields; use Mummert\ContaoWkSyncBundle\Booking\NotificationRouter; use Mummert\ContaoWkSyncBundle\Controller\FrontendModule\BookingFormController; use Mummert\ContaoWkSyncBundle\Controller\FrontendModule\BookingStatusController; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Contracts\Translation\TranslatorInterface; /** * Prüft in den W&K-Einstellungen, ob die Verkettung stimmt: Felder des Anmeldeformulars, Module auf den * Seiten, Anfrageformular mit Zieltabelle, zugeordnete Benachrichtigungen. + * + * Das Feld „setupCheck“ ist reine Anzeige (input_field_callback): keine Spalte, kein Widget, kein + * virtuelles Feld. */ -#[AsCallback(table: 'tl_wk_config', target: 'config.onload')] +#[AsCallback(table: 'tl_wk_config', target: 'fields.setupCheck.input_field')] class SetupCheckListener { /** @@ -29,22 +31,13 @@ class SetupCheckListener public function __construct( private readonly Connection $connection, - private readonly RequestStack $requestStack, private readonly TranslatorInterface $translator, ) { } - public function __invoke(): void + public function __invoke(DataContainer $dc): string { - $request = $this->requestStack->getCurrentRequest(); - - if (null === $request || 'edit' !== $request->query->get('act') || 'wk_config' !== $request->query->get('do')) { - PaletteManipulator::create()->removeField('setupCheck')->applyToPalette('default', 'tl_wk_config'); - - return; - } - - $config = $this->connection->fetchAssociative('SELECT * FROM tl_wk_config WHERE id = ?', [(int) $request->query->get('id')]) ?: []; + $config = $this->connection->fetchAssociative('SELECT * FROM tl_wk_config WHERE id = ?', [(int) $dc->id]) ?: []; $lines = []; $error = false; $add = function (string $state, string $key, array $params = []) use (&$lines, &$error): void { @@ -130,12 +123,16 @@ class SetupCheckListener $add('ok', 'all_ok'); } - $field = &$GLOBALS['TL_DCA']['tl_wk_config']['fields']['setupCheck']; - $field['eval']['text'] = '
    '.implode('', $lines).'
'; - $field['eval']['boxClass'] = $error ? 'tl_error' : 'tl_confirm'; + return \sprintf( + '
    %s
', + $error ? 'tl_error' : 'tl_confirm', + implode('', $lines), + ); } /** + * Die Texte aus der Sprachdatei sind HTML, die eingesetzten Titel aus der Datenbank werden kodiert. + * * @param list $params */ private function line(string $state, string $key, array $params = []): string diff --git a/src/EventListener/Menu/BackendMenuListener.php b/src/EventListener/Menu/BackendMenuListener.php deleted file mode 100644 index 47dd551..0000000 --- a/src/EventListener/Menu/BackendMenuListener.php +++ /dev/null @@ -1,45 +0,0 @@ -getTree(); - - if ('mainMenu' !== $tree->getName() || null === ($group = $tree->getChild('wk_tools'))) { - return; - } - - $GLOBALS['TL_CSS'][] = 'bundles/contaowksync/wk-sync-menu.css|static'; - - if (!method_exists(BackendUser::class, 'navigation')) { - return; - } - - foreach ($group->getChildren() as $item) { - $title = $item->getLinkAttribute('title'); - - if (\is_string($title) && str_contains($title, '&')) { - $item->setLinkAttribute('title', html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8')); - } - } - } -} diff --git a/src/Export/BookingExporter.php b/src/Export/BookingExporter.php index 50b648f..4f68b8f 100644 --- a/src/Export/BookingExporter.php +++ b/src/Export/BookingExporter.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Mummert\ContaoWkSyncBundle\Export; -use Contao\StringUtil; use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Mummert\ContaoWkSyncBundle\Import\SyncFileStorage; @@ -135,9 +134,6 @@ class BookingExporter */ private function row(array $row, string $wkId): array { - // Contao 5 speichert Formulareingaben kodiert (z. B. ( für eine Klammer), die CSV braucht Klartext - $row = array_map(static fn (mixed $value): mixed => \is_string($value) ? StringUtil::decodeEntities($value) : $value, $row); - $gender = (string) ($row['gender'] ?? ''); $position = (string) ($row['position'] ?? ''); diff --git a/src/Widget/Backend/ExplanationWidget.php b/src/Widget/Backend/ExplanationWidget.php deleted file mode 100644 index b250ce7..0000000 --- a/src/Widget/Backend/ExplanationWidget.php +++ /dev/null @@ -1,32 +0,0 @@ -arrConfiguration['text'] ?? ''); - - if ('' === $text) { - $lang = $GLOBALS['TL_LANG'][$this->strTable][$this->strField] ?? ''; - $text = (string) (\is_array($lang) ? ($lang[0] ?? '') : $lang); - } - - return \sprintf('
%s
', (string) ($this->arrConfiguration['boxClass'] ?? 'tl_info'), $text); - } -}