From f93ed0d0c67b0f3c0b4f9fd685b1e31917f45fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Mummert?= Date: Sat, 21 Feb 2026 22:23:53 +0100 Subject: [PATCH] Refactor type handling to contao-tags and add module tag filters --- composer.json | 11 +- contao/dca/tl_calendar_events.php | 16 +- contao/dca/tl_module.php | 61 +++++++- contao/dca/tl_organization.php | 15 +- contao/languages/de/tl_calendar_events.php | 6 +- contao/languages/de/tl_module.php | 2 + contao/languages/de/tl_organization.php | 6 +- contao/languages/en/tl_calendar_events.php | 6 +- contao/languages/en/tl_module.php | 2 + contao/languages/en/tl_organization.php | 6 +- .../templates/frontend/event_edit.html.twig | 4 +- .../frontend/organization_edit.html.twig | 2 +- .../Frontend/EventEditController.php | 15 +- .../Frontend/OrganizationEditController.php | 14 +- src/Form/EventType.php | 18 +-- src/Form/OrganizationType.php | 22 ++- src/Service/EventRepository.php | 141 +++++++++++++++++- src/Service/OrganizationRepository.php | 129 +++++++++++++++- 18 files changed, 413 insertions(+), 63 deletions(-) diff --git a/composer.json b/composer.json index 0b2acc9..4ac5810 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "require": { "php": "^8.3", "contao/core-bundle": "^5.7", - "contao/manager-plugin": "^2.0" + "contao/manager-plugin": "^2.0", + "numero2/contao-tags": "^0.5" }, "autoload": { "psr-4": { @@ -21,5 +22,11 @@ "extra": { "contao-manager-plugin": "MummertMedia\\EventManagerBundle\\Contao\\Manager\\Plugin" }, - "prefer-stable": true + "prefer-stable": true, + "config": { + "allow-plugins": { + "contao-components/installer": true, + "contao/manager-plugin": true + } + } } diff --git a/contao/dca/tl_calendar_events.php b/contao/dca/tl_calendar_events.php index a64eb75..222d224 100644 --- a/contao/dca/tl_calendar_events.php +++ b/contao/dca/tl_calendar_events.php @@ -22,7 +22,7 @@ $GLOBALS['TL_DCA']['tl_calendar_events']['config']['onload_callback'][] = static PaletteManipulator::create() ->addLegend('organization_legend', 'details_legend', PaletteManipulator::POSITION_AFTER) - ->addField(['location_id', 'type', 'organizations'], 'organization_legend', PaletteManipulator::POSITION_APPEND) + ->addField(['location_id', 'tags', 'organizations'], 'organization_legend', PaletteManipulator::POSITION_APPEND) ->applyToPalette((string) $paletteName, 'tl_calendar_events'); PaletteManipulator::create() @@ -71,14 +71,18 @@ $GLOBALS['TL_DCA']['tl_calendar_events']['fields']['location_id'] = [ 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], ]; -$GLOBALS['TL_DCA']['tl_calendar_events']['fields']['type'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['type'], +$GLOBALS['TL_DCA']['tl_calendar_events']['fields']['tags'] = [ + 'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['tags'], 'exclude' => true, + 'filter' => true, 'inputType' => 'select', - 'options' => ['accommodation', 'shopping', 'culture'], - 'reference' => &$GLOBALS['TL_LANG']['tl_calendar_events']['type_options'], - 'eval' => ['multiple' => true, 'chosen' => true, 'includeBlankOption' => false, 'tl_class' => 'w50'], + 'foreignKey' => 'tl_tags.tag', + 'options_callback' => ['numero2_tags.listener.data_container.tags', 'getTagOptions'], + 'load_callback' => [['numero2_tags.listener.data_container.tags', 'loadTags']], + 'save_callback' => [['numero2_tags.listener.data_container.tags', 'saveTags']], + 'eval' => ['multiple' => true, 'size' => 8, 'tl_class' => 'w50 tags', 'chosen' => true, 'groupTagsByField' => true, 'tagGroup' => 'event_type'], 'sql' => ['type' => 'blob', 'notnull' => false], + 'relation' => ['type' => 'hasMany', 'load' => 'eager'], ]; $GLOBALS['TL_DCA']['tl_calendar_events']['fields']['organizations'] = [ diff --git a/contao/dca/tl_module.php b/contao/dca/tl_module.php index 3498f24..66314f4 100644 --- a/contao/dca/tl_module.php +++ b/contao/dca/tl_module.php @@ -2,10 +2,13 @@ declare(strict_types=1); +use Contao\Database; +use Contao\StringUtil; + $GLOBALS['TL_DCA']['tl_module']['palettes']['member_organizations'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; $GLOBALS['TL_DCA']['tl_module']['palettes']['member_events'] = '{title_legend},name,headline,type;{eventmanager_legend},editPage;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; -$GLOBALS['TL_DCA']['tl_module']['palettes']['organization_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,logoFolder;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; -$GLOBALS['TL_DCA']['tl_module']['palettes']['event_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,eventFolder,termsPage,frontendAuthorId,frontendArchiveId;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; +$GLOBALS['TL_DCA']['tl_module']['palettes']['organization_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,logoFolder,organizationTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; +$GLOBALS['TL_DCA']['tl_module']['palettes']['event_edit'] = '{title_legend},name,headline,type;{eventmanager_legend},listPage,eventFolder,termsPage,frontendAuthorId,frontendArchiveId,eventTypeTags;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID'; $GLOBALS['TL_DCA']['tl_module']['fields']['editPage'] = [ 'label' => &$GLOBALS['TL_LANG']['tl_module']['editPage'], @@ -62,3 +65,57 @@ $GLOBALS['TL_DCA']['tl_module']['fields']['frontendArchiveId'] = [ 'eval' => ['mandatory' => true, 'rgxp' => 'digit', 'maxlength' => 10, 'tl_class' => 'w50'], 'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 0], ]; + +$GLOBALS['TL_DCA']['tl_module']['fields']['organizationTypeTags'] = [ + 'label' => &$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'], + 'exclude' => true, + 'inputType' => 'checkbox', + 'options_callback' => static function () { + $rows = Database::getInstance() + ->prepare('SELECT DISTINCT t.id, t.tag FROM tl_tags t LEFT JOIN tl_tags_rel r ON r.tag_id=t.id AND r.ptable=? AND r.field=? ORDER BY t.tag ASC') + ->execute('tl_organization', 'tags') + ->fetchAllAssoc(); + + $options = []; + + foreach ($rows as $row) { + $options[(int) $row['id']] = (string) $row['tag']; + } + + return $options; + }, + 'eval' => ['multiple' => true, 'tl_class' => 'clr'], + 'sql' => ['type' => 'blob', 'notnull' => false], + 'save_callback' => [ + static function ($value): array { + return array_values(array_unique(array_map('intval', StringUtil::deserialize($value, true)))); + }, + ], +]; + +$GLOBALS['TL_DCA']['tl_module']['fields']['eventTypeTags'] = [ + 'label' => &$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'], + 'exclude' => true, + 'inputType' => 'checkbox', + 'options_callback' => static function () { + $rows = Database::getInstance() + ->prepare('SELECT DISTINCT t.id, t.tag FROM tl_tags t LEFT JOIN tl_tags_rel r ON r.tag_id=t.id AND r.ptable=? AND r.field=? ORDER BY t.tag ASC') + ->execute('tl_calendar_events', 'tags') + ->fetchAllAssoc(); + + $options = []; + + foreach ($rows as $row) { + $options[(int) $row['id']] = (string) $row['tag']; + } + + return $options; + }, + 'eval' => ['multiple' => true, 'tl_class' => 'clr'], + 'sql' => ['type' => 'blob', 'notnull' => false], + 'save_callback' => [ + static function ($value): array { + return array_values(array_unique(array_map('intval', StringUtil::deserialize($value, true)))); + }, + ], +]; diff --git a/contao/dca/tl_organization.php b/contao/dca/tl_organization.php index c8c2bf1..7da9ff0 100644 --- a/contao/dca/tl_organization.php +++ b/contao/dca/tl_organization.php @@ -70,7 +70,7 @@ $GLOBALS['TL_DCA']['tl_organization'] = [ ], 'palettes' => [ '__selector__' => [], - 'default' => '{title_legend},title,alias,type,isExternal,logo;{address_legend},street,street2,postal,city,state,country;{contact_legend},phone,email,website;{geo_legend},lat,lng;{description_legend},description;{relation_legend},members;{publish_legend},published', + 'default' => '{title_legend},title,alias,tags,isExternal,logo;{address_legend},street,street2,postal,city,state,country;{contact_legend},phone,email,website;{geo_legend},lat,lng;{description_legend},description;{relation_legend},members;{publish_legend},published', ], 'fields' => [ 'id' => [ @@ -211,14 +211,17 @@ $GLOBALS['TL_DCA']['tl_organization'] = [ 'eval' => ['tl_class' => 'w50'], 'sql' => ['type' => 'string', 'length' => 1, 'fixed' => true, 'default' => ''], ], - 'type' => [ - 'label' => &$GLOBALS['TL_LANG']['tl_organization']['type'], + 'tags' => [ + 'label' => &$GLOBALS['TL_LANG']['tl_organization']['tags'], 'exclude' => true, 'inputType' => 'select', - 'options' => ['accommodation', 'shopping', 'culture'], - 'reference' => &$GLOBALS['TL_LANG']['tl_organization']['type_options'], - 'eval' => ['multiple' => true, 'chosen' => true, 'includeBlankOption' => false, 'tl_class' => 'w50'], + 'foreignKey' => 'tl_tags.tag', + 'options_callback' => ['numero2_tags.listener.data_container.tags', 'getTagOptions'], + 'load_callback' => [['numero2_tags.listener.data_container.tags', 'loadTags']], + 'save_callback' => [['numero2_tags.listener.data_container.tags', 'saveTags']], + 'eval' => ['multiple' => true, 'size' => 8, 'tl_class' => 'w50 tags', 'chosen' => true, 'groupTagsByField' => true, 'tagGroup' => 'organization_type'], 'sql' => ['type' => 'blob', 'notnull' => false], + 'relation' => ['type' => 'hasMany', 'load' => 'eager'], ], 'members' => [ 'label' => &$GLOBALS['TL_LANG']['tl_organization']['members'], diff --git a/contao/languages/de/tl_calendar_events.php b/contao/languages/de/tl_calendar_events.php index 731ac3b..d92ab52 100644 --- a/contao/languages/de/tl_calendar_events.php +++ b/contao/languages/de/tl_calendar_events.php @@ -4,13 +4,9 @@ declare(strict_types=1); $GLOBALS['TL_LANG']['tl_calendar_events']['organization_legend'] = 'Organisationen und Ort'; $GLOBALS['TL_LANG']['tl_calendar_events']['location_id'] = ['Veranstaltungsort', 'Zugeordneter Veranstaltungsort']; -$GLOBALS['TL_LANG']['tl_calendar_events']['type'] = ['Typ', 'Mehrere Typen auswählbar.']; +$GLOBALS['TL_LANG']['tl_calendar_events']['tags'] = ['Typen', 'Vorhandene Tags für Veranstaltungstypen auswählen.']; $GLOBALS['TL_LANG']['tl_calendar_events']['organizations'] = ['Organisationen', 'Zugeordnete Organisationen']; $GLOBALS['TL_LANG']['tl_calendar_events']['photographer'] = ['Urheber/Fotograf', 'Die Angabe des Urhebers ist notwendig. Ihnen muss eine Genehmigung zur Verwendung des Bildes vorliegen.']; $GLOBALS['TL_LANG']['tl_calendar_events']['isSoldOut'] = ['Ausverkauft', 'Diese Veranstaltung ist ausverkauft.']; $GLOBALS['TL_LANG']['tl_calendar_events']['isCanceled'] = ['Abgesagt', 'Diese Veranstaltung wurde abgesagt.']; $GLOBALS['TL_LANG']['tl_calendar_events']['termsAccepted'] = ['Nutzungsbedingungen akzeptiert', 'Die Nutzungsbedingungen wurden akzeptiert.']; - -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['accommodation'] = 'Unterkunft'; -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['shopping'] = 'Shopping'; -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['culture'] = 'Kultur'; diff --git a/contao/languages/de/tl_module.php b/contao/languages/de/tl_module.php index 7edab04..7da9b6b 100644 --- a/contao/languages/de/tl_module.php +++ b/contao/languages/de/tl_module.php @@ -10,3 +10,5 @@ $GLOBALS['TL_LANG']['tl_module']['eventFolder'] = ['Event-Ordner', 'Bitte wähle $GLOBALS['TL_LANG']['tl_module']['termsPage'] = ['Seite mit Nutzungsbedingungen', 'Optional: Seite mit den Nutzungsbedingungen, die im Frontend beim Zustimmungs-Label verlinkt wird.']; $GLOBALS['TL_LANG']['tl_module']['frontendAuthorId'] = ['Backend Benutzer ID', 'ID des Backend-Benutzers, der als Autor für frontendseitig angelegte Events gesetzt wird.']; $GLOBALS['TL_LANG']['tl_module']['frontendArchiveId'] = ['ID des Newsarchivs', 'Archiv-ID (pid), in das frontendseitig angelegte Events gespeichert werden.']; +$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Anzuzeigende Organisationstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Organisationstyp-Tags. Leer = alle.']; +$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Anzuzeigende Veranstaltungstypen', 'Optional: Begrenzen Sie die im Frontend anzeigbaren Veranstaltungstyp-Tags. Leer = alle.']; diff --git a/contao/languages/de/tl_organization.php b/contao/languages/de/tl_organization.php index edccdda..5cea18e 100644 --- a/contao/languages/de/tl_organization.php +++ b/contao/languages/de/tl_organization.php @@ -19,13 +19,9 @@ $GLOBALS['TL_LANG']['tl_organization']['lng'] = ['Längengrad', 'Längengrad']; $GLOBALS['TL_LANG']['tl_organization']['description'] = ['Beschreibung', 'Beschreibung']; $GLOBALS['TL_LANG']['tl_organization']['published'] = ['Veröffentlicht', 'Organisation veröffentlichen']; $GLOBALS['TL_LANG']['tl_organization']['isExternal'] = ['Extern', 'Externe Organisation']; -$GLOBALS['TL_LANG']['tl_organization']['type'] = ['Typ', 'Mehrere Typen auswählbar.']; +$GLOBALS['TL_LANG']['tl_organization']['tags'] = ['Typen', 'Vorhandene Tags für Organisationstypen auswählen.']; $GLOBALS['TL_LANG']['tl_organization']['members'] = ['Mitglieder', 'Zugeordnete Mitglieder']; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['accommodation'] = 'Unterkunft'; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['shopping'] = 'Shopping'; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['culture'] = 'Kultur'; - $GLOBALS['TL_LANG']['tl_organization']['title_legend'] = 'Titel'; $GLOBALS['TL_LANG']['tl_organization']['address_legend'] = 'Adresse'; $GLOBALS['TL_LANG']['tl_organization']['contact_legend'] = 'Kontakt'; diff --git a/contao/languages/en/tl_calendar_events.php b/contao/languages/en/tl_calendar_events.php index aedbb02..61f0a11 100644 --- a/contao/languages/en/tl_calendar_events.php +++ b/contao/languages/en/tl_calendar_events.php @@ -4,13 +4,9 @@ declare(strict_types=1); $GLOBALS['TL_LANG']['tl_calendar_events']['organization_legend'] = 'Organizations and location'; $GLOBALS['TL_LANG']['tl_calendar_events']['location_id'] = ['Location', 'Assigned location']; -$GLOBALS['TL_LANG']['tl_calendar_events']['type'] = ['Type', 'Multiple types can be selected.']; +$GLOBALS['TL_LANG']['tl_calendar_events']['tags'] = ['Types', 'Select existing tags for event types.']; $GLOBALS['TL_LANG']['tl_calendar_events']['organizations'] = ['Organizations', 'Assigned organizations']; $GLOBALS['TL_LANG']['tl_calendar_events']['photographer'] = ['Author/Photographer', 'Please provide the image author/photographer and make sure you have permission to use the image.']; $GLOBALS['TL_LANG']['tl_calendar_events']['isSoldOut'] = ['Sold out', 'This event is sold out.']; $GLOBALS['TL_LANG']['tl_calendar_events']['isCanceled'] = ['Canceled', 'This event has been canceled.']; $GLOBALS['TL_LANG']['tl_calendar_events']['termsAccepted'] = ['Terms accepted', 'The terms of use have been accepted.']; - -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['accommodation'] = 'Accommodation'; -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['shopping'] = 'Shopping'; -$GLOBALS['TL_LANG']['tl_calendar_events']['type_options']['culture'] = 'Culture'; diff --git a/contao/languages/en/tl_module.php b/contao/languages/en/tl_module.php index c6c252e..277d96e 100644 --- a/contao/languages/en/tl_module.php +++ b/contao/languages/en/tl_module.php @@ -10,3 +10,5 @@ $GLOBALS['TL_LANG']['tl_module']['eventFolder'] = ['Event folder', 'Please selec $GLOBALS['TL_LANG']['tl_module']['termsPage'] = ['Terms page', 'Optional: page containing the terms of use linked from the frontend consent label.']; $GLOBALS['TL_LANG']['tl_module']['frontendAuthorId'] = ['Backend user ID', 'Backend user ID that should be set as author for events created from the frontend.']; $GLOBALS['TL_LANG']['tl_module']['frontendArchiveId'] = ['News archive ID', 'Archive ID (pid) where events created from the frontend should be stored.']; +$GLOBALS['TL_LANG']['tl_module']['organizationTypeTags'] = ['Displayed organization types', 'Optional: Limit which organization type tags can be selected in the frontend. Empty = all.']; +$GLOBALS['TL_LANG']['tl_module']['eventTypeTags'] = ['Displayed event types', 'Optional: Limit which event type tags can be selected in the frontend. Empty = all.']; diff --git a/contao/languages/en/tl_organization.php b/contao/languages/en/tl_organization.php index bd4dc2e..c95a616 100644 --- a/contao/languages/en/tl_organization.php +++ b/contao/languages/en/tl_organization.php @@ -19,13 +19,9 @@ $GLOBALS['TL_LANG']['tl_organization']['lng'] = ['Longitude', 'Longitude']; $GLOBALS['TL_LANG']['tl_organization']['description'] = ['Description', 'Description']; $GLOBALS['TL_LANG']['tl_organization']['published'] = ['Published', 'Publish organization']; $GLOBALS['TL_LANG']['tl_organization']['isExternal'] = ['External', 'External organization']; -$GLOBALS['TL_LANG']['tl_organization']['type'] = ['Type', 'Multiple types can be selected.']; +$GLOBALS['TL_LANG']['tl_organization']['tags'] = ['Types', 'Select existing tags for organization types.']; $GLOBALS['TL_LANG']['tl_organization']['members'] = ['Members', 'Assigned members']; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['accommodation'] = 'Accommodation'; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['shopping'] = 'Shopping'; -$GLOBALS['TL_LANG']['tl_organization']['type_options']['culture'] = 'Culture'; - $GLOBALS['TL_LANG']['tl_organization']['title_legend'] = 'Title'; $GLOBALS['TL_LANG']['tl_organization']['address_legend'] = 'Address'; $GLOBALS['TL_LANG']['tl_organization']['contact_legend'] = 'Contact'; diff --git a/contao/templates/frontend/event_edit.html.twig b/contao/templates/frontend/event_edit.html.twig index e0753c5..16d6351 100644 --- a/contao/templates/frontend/event_edit.html.twig +++ b/contao/templates/frontend/event_edit.html.twig @@ -32,7 +32,7 @@ {{ form_row(form.organization_ids) }} {% endif %} {{ form_row(form.location_id) }} - {{ form_row(form.type) }} + {{ form_row(form.tags) }} {{ form_row(form.teaser) }} {{ form_row(form.description) }} {{ form_row(form.url) }} @@ -59,7 +59,7 @@ (function () { const locationSelect = document.querySelector('select.js-location-choice'); const organizationSelect = document.querySelector('select.js-organization-choice'); - const typeSelect = document.querySelector('select.js-event-type-choice'); + const typeSelect = document.querySelector('select.js-event-tags-choice'); if (organizationSelect && typeof window.Choices === 'function') { new window.Choices(organizationSelect, { diff --git a/contao/templates/frontend/organization_edit.html.twig b/contao/templates/frontend/organization_edit.html.twig index e2a4dcd..df29c5b 100644 --- a/contao/templates/frontend/organization_edit.html.twig +++ b/contao/templates/frontend/organization_edit.html.twig @@ -25,7 +25,7 @@