Refactor type handling to contao-tags and add module tag filters

This commit is contained in:
Jürgen Mummert
2026-02-21 22:23:53 +01:00
parent a6440c74a2
commit f93ed0d0c6
18 changed files with 413 additions and 63 deletions
@@ -73,7 +73,7 @@ class EventEditController extends AbstractFrontendModuleController
'startTime' => null,
'endTime' => null,
'location_id' => 0,
'type' => null,
'tags' => null,
'teaser' => '',
'description' => '',
'url' => '',
@@ -88,9 +88,11 @@ class EventEditController extends AbstractFrontendModuleController
}
$memberOrganizationIds = $this->eventRepository->getOrganizationIdsForMember((int) $user->id);
$allowedEventTagIds = array_values(array_unique(array_map('intval', StringUtil::deserialize($model->eventTypeTags ?? null, true))));
$organizationChoices = $this->eventRepository->getOrganizationChoicesForMember((int) $user->id);
$showOrganization = count($memberOrganizationIds) > 1;
$currentOrganizationIds = $this->eventRepository->getOrganizationIdsForEvent($eventId);
$currentTagIds = $this->eventRepository->getTagIdsForEvent($eventId);
$formData = [
'title' => (string) ($event['title'] ?? ''),
@@ -100,7 +102,7 @@ class EventEditController extends AbstractFrontendModuleController
'startTime' => ('1' === (string) ($event['addTime'] ?? '') && !empty($event['startTime'])) ? date('H:i', (int) $event['startTime']) : '',
'endTime' => $this->resolveFormEndTime($event),
'location_id' => (int) ($event['location_id'] ?? 0),
'type' => StringUtil::deserialize($event['type'] ?? null, true),
'tags' => $currentTagIds,
'teaser' => (string) ($event['teaser'] ?? ''),
'description' => (string) ($event['description'] ?? ''),
'url' => (string) ($event['url'] ?? ''),
@@ -135,6 +137,7 @@ class EventEditController extends AbstractFrontendModuleController
$form = $this->createForm(EventType::class, $formData, [
'location_choices' => $this->eventRepository->getLocationChoices(),
'tag_choices' => $this->eventRepository->getTagChoicesForEventType($allowedEventTagIds),
'organization_choices' => $organizationChoices,
'selected_organization_ids' => $showOrganization ? ($formData['organization_ids'] ?? []) : [],
'show_organization' => $showOrganization,
@@ -213,6 +216,14 @@ class EventEditController extends AbstractFrontendModuleController
$this->eventRepository->update($eventId, $submittedData);
}
$selectedTagIds = array_values(array_unique(array_map('intval', (array) ($submittedData['tags'] ?? []))));
if ([] !== $allowedEventTagIds) {
$selectedTagIds = array_values(array_intersect($selectedTagIds, $allowedEventTagIds));
}
$this->eventRepository->assignTagsToEvent($eventId, $selectedTagIds);
$useImage = !empty($submittedData['addImage']);
$removeImage = '1' === (string) $request->request->get('remove_image', '0');
$uploadedImage = $form->get('eventUpload')->getData();
@@ -58,6 +58,7 @@ class OrganizationEditController extends AbstractFrontendModuleController
}
$organization = $this->organizationRepository->findById($organizationId);
$allowedOrganizationTagIds = array_values(array_unique(array_map('intval', StringUtil::deserialize($model->organizationTypeTags ?? null, true))));
if (null === $organization) {
$template->set('error', 'Organisation nicht gefunden.');
@@ -77,7 +78,7 @@ class OrganizationEditController extends AbstractFrontendModuleController
'email' => (string) ($organization['email'] ?? ''),
'website' => (string) ($organization['website'] ?? ''),
'description' => (string) ($organization['description'] ?? ''),
'type' => StringUtil::deserialize($organization['type'] ?? null, true),
'tags' => $this->organizationRepository->getTagIdsForOrganization($organizationId),
];
$currentLogoPath = null;
@@ -90,13 +91,22 @@ class OrganizationEditController extends AbstractFrontendModuleController
}
}
$form = $this->createForm(OrganizationType::class, $formData);
$form = $this->createForm(OrganizationType::class, $formData, [
'tag_choices' => $this->organizationRepository->getTagChoicesForOrganizationType($allowedOrganizationTagIds),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$submittedData = (array) $form->getData();
$this->organizationRepository->update($organizationId, $submittedData);
$selectedTagIds = array_values(array_unique(array_map('intval', (array) ($submittedData['tags'] ?? []))));
if ([] !== $allowedOrganizationTagIds) {
$selectedTagIds = array_values(array_intersect($selectedTagIds, $allowedOrganizationTagIds));
}
$this->organizationRepository->assignTagsToOrganization($organizationId, $selectedTagIds);
$deleteLogo = '1' === (string) $request->request->get('remove_logo', '0');
$uploadedLogo = $form->get('logoUpload')->getData();
+8 -10
View File
@@ -39,7 +39,7 @@ class EventType extends AbstractType
])
->add('startDate', DateType::class, [
'label' => 'Startdatum',
'required' => false,
'required' => true,
'widget' => 'single_text',
'input' => 'string',
'html5' => false,
@@ -110,7 +110,7 @@ class EventType extends AbstractType
->add('location_id', ChoiceType::class, [
'label' => 'Veranstaltungsort',
'choices' => $options['location_choices'],
'required' => false,
'required' => true,
'choice_value' => static fn ($value) => null !== $value ? (string) $value : '',
'placeholder' => 'Bitte auswählen',
'attr' => [
@@ -118,17 +118,13 @@ class EventType extends AbstractType
'data-placeholder' => 'Veranstaltungsort suchen …',
],
])
->add('type', ChoiceType::class, [
'label' => 'Typ',
'choices' => [
'Unterkunft' => 'accommodation',
'Einkaufen' => 'shopping',
'Kultur' => 'culture',
],
->add('tags', ChoiceType::class, [
'label' => 'Typen',
'choices' => $options['tag_choices'],
'multiple' => true,
'required' => false,
'attr' => [
'class' => 'js-event-type-choice',
'class' => 'js-event-tags-choice',
'data-placeholder' => 'Typen suchen …',
],
])
@@ -191,6 +187,7 @@ class EventType extends AbstractType
$resolver->setDefaults([
'csrf_protection' => true,
'location_choices' => [],
'tag_choices' => [],
'organization_choices' => [],
'selected_organization_ids' => [],
'show_organization' => false,
@@ -198,6 +195,7 @@ class EventType extends AbstractType
]);
$resolver->setAllowedTypes('location_choices', 'array');
$resolver->setAllowedTypes('tag_choices', 'array');
$resolver->setAllowedTypes('organization_choices', 'array');
$resolver->setAllowedTypes('selected_organization_ids', 'array');
$resolver->setAllowedTypes('show_organization', 'bool');
+14 -8
View File
@@ -11,6 +11,7 @@ use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrganizationType extends AbstractType
{
@@ -27,17 +28,13 @@ class OrganizationType extends AbstractType
->add('email', EmailType::class, ['label' => 'E-Mail', 'required' => false])
->add('website', TextType::class, ['label' => 'Webseite', 'required' => false])
->add('description', TextareaType::class, ['label' => 'Beschreibung', 'required' => false])
->add('type', ChoiceType::class, [
'label' => 'Typ',
'choices' => [
'Unterkunft' => 'accommodation',
'Einkaufen' => 'shopping',
'Kultur' => 'culture',
],
->add('tags', ChoiceType::class, [
'label' => 'Typen',
'choices' => $options['tag_choices'],
'multiple' => true,
'required' => false,
'attr' => [
'class' => 'js-organization-type-choice',
'class' => 'js-organization-tags-choice',
'data-placeholder' => 'Typ auswählen …',
],
])
@@ -51,4 +48,13 @@ class OrganizationType extends AbstractType
],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'tag_choices' => [],
]);
$resolver->setAllowedTypes('tag_choices', 'array');
}
}
+140 -1
View File
@@ -131,6 +131,12 @@ class EventRepository
);
}
$tagIds = $this->getTagIdsForEvent($eventId);
if ([] !== $tagIds) {
$this->assignTagsToEvent($newEventId, $tagIds);
}
return $newEventId;
}
@@ -257,6 +263,12 @@ class EventRepository
public function delete(int $eventId): void
{
$this->connection->delete(
'tl_tags_rel',
['ptable' => 'tl_calendar_events', 'field' => 'tags', 'pid' => $eventId],
['pid' => ParameterType::INTEGER],
);
$this->connection->delete(
'tl_calendar_events_organization',
['event_id' => $eventId],
@@ -324,6 +336,73 @@ class EventRepository
return array_values(array_unique(array_map('intval', $rows)));
}
/**
* @return array<string, int>
*/
public function getTagChoicesForEventType(array $allowedTagIds = []): array
{
$rows = $this->connection->createQueryBuilder()
->select('DISTINCT t.id', 't.tag')
->from('tl_tags', 't')
->innerJoin('t', 'tl_tags_rel', 'r', 'r.tag_id = t.id')
->where('r.ptable = :ptable')
->andWhere('r.field = :field')
->setParameter('ptable', 'tl_calendar_events')
->setParameter('field', 'tags')
->orderBy('t.tag', 'ASC')
->executeQuery()
->fetchAllAssociative();
if ([] === $rows) {
$rows = $this->connection->createQueryBuilder()
->select('t.id', 't.tag')
->from('tl_tags', 't')
->orderBy('t.tag', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
$choices = [];
foreach ($rows as $row) {
$choices[(string) ($row['tag'] ?? '')] = (int) $row['id'];
}
$allowedTagIds = array_values(array_unique(array_map('intval', $allowedTagIds)));
if ([] !== $allowedTagIds) {
$choices = array_filter(
$choices,
static fn (int $id): bool => in_array($id, $allowedTagIds, true),
);
}
return $choices;
}
/** @return array<int> */
public function getTagIdsForEvent(int $eventId): array
{
if ($eventId <= 0) {
return [];
}
$ids = $this->connection->createQueryBuilder()
->select('tag_id')
->from('tl_tags_rel')
->where('ptable = :ptable')
->andWhere('field = :field')
->andWhere('pid = :pid')
->setParameter('ptable', 'tl_calendar_events')
->setParameter('field', 'tags')
->setParameter('pid', $eventId, ParameterType::INTEGER)
->orderBy('tag_id', 'ASC')
->executeQuery()
->fetchFirstColumn();
return array_values(array_unique(array_map('intval', $ids)));
}
/**
* @return array<string, int>
*/
@@ -496,6 +575,41 @@ class EventRepository
return $eventId;
}
/** @param array<int|string> $tagIds */
public function assignTagsToEvent(int $eventId, array $tagIds): void
{
$this->connection->delete(
'tl_tags_rel',
['ptable' => 'tl_calendar_events', 'field' => 'tags', 'pid' => $eventId],
['pid' => ParameterType::INTEGER],
);
$tagIds = array_values(array_unique(array_map('intval', $tagIds)));
if ([] === $tagIds) {
return;
}
$allowedTagIds = $this->getAllowedTagIdsForEvents();
$tagIds = array_values(array_intersect($tagIds, $allowedTagIds));
foreach ($tagIds as $tagId) {
$this->connection->insert(
'tl_tags_rel',
[
'tag_id' => $tagId,
'pid' => $eventId,
'ptable' => 'tl_calendar_events',
'field' => 'tags',
],
[
'tag_id' => ParameterType::INTEGER,
'pid' => ParameterType::INTEGER,
],
);
}
}
/**
* @param array<string, mixed> $data
*
@@ -533,7 +647,6 @@ class EventRepository
'addTime' => $addTime ? 1 : 0,
'startTime' => $startTimeTimestamp,
'endTime' => $endTimeTimestamp,
'type' => serialize($data['type'] ?? []),
'teaser' => $data['teaser'] ?? null,
'description' => $data['description'] ?? null,
'url' => $url,
@@ -616,4 +729,30 @@ class EventRepository
$types,
);
}
/** @return array<int> */
private function getAllowedTagIdsForEvents(): array
{
$ids = $this->connection->createQueryBuilder()
->select('DISTINCT r.tag_id')
->from('tl_tags_rel', 'r')
->where('r.ptable = :ptable')
->andWhere('r.field = :field')
->setParameter('ptable', 'tl_calendar_events')
->setParameter('field', 'tags')
->executeQuery()
->fetchFirstColumn();
$ids = array_values(array_unique(array_map('intval', $ids)));
if ([] !== $ids) {
return $ids;
}
return array_values(array_unique(array_map('intval', $this->connection->createQueryBuilder()
->select('id')
->from('tl_tags')
->executeQuery()
->fetchFirstColumn())));
}
}
+128 -1
View File
@@ -79,7 +79,6 @@ class OrganizationRepository
'email' => $data['email'] ?? '',
'website' => $data['website'] ?? '',
'description' => $data['description'] ?? null,
'type' => serialize($data['type'] ?? []),
'tstamp' => time(),
],
['id' => $organizationId],
@@ -109,4 +108,132 @@ class OrganizationRepository
$types,
);
}
/**
* @return array<string, int>
*/
public function getTagChoicesForOrganizationType(array $allowedTagIds = []): array
{
$rows = $this->connection->createQueryBuilder()
->select('DISTINCT t.id', 't.tag')
->from('tl_tags', 't')
->innerJoin('t', 'tl_tags_rel', 'r', 'r.tag_id = t.id')
->where('r.ptable = :ptable')
->andWhere('r.field = :field')
->setParameter('ptable', 'tl_organization')
->setParameter('field', 'tags')
->orderBy('t.tag', 'ASC')
->executeQuery()
->fetchAllAssociative();
if ([] === $rows) {
$rows = $this->connection->createQueryBuilder()
->select('t.id', 't.tag')
->from('tl_tags', 't')
->orderBy('t.tag', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
$choices = [];
foreach ($rows as $row) {
$choices[(string) ($row['tag'] ?? '')] = (int) $row['id'];
}
$allowedTagIds = array_values(array_unique(array_map('intval', $allowedTagIds)));
if ([] !== $allowedTagIds) {
$choices = array_filter(
$choices,
static fn (int $id): bool => in_array($id, $allowedTagIds, true),
);
}
return $choices;
}
/** @return array<int> */
public function getTagIdsForOrganization(int $organizationId): array
{
if ($organizationId <= 0) {
return [];
}
$ids = $this->connection->createQueryBuilder()
->select('tag_id')
->from('tl_tags_rel')
->where('ptable = :ptable')
->andWhere('field = :field')
->andWhere('pid = :pid')
->setParameter('ptable', 'tl_organization')
->setParameter('field', 'tags')
->setParameter('pid', $organizationId, ParameterType::INTEGER)
->orderBy('tag_id', 'ASC')
->executeQuery()
->fetchFirstColumn();
return array_values(array_unique(array_map('intval', $ids)));
}
/** @param array<int|string> $tagIds */
public function assignTagsToOrganization(int $organizationId, array $tagIds): void
{
$this->connection->delete(
'tl_tags_rel',
['ptable' => 'tl_organization', 'field' => 'tags', 'pid' => $organizationId],
['pid' => ParameterType::INTEGER],
);
$tagIds = array_values(array_unique(array_map('intval', $tagIds)));
if ([] === $tagIds) {
return;
}
$allowedTagIds = $this->getAllowedTagIdsForOrganization();
$tagIds = array_values(array_intersect($tagIds, $allowedTagIds));
foreach ($tagIds as $tagId) {
$this->connection->insert(
'tl_tags_rel',
[
'tag_id' => $tagId,
'pid' => $organizationId,
'ptable' => 'tl_organization',
'field' => 'tags',
],
[
'tag_id' => ParameterType::INTEGER,
'pid' => ParameterType::INTEGER,
],
);
}
}
/** @return array<int> */
private function getAllowedTagIdsForOrganization(): array
{
$ids = $this->connection->createQueryBuilder()
->select('DISTINCT r.tag_id')
->from('tl_tags_rel', 'r')
->where('r.ptable = :ptable')
->andWhere('r.field = :field')
->setParameter('ptable', 'tl_organization')
->setParameter('field', 'tags')
->executeQuery()
->fetchFirstColumn();
$ids = array_values(array_unique(array_map('intval', $ids)));
if ([] !== $ids) {
return $ids;
}
return array_values(array_unique(array_map('intval', $this->connection->createQueryBuilder()
->select('id')
->from('tl_tags')
->executeQuery()
->fetchFirstColumn())));
}
}