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 <noreply@anthropic.com>
This commit is contained in:
1 parent
27375e8d66
commit
4d4f21b7c8
26 files changed
+132
-174
No files matched your search
@@ -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.<feld>.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.
|
||||
+3
-3
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
@@ -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',
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -56,6 +56,8 @@ $lang['notificationOverview'] = '<p><strong>So wird die Benachrichtigung für ei
|
||||
</tbody>
|
||||
</table>';
|
||||
|
||||
$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 <a href="%s">Notification Center → Benachrichtigungen</a> 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.'];
|
||||
|
||||
@@ -56,6 +56,8 @@ $lang['notificationOverview'] = '<p><strong>How the notification for a booking i
|
||||
</tbody>
|
||||
</table>';
|
||||
|
||||
$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 <a href="%s">Notification Center → Notifications</a> 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.'];
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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); }
|
||||
@@ -17,4 +17,9 @@ interface BookingNotifierInterface
|
||||
* @return array<int, string> ID => Titel der verfügbaren Benachrichtigungen
|
||||
*/
|
||||
public function availableNotifications(): array;
|
||||
|
||||
/**
|
||||
* Ob überhaupt Benachrichtigungen verschickt werden können (Notification Center installiert).
|
||||
*/
|
||||
public function isAvailable(): bool;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -51,4 +51,9 @@ class NotificationCenterNotifier implements BookingNotifierInterface
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,9 @@ class NullNotifier implements BookingNotifierInterface
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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<string, string>
|
||||
*/
|
||||
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'];
|
||||
}
|
||||
}
|
||||
@@ -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<string> $args Spaltenwerte in der Reihenfolge von list.label.fields
|
||||
*
|
||||
* @return list<string>|RecordLabel
|
||||
* @param list<string> $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('<span class="tl_gray"> · </span>', array_map(static fn (string $name): string => StringUtil::specialchars($name, false, false), $names));
|
||||
$args[2] = implode('<span class="tl_gray"> · </span>', array_map(StringUtil::specialchars(...), $names));
|
||||
|
||||
return class_exists(RecordLabel::class) ? RecordLabel::fromHtml($args) : $args;
|
||||
return RecordLabel::fromHtml($args);
|
||||
}
|
||||
}
|
||||
@@ -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('<div class="widget clr"><div class="%s wk-sync-explanation">%s</div></div>', $class, $html);
|
||||
}
|
||||
}
|
||||
@@ -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'] = '<ul class="wk-sync-check">'.implode('', $lines).'</ul>';
|
||||
$field['eval']['boxClass'] = $error ? 'tl_error' : 'tl_confirm';
|
||||
return \sprintf(
|
||||
'<div class="widget clr"><div class="%s wk-sync-explanation"><ul class="wk-sync-check">%s</ul></div></div>',
|
||||
$error ? 'tl_error' : 'tl_confirm',
|
||||
implode('', $lines),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Texte aus der Sprachdatei sind HTML, die eingesetzten Titel aus der Datenbank werden kodiert.
|
||||
*
|
||||
* @param list<string> $params
|
||||
*/
|
||||
private function line(string $state, string $key, array $params = []): string
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\ContaoWkSyncBundle\EventListener\Menu;
|
||||
|
||||
use Contao\BackendUser;
|
||||
use Contao\CoreBundle\Event\MenuEvent;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
|
||||
/**
|
||||
* Backend-Navigation der Gruppe „W&K-Tools“: Gruppen-Icon einbinden und die Tooltips korrigieren.
|
||||
*
|
||||
* Contao 5 escaped die Modul-Beschreibungen für den Tooltip bereits in BackendUser::navigation()
|
||||
* (StringUtil::specialchars) und das Menü-Template escaped das title-Attribut ein zweites Mal, sodass
|
||||
* aus „W&K“ im Tooltip „W&K“ wird. Der Core-Listener läuft mit Priorität 10; dieser Listener nimmt
|
||||
* die erste Escapierung für die W&K-Module zurück. Contao 6 baut die Navigation ohne
|
||||
* BackendUser::navigation() und escaped nur noch einmal, dort bleibt der Tooltip unverändert.
|
||||
*/
|
||||
#[AsEventListener]
|
||||
class BackendMenuListener
|
||||
{
|
||||
public function __invoke(MenuEvent $event): void
|
||||
{
|
||||
$tree = $event->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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'] ?? '');
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\ContaoWkSyncBundle\Widget\Backend;
|
||||
|
||||
use Contao\Widget;
|
||||
|
||||
/**
|
||||
* Erklärungstext im Backend-Formular (Contao kennt „explanation“ nur im Frontend-Formulargenerator).
|
||||
*
|
||||
* Der Text kommt aus eval.text oder, wenn leer, aus $GLOBALS['TL_LANG'][Tabelle][Feld]; eval.boxClass
|
||||
* bestimmt die Box (Standard tl_info, alternativ z. B. tl_error). Das Feld wird nicht gespeichert.
|
||||
*/
|
||||
class ExplanationWidget extends Widget
|
||||
{
|
||||
protected $blnSubmitInput = false;
|
||||
|
||||
protected $strTemplate = 'be_widget';
|
||||
|
||||
public function generate(): string
|
||||
{
|
||||
$text = (string) ($this->arrConfiguration['text'] ?? '');
|
||||
|
||||
if ('' === $text) {
|
||||
$lang = $GLOBALS['TL_LANG'][$this->strTable][$this->strField] ?? '';
|
||||
$text = (string) (\is_array($lang) ? ($lang[0] ?? '') : $lang);
|
||||
}
|
||||
|
||||
return \sprintf('<div class="%s wk-sync-explanation">%s</div>', (string) ($this->arrConfiguration['boxClass'] ?? 'tl_info'), $text);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user