diff --git a/contao/dca/tl_module.php b/contao/dca/tl_module.php
index 66314f4..54ee83a 100644
--- a/contao/dca/tl_module.php
+++ b/contao/dca/tl_module.php
@@ -7,6 +7,7 @@ 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']['event_filter'] = '{title_legend},name,headline,type;{eventmanager_legend},cal_calendar;{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';
diff --git a/contao/languages/de/modules.php b/contao/languages/de/modules.php
index 8451e0b..084d473 100644
--- a/contao/languages/de/modules.php
+++ b/contao/languages/de/modules.php
@@ -9,4 +9,5 @@ $GLOBALS['TL_LANG']['FMD']['eventmanager'] = 'Event-Manager';
$GLOBALS['TL_LANG']['FMD']['member_organizations'] = ['Meine Organisationen', 'Listet Organisationen des eingeloggten Mitglieds auf.'];
$GLOBALS['TL_LANG']['FMD']['organization_edit'] = ['Organisation bearbeiten', 'Bearbeitungsformular für eine Organisation.'];
$GLOBALS['TL_LANG']['FMD']['member_events'] = ['Meine Veranstaltungen', 'Listet Veranstaltungen der zugeordneten Organisationen auf.'];
+$GLOBALS['TL_LANG']['FMD']['event_filter'] = ['Eventfilter', 'Filter für kommende Veranstaltungen (Tags, Orte, Veranstalter).'];
$GLOBALS['TL_LANG']['FMD']['event_edit'] = ['Veranstaltung bearbeiten', 'Bearbeitungsformular für eine Veranstaltung.'];
diff --git a/contao/languages/en/modules.php b/contao/languages/en/modules.php
index 0267ecf..fc33698 100644
--- a/contao/languages/en/modules.php
+++ b/contao/languages/en/modules.php
@@ -9,4 +9,5 @@ $GLOBALS['TL_LANG']['FMD']['eventmanager'] = 'Event manager';
$GLOBALS['TL_LANG']['FMD']['member_organizations'] = ['My organizations', 'Lists organizations of the logged-in member.'];
$GLOBALS['TL_LANG']['FMD']['organization_edit'] = ['Edit organization', 'Edit form for one organization.'];
$GLOBALS['TL_LANG']['FMD']['member_events'] = ['My events', 'Lists events of the member organizations.'];
+$GLOBALS['TL_LANG']['FMD']['event_filter'] = ['Event filter', 'Filters upcoming events (tags, locations, organizers).'];
$GLOBALS['TL_LANG']['FMD']['event_edit'] = ['Edit event', 'Edit form for one event.'];
diff --git a/contao/templates/frontend/event_filter.html.twig b/contao/templates/frontend/event_filter.html.twig
new file mode 100644
index 0000000..0958379
--- /dev/null
+++ b/contao/templates/frontend/event_filter.html.twig
@@ -0,0 +1,27 @@
+
+
+
+ {% for tag in tagButtons|default([]) %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controller/Frontend/EventFilterController.php b/src/Controller/Frontend/EventFilterController.php
new file mode 100644
index 0000000..c0a4eed
--- /dev/null
+++ b/src/Controller/Frontend/EventFilterController.php
@@ -0,0 +1,193 @@
+cal_calendar, true));
+ $eventIds = $this->findUpcomingEventIds($calendarIds);
+
+ $template->set('tagButtons', $this->findTagButtons($eventIds));
+ $template->set('locations', $this->findLocations($eventIds));
+ $template->set('organizations', $this->findOrganizations($eventIds));
+
+ return $template->getResponse();
+ }
+
+ /**
+ * @param list $calendarIds
+ *
+ * @return list
+ */
+ private function findUpcomingEventIds(array $calendarIds): array
+ {
+ $today = strtotime('today');
+
+ if ([] === $calendarIds) {
+ $rows = $this->connection->executeQuery(
+ <<<'SQL'
+ SELECT e.id
+ FROM tl_calendar_events e
+ INNER JOIN tl_calendar c ON c.id=e.pid
+ WHERE e.published='1'
+ AND c.published='1'
+ AND (e.startTime>=? OR e.endTime>=?)
+ ORDER BY e.startTime ASC
+ SQL,
+ [$today, $today],
+ [\PDO::PARAM_INT, \PDO::PARAM_INT],
+ )->fetchFirstColumn();
+ } else {
+ $rows = $this->connection->executeQuery(
+ <<<'SQL'
+ SELECT e.id
+ FROM tl_calendar_events e
+ INNER JOIN tl_calendar c ON c.id=e.pid
+ WHERE e.pid IN (?)
+ AND e.published='1'
+ AND c.published='1'
+ AND (e.startTime>=? OR e.endTime>=?)
+ ORDER BY e.startTime ASC
+ SQL,
+ [$calendarIds, $today, $today],
+ [ArrayParameterType::INTEGER, \PDO::PARAM_INT, \PDO::PARAM_INT],
+ )->fetchFirstColumn();
+ }
+
+ return array_values(array_unique(array_map('intval', $rows)));
+ }
+
+ /**
+ * @param list $eventIds
+ *
+ * @return list
+ */
+ private function findTagButtons(array $eventIds): array
+ {
+ if ([] === $eventIds) {
+ return [];
+ }
+
+ $rows = $this->connection->executeQuery(
+ <<<'SQL'
+ SELECT t.id, t.tag, COUNT(DISTINCT r.item_id) AS total
+ FROM tl_tags_rel r
+ INNER JOIN tl_tags t ON t.id=r.tag_id
+ WHERE r.ptable='tl_calendar_events'
+ AND r.field='tags'
+ AND r.item_id IN (?)
+ GROUP BY t.id, t.tag
+ ORDER BY t.tag ASC
+ SQL,
+ [$eventIds],
+ [ArrayParameterType::INTEGER],
+ )->fetchAllAssociative();
+
+ $items = [];
+
+ foreach ($rows as $row) {
+ $items[] = [
+ 'id' => (int) ($row['id'] ?? 0),
+ 'title' => (string) ($row['tag'] ?? ''),
+ 'count' => (int) ($row['total'] ?? 0),
+ ];
+ }
+
+ return $items;
+ }
+
+ /**
+ * @param list $eventIds
+ *
+ * @return list
+ */
+ private function findLocations(array $eventIds): array
+ {
+ if ([] === $eventIds) {
+ return [];
+ }
+
+ $rows = $this->connection->executeQuery(
+ <<<'SQL'
+ SELECT l.id, l.title, COUNT(e.id) AS total
+ FROM tl_calendar_events e
+ INNER JOIN tl_location l ON l.id=e.location
+ WHERE e.id IN (?)
+ AND e.location>0
+ GROUP BY l.id, l.title
+ ORDER BY l.title ASC
+ SQL,
+ [$eventIds],
+ [ArrayParameterType::INTEGER],
+ )->fetchAllAssociative();
+
+ $items = [];
+
+ foreach ($rows as $row) {
+ $items[] = [
+ 'id' => (int) ($row['id'] ?? 0),
+ 'title' => (string) ($row['title'] ?? ''),
+ 'count' => (int) ($row['total'] ?? 0),
+ ];
+ }
+
+ return $items;
+ }
+
+ /**
+ * @param list $eventIds
+ *
+ * @return list
+ */
+ private function findOrganizations(array $eventIds): array
+ {
+ if ([] === $eventIds) {
+ return [];
+ }
+
+ $rows = $this->connection->executeQuery(
+ <<<'SQL'
+ SELECT o.id, o.title, COUNT(DISTINCT rel.event_id) AS total
+ FROM tl_calendar_events_organization rel
+ INNER JOIN tl_organization o ON o.id=rel.organization_id
+ WHERE rel.event_id IN (?)
+ GROUP BY o.id, o.title
+ ORDER BY o.title ASC
+ SQL,
+ [$eventIds],
+ [ArrayParameterType::INTEGER],
+ )->fetchAllAssociative();
+
+ $items = [];
+
+ foreach ($rows as $row) {
+ $items[] = [
+ 'id' => (int) ($row['id'] ?? 0),
+ 'title' => (string) ($row['title'] ?? ''),
+ 'count' => (int) ($row['total'] ?? 0),
+ ];
+ }
+
+ return $items;
+ }
+}