Initial standalone bundle extraction
This commit is contained in:
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
@@ -0,0 +1,205 @@
|
||||
# mummert/ks-nossenerland-bundle
|
||||
|
||||
Contao-Bundle fuer projektspezifische Erweiterungen rund um Veranstaltungen im Kirchspiel Nossener Land.
|
||||
|
||||
Ziel dieses Bundles ist die Kapselung bestehender, produktiver Logik (ohne fachliche Aenderung) als installierbares Paket.
|
||||
|
||||
## Funktionsueberblick
|
||||
|
||||
Das Bundle erweitert vor allem die Event-Verwaltung und den Event-Output in Contao:
|
||||
|
||||
- DCA-Erweiterung fuer `tl_calendar_events` (zusatzliche Felder, Palettenanpassung, Callback fuer Aliasbildung)
|
||||
- Template-Hooks zur Anreicherung von Event-, Orts- und Angebotsdaten
|
||||
- iCal-Hook zur Anpassung von VEvent-Daten
|
||||
- CLI-Commands fuer
|
||||
- Export von Events zum EVLKS-Kalender (SOAP + Aufraeumen nicht mehr vorhandener Eintraege)
|
||||
- Export von Events in eine JSON-Datei fuer externe Nutzung
|
||||
|
||||
## Technische Bestandteile
|
||||
|
||||
### 1) DCA-Anpassungen fuer Kalender-Events
|
||||
|
||||
Datei: `contao/dca/tl_calendar_events.php`
|
||||
|
||||
- Fuegt u. a. Felder hinzu:
|
||||
- `catalog_ort`
|
||||
- `catalog_kontakt`
|
||||
- `catalog_pfarrbereiche`
|
||||
- `godi_options`
|
||||
- `subtitle`
|
||||
- `contributors`
|
||||
- `evlkscalendar`
|
||||
- `export`
|
||||
- `external_location`
|
||||
- Passt Paletten an (Legenden/Felder) und entfernt Standard-Location-Felder aus der Default-Palette.
|
||||
- Aktiviert Alias-Neuaufbau ueber `onsubmit_callback`.
|
||||
|
||||
### 2) Alias-Bildung fuer Events
|
||||
|
||||
Datei: `src/EventListener/CalendarAliasListener.php`
|
||||
|
||||
- Erzeugt beim Speichern eines Events einen sprechenden Alias aus:
|
||||
- Eventtitel
|
||||
- Ortstitel (`ctlg_orte`)
|
||||
- Datum
|
||||
- Nutzt Transliterations-/Kuerzungslogik und sorgt fuer Eindeutigkeit durch Suffix `-1`, `-2`, ...
|
||||
|
||||
### 3) Backend-Listing/Options-Callbacks
|
||||
|
||||
Datei: `src/DataContainer/CalendarEvents.php`
|
||||
|
||||
- Formatiert Kinddatensaetze im Event-Backend-Listing.
|
||||
- Liefert Select-Optionen aus:
|
||||
- `ctlg_orte`
|
||||
- `ctlg_contacts`
|
||||
- `ctlg_districts`
|
||||
|
||||
### 4) Template-Hooks (parseTemplate)
|
||||
|
||||
Dateien:
|
||||
|
||||
- `src/EventListener/EventFullListener.php`
|
||||
- Reichert `event_full` um Ort, Alias, Koordinaten, District-Informationen an.
|
||||
- `src/EventListener/EventJsonDataListener.php`
|
||||
- Laedt JSON aus `https://ics.mummert.dev/kirchenjahr.json` und injiziert passenden Datensatz nach Datum in `event_full` und `event_upcoming_all`.
|
||||
- `src/EventListener/OfferListener.php`
|
||||
- Reichert Angebots-/Kontakt-Templates (`cm_master_angebote`, `cm_master_contacts`, `cm_listing_angebote`) mit Gemeinde-/District-Daten an.
|
||||
- `src/EventListener/PlaceListener.php`
|
||||
- Reichert Orts-Listing (`cm_listing_orte`) mit Gemeinde-IDs und District-Aliases an.
|
||||
|
||||
### 5) iCal-Anpassung
|
||||
|
||||
Datei: `src/EventListener/ModifyIcalDataListener.php`
|
||||
|
||||
Hook: `editVEvent`
|
||||
|
||||
- Setzt/normalisiert iCal-Felder wie `DTSTART`, `DESCRIPTION`, `LOCATION`, `GEO`, `ORGANIZER`.
|
||||
- Verarbeitet Kontakt- und Mitwirkendeninformationen aus Eventfeldern.
|
||||
|
||||
### 6) Event-Export zum EVLKS-Kalender
|
||||
|
||||
Dateien:
|
||||
|
||||
- `src/Command/ExportEventsCommand.php`
|
||||
- `src/Service/SoapClientService.php`
|
||||
|
||||
Command: `nossener-land:export-events`
|
||||
|
||||
Ablauf:
|
||||
|
||||
1. Holt zukuenftige Events aus `tl_calendar_events` (derzeit `pid IN (1,2,3)` und `evlkscalendar != 1`).
|
||||
2. Mappt Eventdaten fuer EVLKS (inkl. Place-Mapping, Eventtyp, Personen/Kontakt, Zeiten).
|
||||
3. Exportiert per SOAP.
|
||||
4. Holt Remote-Events per JSON (`vid`) und loescht externe Events, die lokal nicht mehr im Exportlauf enthalten sind.
|
||||
|
||||
Hinweis:
|
||||
|
||||
- Das Place-Mapping ist statisch in `SoapClientService` hinterlegt.
|
||||
- Der SOAP-Zugang ist aktuell in `config/services.yml` konfiguriert.
|
||||
|
||||
### 7) JSON-Export fuer Website/Consumer
|
||||
|
||||
Datei: `src/Command/ExportEventsJsonCommand.php`
|
||||
|
||||
Command: `nossener-land:export-eventstojson`
|
||||
|
||||
Ablauf:
|
||||
|
||||
1. Liest Events mit `export=1 AND published=1`.
|
||||
2. Wandelt `singleSRC` UUID in absolute URL um.
|
||||
3. Vergibt bei Bedarf Alias.
|
||||
4. Schreibt JSON nach:
|
||||
- `/srv/www/ks-nossener-land/public/kirchspiel-nossener-land.de/public/events.json`
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Contao 5.7
|
||||
- PHP >= 8.3
|
||||
- Tabellen aus dem Umfeld von Catalog Manager, u. a.:
|
||||
- `ctlg_orte`
|
||||
- `ctlg_contacts`
|
||||
- `ctlg_gemeinden`
|
||||
- `ctlg_districts`
|
||||
- `ctlg_angebote`
|
||||
- Netzwerkzugriff auf:
|
||||
- EVLKS SOAP/JSON Endpunkte
|
||||
- JSON-Quelle `ics.mummert.dev`
|
||||
|
||||
## Console-Commands (manuell)
|
||||
|
||||
Im Contao-Projektverzeichnis ausfuehren:
|
||||
|
||||
```bash
|
||||
php bin/console nossener-land:export-events
|
||||
php bin/console nossener-land:export-eventstojson
|
||||
```
|
||||
|
||||
## Cronjobs
|
||||
|
||||
Die folgenden Beispiele sind direkt anwendbar. Pfade bitte auf das jeweilige Projekt anpassen.
|
||||
|
||||
### A) EVLKS-Export (inkrementell, inkl. Aufraeumen)
|
||||
|
||||
Empfohlenes Intervall: alle 15 Minuten.
|
||||
|
||||
```cron
|
||||
*/15 * * * * cd /srv/www/contao57 && /usr/bin/flock -n /tmp/ks-nossenerland-export-events.lock php bin/console nossener-land:export-events --env=prod --no-interaction >> var/log/cron-export-events.log 2>&1
|
||||
```
|
||||
|
||||
Warum 15 Minuten:
|
||||
|
||||
- gute Balance zwischen Aktualitaet und Last
|
||||
- robust bei redaktionellen Aenderungen waehrend des Tages
|
||||
- vermeidet zu haeufige externe API-Aufrufe
|
||||
|
||||
Alternative Intervalle:
|
||||
|
||||
- alle 5 Minuten: wenn sehr zeitkritische Publikation noetig ist
|
||||
- alle 30 Minuten: wenn Last reduziert werden soll
|
||||
|
||||
### B) JSON-Export fuer externe Anzeige
|
||||
|
||||
Empfohlenes Intervall: alle 30 Minuten.
|
||||
|
||||
```cron
|
||||
*/30 * * * * cd /srv/www/contao57 && /usr/bin/flock -n /tmp/ks-nossenerland-export-json.lock php bin/console nossener-land:export-eventstojson --env=prod --no-interaction >> var/log/cron-export-json.log 2>&1
|
||||
```
|
||||
|
||||
Warum 30 Minuten:
|
||||
|
||||
- Daten aendern sich typischerweise weniger haeufig als EVLKS-Sync-Anforderungen
|
||||
- reduziert I/O und Dateischreiblast
|
||||
|
||||
Alternative Intervalle:
|
||||
|
||||
- stuendlich: fuer sehr geringe Aenderungsfrequenz
|
||||
- alle 10-15 Minuten: wenn JSON als nahezu Live-Feed genutzt wird
|
||||
|
||||
### C) Startzeiten staffeln
|
||||
|
||||
Empfehlung fuer stabilen Betrieb:
|
||||
|
||||
- EVLKS-Export auf Viertelstunden
|
||||
- JSON-Export zeitversetzt, z. B. Minute 7 und 37
|
||||
|
||||
Beispiel:
|
||||
|
||||
```cron
|
||||
*/15 * * * * cd /srv/www/contao57 && /usr/bin/flock -n /tmp/ks-nossenerland-export-events.lock php bin/console nossener-land:export-events --env=prod --no-interaction >> var/log/cron-export-events.log 2>&1
|
||||
7,37 * * * * cd /srv/www/contao57 && /usr/bin/flock -n /tmp/ks-nossenerland-export-json.lock php bin/console nossener-land:export-eventstojson --env=prod --no-interaction >> var/log/cron-export-json.log 2>&1
|
||||
```
|
||||
|
||||
## Empfohlene Betriebsregeln
|
||||
|
||||
- Immer `flock` nutzen, um Ueberlappungen bei langen Laufzeiten zu verhindern.
|
||||
- Ausgabe in getrennte Logdateien schreiben.
|
||||
- Nach Deployment einmal manuell beide Commands ausfuehren.
|
||||
- Bei API-Problemen zuerst Logs pruefen und dann betroffene Command-Intervalle temporaer erhoehen.
|
||||
|
||||
## Bekannte projektbezogene Punkte (kein Bugfix in diesem Schritt)
|
||||
|
||||
- In `ExternalLocationModel` sind externe DB-Zugangsdaten fest hinterlegt.
|
||||
- `ExportEventsJsonCommand` nutzt einen fest codierten Zielpfad fuer `events.json`.
|
||||
- SOAP-API-Zugangsdaten liegen aktuell in `config/services.yml`.
|
||||
|
||||
Diese Punkte sind bewusst unveraendert geblieben (Ziel: keine Funktionsaenderung beim Bundle-Umbau).
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "mummert/ks-nossenerland-bundle",
|
||||
"description": "Contao bundle for Kirchspiel Nossener Land integrations and event export.",
|
||||
"type": "contao-bundle",
|
||||
"license": "proprietary",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"contao/core-bundle": "^5.7",
|
||||
"contao/calendar-bundle": "5.7.*",
|
||||
"contao/manager-plugin": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"alnv/catalog-manager-bundle": "^3.0",
|
||||
"janborg/contao-ical-bundle": "^0.5.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mummert\\KsNossenerlandBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"contao-manager-plugin": "Mummert\\KsNossenerlandBundle\\Contao\\Manager\\Plugin"
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"contao-components/installer": true,
|
||||
"contao/manager-plugin": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: true
|
||||
|
||||
GuzzleHttp\Client:
|
||||
class: GuzzleHttp\Client
|
||||
arguments: []
|
||||
|
||||
Mummert\KsNossenerlandBundle\:
|
||||
resource: '../src/*'
|
||||
exclude:
|
||||
- '../src/Contao'
|
||||
- '../src/DependencyInjection'
|
||||
- '../src/KsNossenerlandBundle.php'
|
||||
|
||||
Mummert\KsNossenerlandBundle\Command\ExportEventsJsonCommand:
|
||||
tags:
|
||||
- { name: 'console.command', command: 'nossener-land:export-eventstojson' }
|
||||
|
||||
Mummert\KsNossenerlandBundle\Command\ExportEventsCommand:
|
||||
tags:
|
||||
- { name: 'console.command', command: 'nossener-land:export-events' }
|
||||
|
||||
Mummert\KsNossenerlandBundle\Service\SoapClientService:
|
||||
arguments:
|
||||
$wsdlUrl: 'http://kalender.evlks.de/soap?WSDL'
|
||||
$endpointUrl: 'http://kalender.evlks.de/soap'
|
||||
$apiKey: '67d2e6c68833a9.49074814'
|
||||
$vid: '198'
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\ModifyIcalDataListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: editVEvent }
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\EventJsonDataListener:
|
||||
tags:
|
||||
- { name: contao.hook, hook: parseTemplate, priority: 100 }
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\EventFullListener:
|
||||
arguments:
|
||||
$logger: '@logger'
|
||||
$connection: '@database_connection'
|
||||
tags:
|
||||
- { name: contao.hook, hook: parseTemplate, method: onParseTemplate }
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\PlaceListener:
|
||||
arguments:
|
||||
$logger: '@logger'
|
||||
$connection: '@database_connection'
|
||||
tags:
|
||||
- { name: contao.hook, hook: parseTemplate, method: onParseTemplate }
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\OfferListener:
|
||||
arguments:
|
||||
$logger: '@logger'
|
||||
$connection: '@database_connection'
|
||||
tags:
|
||||
- { name: contao.hook, hook: parseTemplate, method: onParseTemplate }
|
||||
|
||||
Mummert\KsNossenerlandBundle\EventListener\CalendarAliasListener:
|
||||
tags:
|
||||
- { name: contao.callback, table: tl_calendar_events, target: config.oncreate, method: onSubmitCallback }
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
$GLOBALS['TL_DCA']['exported_events'] = [
|
||||
'config' => [
|
||||
'sql' => [
|
||||
'keys' => [
|
||||
'externalid' => 'primary'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
'externalid' => [
|
||||
'sql' => "VARCHAR(255) NOT NULL"
|
||||
],
|
||||
'last_export_date' => [
|
||||
'sql' => "DATETIME NOT NULL"
|
||||
],
|
||||
'status' => [
|
||||
'sql' => "VARCHAR(10) NOT NULL DEFAULT 'ok'"
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
$dca = &$GLOBALS['TL_DCA']['tl_calendar_events'];
|
||||
|
||||
|
||||
use Contao\CoreBundle\DataContainer\PaletteManipulator;
|
||||
use Mummert\KsNossenerlandBundle\DataContainer\CalendarEvents;
|
||||
use Mummert\KsNossenerlandBundle\Model\ExternalLocationModel;
|
||||
|
||||
$dca['fields']['featured']['filter'] = false;
|
||||
$dca['fields']['recurring']['filter'] = false;
|
||||
$dca['fields']['published']['filter'] = false;
|
||||
$dca['fields']['startTime']['filter'] = false;
|
||||
|
||||
$fields = [
|
||||
'featured', 'addTime', 'addImage', 'overwriteMeta', 'fullsize', 'recurring', 'addEnclosure', 'target',
|
||||
'published', 'socialImage', 'pid', 'tstamp', 'startTime', 'endTime', 'startDate',
|
||||
'endDate', 'pageTitle', 'robots', 'description', 'canonicalLink', 'location', 'address', 'singleSRC',
|
||||
'alt', 'imageTitle', 'size', 'imageUrl', 'caption', 'floating', 'repeatEach', 'repeatEnd', 'recurrences', 'enclosure',
|
||||
'source', 'linkText', 'jumpTo', 'articleId', 'url', 'cssClass', 'start', 'stop', 'languageMain', 'catalog_ort',
|
||||
'catalog_kontakt', 'godi_options', 'catalog_pfarrbereiche', 'styleManager', 'export',
|
||||
'external_location', 'evlkscalendar'
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$dca['fields'][$field]['search'] = false;
|
||||
}
|
||||
|
||||
$dca['list']['sorting']['child_record_callback'] = [
|
||||
'Mummert\KsNossenerlandBundle\DataContainer\CalendarEvents',
|
||||
'listEvents'
|
||||
];
|
||||
|
||||
// Subpalettes
|
||||
$dca['palettes']['__selector__'][] = 'export';
|
||||
$dca['subpalettes']['export'] = 'external_location';
|
||||
|
||||
/**
|
||||
* Felder
|
||||
*/
|
||||
$dca['fields']['catalog_ort'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['catalog_ort'],
|
||||
'exclude' => false,
|
||||
'filter' => true,
|
||||
'inputType' => 'select',
|
||||
'options_callback' => [CalendarEvents::class, 'getCtlgOrteOptions'],
|
||||
'eval' => [
|
||||
'mandatory' => true,
|
||||
'includeBlankOption' => true,
|
||||
'blankOptionLabel' => '-', // Explizites leeres Label für HTML5-Validierung
|
||||
'tl_class' => 'w50',
|
||||
],
|
||||
'sql' => "int(10) unsigned NOT NULL default '0'", // Falls nötig, hier default '0' entfernen
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['catalog_kontakt'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['catalog_kontakt'],
|
||||
'exclude' => false,
|
||||
'filter' => true,
|
||||
'inputType' => 'select',
|
||||
'options_callback' => [CalendarEvents::class, 'getCtlgKontaktOptions'],
|
||||
'eval' => [
|
||||
'mandatory' => false,
|
||||
'includeBlankOption' => true,
|
||||
'multiple' => true,
|
||||
'chosen' => true,
|
||||
'csv' => ',',
|
||||
'tl_class' => 'w50',
|
||||
],
|
||||
'sql' => "text NULL",
|
||||
];
|
||||
|
||||
$dca['fields']['catalog_pfarrbereiche'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['catalog_pfarrbereiche'],
|
||||
'exclude' => true,
|
||||
'inputType' => 'select',
|
||||
'options_callback' => [CalendarEvents::class, 'getCtlgPfarrbereicheOptions'],
|
||||
'eval' => [
|
||||
'mandatory' => false,
|
||||
'includeBlankOption' => true,
|
||||
'multiple' => true,
|
||||
'chosen' => true,
|
||||
'csv' => ',',
|
||||
'tl_class' => 'w50',
|
||||
],
|
||||
'sql' => "text NULL", // Speichert die Werte als kommaseparierte Liste
|
||||
];
|
||||
|
||||
$dca['fields']['godi_options'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['godi_options'],
|
||||
'exclude' => false,
|
||||
'inputType' => 'checkboxWizard',
|
||||
'options' => [
|
||||
1 => 'Abendmahl',
|
||||
2 => 'Kindergottesdienst',
|
||||
3 => 'Kirchenkaffee',
|
||||
4 => 'Taufe',
|
||||
],
|
||||
'eval' => [
|
||||
'mandatory' => false,
|
||||
'multiple' => true,
|
||||
'csv' => ',',
|
||||
'tl_class' => 'clr',
|
||||
],
|
||||
'sql' => "text NULL",
|
||||
];
|
||||
|
||||
$dca['fields']['subtitle'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['subtitle'],
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'filter' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => "varchar(255) DEFAULT NULL",
|
||||
];
|
||||
|
||||
$dca['fields']['contributors'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['contributors'],
|
||||
'exclude' => false,
|
||||
'search' => false,
|
||||
'sorting' => false,
|
||||
'filter' => false,
|
||||
'inputType' => 'text',
|
||||
'eval' => ['maxlength' => 255, 'tl_class' => 'w50'],
|
||||
'sql' => "varchar(255) DEFAULT NULL",
|
||||
];
|
||||
|
||||
|
||||
$dca['fields']['evlkscalendar'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['evlkscalendar'],
|
||||
'inputType' => 'checkbox',
|
||||
'eval' => ['tl_class' => 'w50'],
|
||||
'sql' => "char(1) NOT NULL default ''",
|
||||
];
|
||||
|
||||
$dca['fields']['export'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['export'],
|
||||
'inputType' => 'checkbox',
|
||||
'eval' => ['submitOnChange' => true],
|
||||
'sql' => "char(1) NOT NULL default ''",
|
||||
];
|
||||
|
||||
$dca['fields']['external_location'] = [
|
||||
'label' => &$GLOBALS['TL_LANG']['tl_calendar_events']['external_location'],
|
||||
'inputType' => 'select',
|
||||
'options_callback' => function () {
|
||||
try {
|
||||
$locations = ExternalLocationModel::findAllLocations();
|
||||
|
||||
if (empty($locations)) {
|
||||
return ['debug' => "Keine Einträge in der Tabelle 'tl_location' gefunden."];
|
||||
}
|
||||
|
||||
$options = [];
|
||||
foreach ($locations as $location) {
|
||||
$options[$location['id']] = $location['title'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
} catch (\Exception $e) {
|
||||
return ['debug' => "Fehler: " . $e->getMessage()];
|
||||
}
|
||||
},
|
||||
'eval' => [
|
||||
'mandatory' => true,
|
||||
'includeBlankOption' => true,
|
||||
'tl_class' => 'w50',
|
||||
],
|
||||
'sql' => "int(10) unsigned NOT NULL default '0'",
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
PaletteManipulator::create()
|
||||
->addLegend('godioptions_legend', 'details_legend', PaletteManipulator::POSITION_AFTER)
|
||||
->addLegend('nossenerland_legend', 'details_legend', PaletteManipulator::POSITION_AFTER)
|
||||
->addField('godi_options', 'godioptions_legend', PaletteManipulator::POSITION_APPEND)
|
||||
->addField('subtitle', 'title_legend', PaletteManipulator::POSITION_APPEND)
|
||||
->addField('evlkscalendar', 'nossenerland_legend', PaletteManipulator::POSITION_APPEND)
|
||||
->addField('export', 'nossenerland_legend', PaletteManipulator::POSITION_APPEND)
|
||||
->addField('contributors', 'address')
|
||||
->addField('catalog_kontakt', 'address')
|
||||
->addField('catalog_pfarrbereiche', 'address')
|
||||
->addField('catalog_ort', 'address')
|
||||
->applyToPalette('default', 'tl_calendar_events');
|
||||
|
||||
|
||||
PaletteManipulator::create()
|
||||
->removeField('location')
|
||||
->removeField('address')
|
||||
->removeField('location_name')
|
||||
->removeField('location_str')
|
||||
->removeField('location_ort')
|
||||
->applyToPalette('default', 'tl_calendar_events');
|
||||
|
||||
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_calendar_events']['config']['onsubmit_callback'][] = [
|
||||
'Mummert\KsNossenerlandBundle\EventListener\CalendarAliasListener',
|
||||
'onSubmitCallback'
|
||||
];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
$lang = &$GLOBALS['TL_LANG']['tl_calendar_events'];
|
||||
|
||||
$lang['catalog_ort'][0] = 'Veranstaltungsort';
|
||||
$lang['catalog_ort'][1] = 'bitte aus der Datenbank wählen';
|
||||
|
||||
$lang['catalog_kontakt'][0] = 'verantwortliche Person(en)';
|
||||
$lang['catalog_kontakt'][1] = 'bitte aus der Datenbank wählen';
|
||||
|
||||
$lang['contributors'][0] = 'Mitwirkende';
|
||||
$lang['contributors'][1] = 'weitere Mitwirkende angeben, sofern nicht in der Datenbank';
|
||||
|
||||
$lang['subtitle'][0] = 'Untertitel';
|
||||
$lang['subtitle'][1] = 'z.B. Thema des Gottesdienstes';
|
||||
|
||||
$lang['godi_options'][0] = 'Gottesdienst mit:';
|
||||
$lang['godi_options'][1] = 'Eigenschaften angeben';
|
||||
|
||||
$lang['export'][0] = 'Veranstaltung auf nossener-land.de anzeigen';
|
||||
$lang['export'][1] = 'auswählen wenn die Veranstaltung auch auf nossener-land.de angezeigt werden soll';
|
||||
|
||||
$lang['evlkscalendar'][0] = 'Veranstaltung nicht auf EVLKS Kaelnder zeigen';
|
||||
$lang['evlkscalendar'][1] = 'wenn ausgewählt, wird die Veranstaltung nicht zum EVLKS Kalender exportiert';
|
||||
|
||||
$lang['external_location'][0] = 'Veranstaltungsort aus nossener-land.de Datenbank';
|
||||
$lang['external_location'][1] = 'Export funktioniert nur mit Veranstaltungsort aus nossener-land.de Datenbank';
|
||||
|
||||
$lang['catalog_pfarrbereiche'][0] = 'Pfarrbereiche';
|
||||
$lang['catalog_pfarrbereiche'][1] = 'nur bei überregionalen Veranstaltungen angeben oder wenn Event für mehr als einen Pfarrbereich relevant, ansonsten erfolgt Zuweisung bereits über Veranstaltungsort';
|
||||
|
||||
$lang['godioptions_legend'] = 'Gottesdienst-Eigenschaften';
|
||||
$lang['nossenerland_legend'] = 'Veranstaltung exportieren';
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Mummert\KsNossenerlandBundle\Service\SoapClientService;
|
||||
|
||||
|
||||
#[AsCommand(
|
||||
name: 'nossener-land:export-events',
|
||||
description: 'Exportiert und aktualisiert Events im EVLKS-Kalender.'
|
||||
)]
|
||||
class ExportEventsCommand extends Command
|
||||
{
|
||||
private $connection;
|
||||
private $soapClientService;
|
||||
|
||||
public function __construct(Connection $connection, SoapClientService $soapClientService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->connection = $connection;
|
||||
$this->soapClientService = $soapClientService;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setHelp('Dieses Kommando exportiert zukünftige Events nach EVLKS und löscht nicht mehr vorhandene.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$today = (new \DateTimeImmutable('today'))->getTimestamp();
|
||||
|
||||
$stmt = $this->connection->executeQuery(
|
||||
'SELECT * FROM tl_calendar_events WHERE pid IN (1, 2, 3) AND evlkscalendar != 1 AND (
|
||||
(endDate IS NOT NULL AND endDate >= ?) OR (endDate IS NULL AND startDate >= ?)
|
||||
)',
|
||||
[$today, $today]
|
||||
);
|
||||
|
||||
$exportedIds = [];
|
||||
$exportCount = 0;
|
||||
|
||||
while ($event = $stmt->fetchAssociative()) {
|
||||
$response = $this->soapClientService->sendEventToSoapAPI($event);
|
||||
if ($response) {
|
||||
$exportedIds[] = (string) $event['id'];
|
||||
$exportCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$remoteExternalIds = $this->soapClientService->fetchRemoteExternalIds();
|
||||
$deletedCount = 0;
|
||||
|
||||
foreach ($remoteExternalIds as $externalId) {
|
||||
if (!in_array($externalId, $exportedIds, true)) {
|
||||
$response = $this->soapClientService->deleteEventByExternalId($externalId);
|
||||
if ($response) {
|
||||
$output->writeln("🗑️ gelöscht: $externalId");
|
||||
$deletedCount++;
|
||||
} else {
|
||||
$output->writeln("❌ Fehler beim Löschen von Event $externalId");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('Export Summary:');
|
||||
$output->writeln('----------------');
|
||||
$output->writeln('Date: ' . date('Y-m-d H:i:s'));
|
||||
$output->writeln("Number of exported events: $exportCount");
|
||||
$output->writeln("Number of deleted events: $deletedCount");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\Command;
|
||||
|
||||
use Contao\CoreBundle\Framework\ContaoFramework;
|
||||
use Contao\FilesModel;
|
||||
use Contao\Database;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class ExportEventsJsonCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'nossener-land:export-eventstojson';
|
||||
|
||||
private ContaoFramework $framework;
|
||||
|
||||
public function __construct(ContaoFramework $framework)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->framework = $framework;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Exportiert Events mit export=1 und published=1 als JSON-Datei.')
|
||||
->setHelp('Dieser Command exportiert alle Events, die für den Export und die Veröffentlichung markiert sind.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
// Contao Framework initialisieren
|
||||
$this->framework->initialize();
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Events abrufen
|
||||
$events = $db->prepare("
|
||||
SELECT id, addTime, addImage, title, startTime, endTime, startDate, endDate, description, teaser, singleSRC, external_location, alias
|
||||
FROM tl_calendar_events
|
||||
WHERE export = 1 AND published = 1
|
||||
")->execute()->fetchAllAssoc();
|
||||
|
||||
if (empty($events)) {
|
||||
$io->warning('Keine Events mit export=1 und published=1 gefunden.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$baseUrl = 'https://kirchspiel-nossener-land.de'; // Basis-URL deiner Website
|
||||
|
||||
// Events verarbeiten
|
||||
foreach ($events as &$event) {
|
||||
// singleSRC: UUID in vollständige URL umwandeln
|
||||
if (!empty($event['singleSRC'])) {
|
||||
$file = FilesModel::findByUuid($event['singleSRC']);
|
||||
if ($file) {
|
||||
$event['singleSRC'] = $baseUrl . '/' . $file->path;
|
||||
} else {
|
||||
$event['singleSRC'] = null; // Setze auf null, falls die Datei nicht gefunden wurde
|
||||
}
|
||||
}
|
||||
|
||||
// alias: sicherstellen, dass ein Alias vorhanden ist
|
||||
if (empty($event['alias'])) {
|
||||
$event['alias'] = strtolower(preg_replace('/[^a-z0-9]+/', '-', trim($event['title']))) . '-' . date('Y-m-d', $event['startDate']);
|
||||
}
|
||||
|
||||
// Zusätzliche Transformationen (falls nötig) hier einfügen
|
||||
}
|
||||
|
||||
// JSON erstellen
|
||||
$json = json_encode($events, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
// JSON-Datei speichern
|
||||
$filePath = '/srv/www/ks-nossener-land/public/kirchspiel-nossener-land.de/public/events.json';
|
||||
file_put_contents($filePath, $json);
|
||||
|
||||
$io->success("Export erfolgreich! Datei gespeichert unter: $filePath");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\Contao\Manager;
|
||||
|
||||
use Contao\CalendarBundle\ContaoCalendarBundle;
|
||||
use Contao\CoreBundle\ContaoCoreBundle;
|
||||
use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
|
||||
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
|
||||
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
|
||||
use Mummert\KsNossenerlandBundle\KsNossenerlandBundle;
|
||||
|
||||
class Plugin implements BundlePluginInterface
|
||||
{
|
||||
public function getBundles(ParserInterface $parser): iterable
|
||||
{
|
||||
return [
|
||||
BundleConfig::create(KsNossenerlandBundle::class)
|
||||
->setLoadAfter([ContaoCoreBundle::class, ContaoCalendarBundle::class]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\DataContainer;
|
||||
|
||||
use Contao\Database;
|
||||
use Contao\Date;
|
||||
use Contao\StringUtil;
|
||||
use Contao\System;
|
||||
use Contao\Calendar;
|
||||
use Contao\Config;
|
||||
|
||||
class CalendarEvents
|
||||
{
|
||||
public function listEvents($arrRow)
|
||||
{
|
||||
$database = Database::getInstance();
|
||||
|
||||
// Ortstitel aus `ctlg_orte`-Tabelle holen
|
||||
$ort = $database->prepare("SELECT ortTitle FROM ctlg_orte WHERE id=?")
|
||||
->execute($arrRow['catalog_ort'])
|
||||
->fetchAssoc();
|
||||
|
||||
$ortTitle = $ort ? $ort['ortTitle'] : '-';
|
||||
|
||||
// Event-Datum formatieren
|
||||
$span = Calendar::calculateSpan($arrRow['startTime'], $arrRow['endTime']);
|
||||
|
||||
if ($span > 0) {
|
||||
$date = Date::parse(Config::get(($arrRow['addTime'] ? 'datimFormat' : 'dateFormat')), $arrRow['startTime']) .
|
||||
$GLOBALS['TL_LANG']['MSC']['cal_timeSeparator'] .
|
||||
Date::parse(Config::get(($arrRow['addTime'] ? 'datimFormat' : 'dateFormat')), $arrRow['endTime']);
|
||||
} elseif ($arrRow['startTime'] == $arrRow['endTime']) {
|
||||
$date = Date::parse(Config::get('dateFormat'), $arrRow['startTime']) .
|
||||
($arrRow['addTime'] ? ' ' . Date::parse(Config::get('timeFormat'), $arrRow['startTime']) : '');
|
||||
} else {
|
||||
$date = Date::parse(Config::get('dateFormat'), $arrRow['startTime']) .
|
||||
($arrRow['addTime'] ? ' ' . Date::parse(Config::get('timeFormat'), $arrRow['startTime']) .
|
||||
$GLOBALS['TL_LANG']['MSC']['cal_timeSeparator'] .
|
||||
Date::parse(Config::get('timeFormat'), $arrRow['endTime']) : '');
|
||||
}
|
||||
|
||||
// Rückgabe ohne <strong>-Tag
|
||||
return '<div class="tl_content_left">' .
|
||||
$arrRow['title'] .
|
||||
' <span style="color:#999;padding-left:3px">[' . $ortTitle . ' ' . $date . ']</span>' .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
public function getCtlgOrteOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
$result = Database::getInstance()->prepare("SELECT id, ortTitle FROM ctlg_orte ORDER BY ortTitle")->execute();
|
||||
|
||||
while ($result->next()) {
|
||||
$options[$result->id] = $result->ortTitle;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function getCtlgPfarrbereicheOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
// Daten aus ctlg_districts sortiert nach districtsTitle laden
|
||||
$result = Database::getInstance()->prepare("
|
||||
SELECT id, districtsTitle
|
||||
FROM ctlg_districts
|
||||
ORDER BY districtsTitle
|
||||
")->execute();
|
||||
|
||||
while ($result->next()) {
|
||||
$options[$result->id] = $result->districtsTitle;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
|
||||
public function getCtlgKontaktOptions(): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
// Sortierung nach lastname, contactsTerm, firstname
|
||||
$result = Database::getInstance()->prepare("
|
||||
SELECT id, contactsTerm, contactsFirstname, contactsLastname, contactsFunction
|
||||
FROM ctlg_contacts
|
||||
ORDER BY contactsLastname, contactsTerm, contactsFirstname
|
||||
")->execute();
|
||||
|
||||
while ($result->next()) {
|
||||
// Aufbau des Labels
|
||||
if (empty($result->contactsLastname) && empty($result->contactsFirstname)) {
|
||||
// Wenn lastname und firstname fehlen
|
||||
$label = $result->contactsTerm;
|
||||
} else {
|
||||
// Wenn lastname oder firstname vorhanden
|
||||
$label = $result->contactsLastname;
|
||||
|
||||
if (!empty($result->contactsFirstname)) {
|
||||
$label .= ', ' . $result->contactsFirstname;
|
||||
}
|
||||
|
||||
if (!empty($result->contactsFunction)) {
|
||||
$label .= ' (' . $result->contactsFunction . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// Nur Labels hinzufügen, die tatsächlich Werte haben
|
||||
if (!empty($label)) {
|
||||
$options[$result->id] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
class KsNossenerlandExtension extends Extension
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
|
||||
$loader->load('services.yml');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\Database;
|
||||
use Contao\DataContainer;
|
||||
use Contao\StringUtil;
|
||||
|
||||
class CalendarAliasListener
|
||||
{
|
||||
public function onSubmitCallback($dc): void
|
||||
{
|
||||
// Sicherstellen, dass der Callback mit einem gültigen DataContainer aufgerufen wurde
|
||||
if (!$dc instanceof DataContainer || !$dc->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Event-Daten aus der Datenbank holen
|
||||
$event = Database::getInstance()
|
||||
->prepare("SELECT title, catalog_ort, startDate FROM tl_calendar_events WHERE id=?")
|
||||
->execute($dc->id)
|
||||
->fetchAssoc();
|
||||
|
||||
if (!$event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sicherstellen, dass startDate nicht 0 ist
|
||||
$startDate = (int) $event['startDate'];
|
||||
if ($startDate <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Event-Titel und Ort holen und verarbeiten
|
||||
$title = $this->replaceUmlauts($event['title']);
|
||||
$ortTitle = $this->getOrtTitle($event['catalog_ort']);
|
||||
$date = date('Y-m-d', $startDate);
|
||||
|
||||
// Kürzen und Alias generieren
|
||||
$shortTitle = $this->shortenTitle($title, 25);
|
||||
$shortOrt = $this->shortenTitle($ortTitle, 20);
|
||||
$baseAlias = StringUtil::generateAlias("$shortTitle-$shortOrt-$date");
|
||||
|
||||
// Falls Alias bereits existiert: Zahl anhängen (-1, -2, -3 ...)
|
||||
$finalAlias = $this->getUniqueAlias($baseAlias, $dc->id);
|
||||
|
||||
// Alias direkt in der Datenbank speichern
|
||||
Database::getInstance()
|
||||
->prepare("UPDATE tl_calendar_events SET alias=? WHERE id=?")
|
||||
->execute($finalAlias, $dc->id);
|
||||
}
|
||||
|
||||
private function getOrtTitle(int $catalogOrtId): string
|
||||
{
|
||||
$result = Database::getInstance()
|
||||
->prepare("SELECT ortTitle FROM ctlg_orte WHERE id=?")
|
||||
->execute($catalogOrtId);
|
||||
|
||||
return $this->replaceUmlauts($result->ortTitle);
|
||||
}
|
||||
|
||||
private function replaceUmlauts(string $text): string
|
||||
{
|
||||
$umlauts = [
|
||||
'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue',
|
||||
'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ß' => 'ss'
|
||||
];
|
||||
return str_replace(array_keys($umlauts), array_values($umlauts), $text);
|
||||
}
|
||||
|
||||
private function shortenTitle(string $title, int $maxLength): string
|
||||
{
|
||||
if (mb_strlen($title) <= $maxLength) {
|
||||
return StringUtil::generateAlias($title);
|
||||
}
|
||||
|
||||
$words = explode(' ', $title);
|
||||
$shortTitle = '';
|
||||
|
||||
foreach ($words as $word) {
|
||||
if (mb_strlen($shortTitle . ' ' . $word) > $maxLength) {
|
||||
break;
|
||||
}
|
||||
$shortTitle .= (empty($shortTitle) ? '' : '-') . $word;
|
||||
}
|
||||
|
||||
return StringUtil::generateAlias($shortTitle);
|
||||
}
|
||||
|
||||
private function getUniqueAlias(string $baseAlias, int $eventId): string
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
$alias = $baseAlias;
|
||||
$counter = 1;
|
||||
|
||||
while ($db->prepare("SELECT id FROM tl_calendar_events WHERE alias=? AND id!=?")
|
||||
->execute($alias, $eventId)->numRows > 0) {
|
||||
$alias = $baseAlias . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $alias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\Template;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Contao\StringUtil;
|
||||
|
||||
class EventFullListener
|
||||
{
|
||||
private Connection $connection;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(Connection $connection, LoggerInterface $logger)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Hook("parseTemplate")
|
||||
*/
|
||||
public function onParseTemplate(Template $template): void
|
||||
{
|
||||
if ($template->getName() !== 'event_full') {
|
||||
return;
|
||||
}
|
||||
|
||||
$ortId = (int) ($template->external_location ?? 0);
|
||||
|
||||
$template->eventPlace = '';
|
||||
$template->eventAlias = '';
|
||||
$template->eventLatitude = '';
|
||||
$template->eventLongitude = '';
|
||||
$template->eventDistricts = [];
|
||||
|
||||
// Veranstaltungsort-Daten laden
|
||||
if ($ortId > 0) {
|
||||
$ort = $this->connection->fetchAssociative('SELECT * FROM ctlg_orte WHERE id = ?', [$ortId]);
|
||||
|
||||
if ($ort) {
|
||||
$template->eventPlace = $ort['ortTitle'] ?? '';
|
||||
$template->eventAlias = $ort['alias'] ?? '';
|
||||
$template->eventLatitude = $ort['ortLatitude'] ?? '';
|
||||
$template->eventLongitude = $ort['ortLongitude'] ?? '';
|
||||
|
||||
$gemeindeId = $ort['ortGemeinde'] ?? null;
|
||||
|
||||
if ($gemeindeId) {
|
||||
$gemeinde = $this->connection->fetchAssociative('SELECT * FROM ctlg_gemeinden WHERE id = ?', [$gemeindeId]);
|
||||
|
||||
if ($gemeinde && !empty($gemeinde['gemeindeBereich'])) {
|
||||
$district = $this->connection->fetchAssociative('SELECT * FROM ctlg_districts WHERE id = ?', [$gemeinde['gemeindeBereich']]);
|
||||
|
||||
if ($district) {
|
||||
$template->eventDistricts[] = [
|
||||
'gemeindeTitel' => $gemeinde['gemeindeTitel'],
|
||||
'title' => $district['districtsTitle'],
|
||||
'alias' => $district['districtsAlias'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\Template;
|
||||
|
||||
class EventJsonDataListener
|
||||
{
|
||||
private static $jsonData = null; // Statische Variable, um die JSON-Daten zu cachen
|
||||
private $jsonUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->jsonUrl = 'https://ics.mummert.dev/kirchenjahr.json'; // JSON-URL
|
||||
}
|
||||
|
||||
public function onParseTemplate(Template $template): void
|
||||
{
|
||||
// Unterstützte Templates
|
||||
$supportedTemplates = ['event_full', 'event_upcoming_all'];
|
||||
|
||||
// Nur für unterstützte Templates ausführen
|
||||
if (!in_array($template->getName(), $supportedTemplates, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lade die JSON-Daten (nur einmal)
|
||||
if (self::$jsonData === null) {
|
||||
self::$jsonData = $this->fetchJsonData();
|
||||
}
|
||||
|
||||
// JSON-Daten sind nicht verfügbar
|
||||
if (self::$jsonData === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Event-Datum auslesen (als `start` im JSON)
|
||||
$eventDate = $template->startDate ? date('Y-m-d', $template->startDate) : null;
|
||||
|
||||
if (!$eventDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Passenden Datensatz anhand des Datums suchen
|
||||
$eventData = array_filter(self::$jsonData, fn($event) => $event['start'] === $eventDate);
|
||||
|
||||
if (!$eventData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Daten ans Template übergeben
|
||||
$template->jsonEventData = reset($eventData);
|
||||
}
|
||||
|
||||
private function fetchJsonData(): ?array
|
||||
{
|
||||
try {
|
||||
$json = file_get_contents($this->jsonUrl);
|
||||
return $json ? json_decode($json, true) : null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\CalendarEventsModel;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Kigkonsult\Icalcreator\Vevent;
|
||||
use Contao\System;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
#[AsHook('editVEvent')]
|
||||
class ModifyIcalDataListener
|
||||
{
|
||||
private Connection $connection;
|
||||
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function __invoke(Vevent $vEvent, CalendarEventsModel $objEvent): Vevent
|
||||
{
|
||||
// Loggen, ob der Listener getriggert wird
|
||||
System::getContainer()->get('monolog.logger.contao')->info(
|
||||
'ModifyIcalDataListener triggered for event ID ' . $objEvent->id
|
||||
);
|
||||
|
||||
// Zeitzone setzen
|
||||
if (!empty($objEvent->startTime)) {
|
||||
$startDate = (new \DateTime())->setTimestamp($objEvent->startTime)->setTimezone(new \DateTimeZone('Europe/Berlin'));
|
||||
$vEvent->setDtstart($startDate);
|
||||
}
|
||||
|
||||
// Beschreibung (teaser) setzen, HTML-Tags entfernen
|
||||
$description = strip_tags($objEvent->teaser);
|
||||
|
||||
// Zusätzlicher Beschreibungstext für Kontakte & Contributors
|
||||
$additionalDescription = '';
|
||||
|
||||
// Teilnehmer aus catalog_kontakt (Mehrfach-IDs) und Organisator ermitteln
|
||||
$organizerEmail = 'ksp.nossener-land@evlks.de';
|
||||
$organizerName = 'Ev.-Luth. Kirchspiel Nossener Land';
|
||||
|
||||
if (!empty($objEvent->catalog_kontakt)) {
|
||||
$contactIds = array_filter(array_map('trim', explode(',', $objEvent->catalog_kontakt)));
|
||||
|
||||
if (!empty($contactIds)) {
|
||||
$query = $this->connection->createQueryBuilder()
|
||||
->select('contactsTerm', 'contactsFirstname', 'contactsLastname', 'contactsEmail')
|
||||
->from('ctlg_contacts')
|
||||
->where('id IN (:ids)')
|
||||
->setParameter('ids', $contactIds, Connection::PARAM_INT_ARRAY)
|
||||
->executeQuery();
|
||||
|
||||
$contacts = $query->fetchAllAssociative();
|
||||
|
||||
$contactStrings = [];
|
||||
foreach ($contacts as $index => $contact) {
|
||||
$contactName = trim("{$contact['contactsTerm']} {$contact['contactsFirstname']} {$contact['contactsLastname']}");
|
||||
if (!empty($contactName)) {
|
||||
$contactStrings[] = $contactName;
|
||||
}
|
||||
|
||||
// Falls es der erste Eintrag ist und eine E-Mail vorhanden ist, als Organisator setzen
|
||||
if ($index === 0 && !empty($contact['contactsEmail'])) {
|
||||
$organizerEmail = $contact['contactsEmail'];
|
||||
$organizerName = $contactName;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($contactStrings)) {
|
||||
$additionalDescription .= "mit " . implode("\n", $contactStrings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Falls das Feld contributors ausgefüllt ist, ebenfalls hinzufügen
|
||||
if (!empty($objEvent->contributors)) {
|
||||
$contributorList = array_map('trim', explode(',', $objEvent->contributors));
|
||||
if (!empty($contributorList)) {
|
||||
$additionalDescription .= "mit " . implode("\n", $contributorList);
|
||||
}
|
||||
}
|
||||
|
||||
// Falls teaser bereits existiert, trennen wir die zusätzlichen Infos mit einem Zeilenumbruch
|
||||
if (!empty($description) && !empty($additionalDescription)) {
|
||||
$description .= "\n\n" . $additionalDescription;
|
||||
} elseif (!empty($additionalDescription)) {
|
||||
$description = $additionalDescription;
|
||||
}
|
||||
|
||||
// Beschreibung setzen
|
||||
$vEvent->setDescription($description);
|
||||
|
||||
// LOCATION und GEO aus ctlg_orte abrufen
|
||||
if (!empty($objEvent->catalog_ort)) {
|
||||
$query = $this->connection->createQueryBuilder()
|
||||
->select('ortTitle', 'ortLatitude', 'ortLongitude')
|
||||
->from('ctlg_orte')
|
||||
->where('id = :id')
|
||||
->setParameter('id', $objEvent->catalog_ort)
|
||||
->executeQuery();
|
||||
|
||||
$locationData = $query->fetchAssociative();
|
||||
|
||||
if ($locationData) {
|
||||
$vEvent->setLocation($locationData['ortTitle']);
|
||||
|
||||
if (!empty($locationData['ortLatitude']) && !empty($locationData['ortLongitude'])) {
|
||||
$vEvent->setGeo($locationData['ortLatitude'], $locationData['ortLongitude']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ORGANIZER setzen
|
||||
$vEvent->setOrganizer("mailto:{$organizerEmail}", ['CN' => $organizerName]);
|
||||
|
||||
return $vEvent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\Template;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Contao\StringUtil;
|
||||
|
||||
class OfferListener
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
private Connection $connection;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Connection $connection)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Hook("parseTemplate")
|
||||
*/
|
||||
public function onParseTemplate(Template $template): void
|
||||
{
|
||||
// Einzelansicht
|
||||
if ($template->getName() === 'cm_master_angebote') {
|
||||
$angebotId = $template->id;
|
||||
|
||||
$angebot = $this->connection->fetchAssociative('SELECT * FROM ctlg_angebote WHERE id = ?', [$angebotId]);
|
||||
|
||||
if ($angebot) {
|
||||
$gemeindeIds = StringUtil::deserialize($angebot['angebotGemeinde'], true);
|
||||
$gemeinden = [];
|
||||
|
||||
if (!empty($gemeindeIds)) {
|
||||
$gemeindenRaw = $this->connection->fetchAllAssociative(
|
||||
'SELECT * FROM ctlg_gemeinden WHERE id IN (?)',
|
||||
[$gemeindeIds],
|
||||
[Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
|
||||
foreach ($gemeindenRaw as $gemeinde) {
|
||||
$bereichId = $gemeinde['gemeindeBereich'] ?? null;
|
||||
$district = null;
|
||||
|
||||
if ($bereichId) {
|
||||
$districtRaw = $this->connection->fetchAssociative(
|
||||
'SELECT * FROM ctlg_districts WHERE id = ?',
|
||||
[$bereichId]
|
||||
);
|
||||
|
||||
if ($districtRaw) {
|
||||
$district = [
|
||||
'id' => $bereichId,
|
||||
'title' => $districtRaw['districtsTitle'],
|
||||
'alias' => $districtRaw['districtsAlias'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$gemeinden[] = [
|
||||
'titel' => $gemeinde['gemeindeTitel'],
|
||||
'einheit' => $gemeinde['gemeindeEinheit'],
|
||||
'district' => $district,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$template->angebotGemeinden = $gemeinden;
|
||||
}
|
||||
}
|
||||
|
||||
// Kontaktansicht
|
||||
if ($template->getName() === 'cm_master_contacts') {
|
||||
$currentContactId = $template->id;
|
||||
|
||||
$angebote = $this->connection->fetchAllAssociative('SELECT * FROM ctlg_angebote');
|
||||
$filteredOffers = [];
|
||||
|
||||
foreach ($angebote as $angebot) {
|
||||
$angebotContacts = StringUtil::deserialize($angebot['angeboteContacts']);
|
||||
if (is_array($angebotContacts) && in_array($currentContactId, $angebotContacts)) {
|
||||
$gemeindeIds = StringUtil::deserialize($angebot['angebotGemeinde'], true);
|
||||
$gemeinden = [];
|
||||
|
||||
if (!empty($gemeindeIds)) {
|
||||
$gemeindenRaw = $this->connection->fetchAllAssociative(
|
||||
'SELECT * FROM ctlg_gemeinden WHERE id IN (?)',
|
||||
[$gemeindeIds],
|
||||
[Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
|
||||
foreach ($gemeindenRaw as $gemeinde) {
|
||||
$bereichId = $gemeinde['gemeindeBereich'] ?? null;
|
||||
$district = null;
|
||||
|
||||
if ($bereichId) {
|
||||
$districtRaw = $this->connection->fetchAssociative(
|
||||
'SELECT * FROM ctlg_districts WHERE id = ?',
|
||||
[$bereichId]
|
||||
);
|
||||
|
||||
if ($districtRaw) {
|
||||
$district = [
|
||||
'id' => $bereichId,
|
||||
'title' => $districtRaw['districtsTitle'],
|
||||
'alias' => $districtRaw['districtsAlias'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$gemeinden[] = [
|
||||
'titel' => $gemeinde['gemeindeTitel'],
|
||||
'einheit' => $gemeinde['gemeindeEinheit'],
|
||||
'district' => $district,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$angebot['angebotGemeinden'] = $gemeinden;
|
||||
$angebot['angebotGemeindeIds'] = $gemeindeIds;
|
||||
|
||||
// Nur Aliase extrahieren
|
||||
$angebot['angebotDistrictIds'] = array_values(array_unique(array_map(
|
||||
fn($g) => $g['district']['alias'] ?? null,
|
||||
array_filter($gemeinden, fn($g) => !empty($g['district']['alias']))
|
||||
)));
|
||||
|
||||
$filteredOffers[] = $angebot;
|
||||
}
|
||||
}
|
||||
|
||||
$orte = $this->connection->fetchAllAssociative('SELECT * FROM ctlg_orte');
|
||||
$filteredOrte = [];
|
||||
|
||||
foreach ($orte as $ort) {
|
||||
$orteContacts = StringUtil::deserialize($ort['ortContacts']);
|
||||
if (is_array($orteContacts) && in_array($currentContactId, $orteContacts)) {
|
||||
|
||||
$ortDistrictTitles = [];
|
||||
|
||||
$gemeindeId = (int) $ort['ortGemeinde'];
|
||||
if ($gemeindeId > 0) {
|
||||
$bereichId = $this->connection->fetchOne(
|
||||
'SELECT gemeindeBereich FROM ctlg_gemeinden WHERE id = ?',
|
||||
[$gemeindeId]
|
||||
);
|
||||
|
||||
if ($bereichId) {
|
||||
$title = $this->connection->fetchOne(
|
||||
'SELECT districtsTitle FROM ctlg_districts WHERE id = ?',
|
||||
[$bereichId]
|
||||
);
|
||||
|
||||
if ($title) {
|
||||
$ortDistrictTitles[] = $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ort['orteDistrictsTitles'] = $ortDistrictTitles;
|
||||
$filteredOrte[] = $ort;
|
||||
}
|
||||
}
|
||||
|
||||
$template->angebote = $filteredOffers;
|
||||
$template->orte = $filteredOrte;
|
||||
}
|
||||
|
||||
// Listenansicht
|
||||
if ($template->getName() === 'cm_listing_angebote') {
|
||||
$raw = StringUtil::deserialize($template->angebotGemeinde, true);
|
||||
$gemeindeIds = [];
|
||||
|
||||
foreach ($raw as $item) {
|
||||
if (is_array($item) && isset($item['value'])) {
|
||||
$gemeindeIds[] = (string) $item['value'];
|
||||
} elseif (is_scalar($item)) {
|
||||
$gemeindeIds[] = (string) $item;
|
||||
}
|
||||
}
|
||||
|
||||
$template->angebotGemeindeIds = $gemeindeIds;
|
||||
|
||||
// Gemeindebezogene Aliase der Districts laden
|
||||
$districtIds = [];
|
||||
|
||||
if (!empty($gemeindeIds)) {
|
||||
$rows = $this->connection->fetchAllAssociative(
|
||||
'SELECT g.gemeindeBereich, d.districtsAlias
|
||||
FROM ctlg_gemeinden g
|
||||
LEFT JOIN ctlg_districts d ON g.gemeindeBereich = d.id
|
||||
WHERE g.id IN (?)',
|
||||
[$gemeindeIds],
|
||||
[Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if (!empty($row['districtsAlias'])) {
|
||||
$districtIds[] = $row['districtsAlias'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$template->angebotDistrictIds = array_values(array_unique($districtIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\Template;
|
||||
use Contao\StringUtil;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class PlaceListener
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
private Connection $connection;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Connection $connection)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Hook("parseTemplate")
|
||||
*/
|
||||
public function onParseTemplate(Template $template): void
|
||||
{
|
||||
// Nur für Template cm_listing_orte.html5
|
||||
if ($template->getName() !== 'cm_listing_orte') {
|
||||
return;
|
||||
}
|
||||
|
||||
$gemeindenRaw = StringUtil::deserialize($template->ortGemeinde, true);
|
||||
|
||||
// Gemeindelisten extrahieren
|
||||
$gemeindeIds = array_map(function ($item) {
|
||||
if (is_array($item) && isset($item['value'])) {
|
||||
return (string) $item['value'];
|
||||
}
|
||||
return (string) $item;
|
||||
}, $gemeindenRaw);
|
||||
|
||||
$template->ortGemeindeIds = $gemeindeIds;
|
||||
|
||||
// Passende District-Aliase ermitteln
|
||||
$districtAliases = [];
|
||||
|
||||
if (!empty($gemeindeIds)) {
|
||||
$rows = $this->connection->fetchAllAssociative(
|
||||
'SELECT d.districtsAlias
|
||||
FROM ctlg_gemeinden g
|
||||
LEFT JOIN ctlg_districts d ON g.gemeindeBereich = d.id
|
||||
WHERE g.id IN (?)',
|
||||
[$gemeindeIds],
|
||||
[Connection::PARAM_INT_ARRAY]
|
||||
);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if (!empty($row['districtsAlias'])) {
|
||||
$districtAliases[] = (string) $row['districtsAlias'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$template->ortDistrictIds = array_values(array_unique($districtAliases));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class KsNossenerlandBundle extends Bundle
|
||||
{
|
||||
public function getPath(): string
|
||||
{
|
||||
return dirname(__DIR__);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\Model;
|
||||
|
||||
use PDO;
|
||||
|
||||
class ExternalLocationModel
|
||||
{
|
||||
/**
|
||||
* Erstellt die PDO-Verbindung zur externen Datenbank.
|
||||
*
|
||||
* @return PDO
|
||||
*/
|
||||
private static function getConnection()
|
||||
{
|
||||
return new PDO(
|
||||
'mysql:host=localhost;dbname=nossener-land_1;charset=utf8mb4',
|
||||
'nossener-land',
|
||||
'KZTpsoDlKFJCa7tzETDirtbJ',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Einträge aus der Tabelle 'tl_location' und sortiert sie nach title (A-Z).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function findAllLocations()
|
||||
{
|
||||
try {
|
||||
$pdo = self::getConnection();
|
||||
|
||||
// Alphabetisch nach `title` sortieren
|
||||
$stmt = $pdo->query('SELECT id, title FROM tl_location ORDER BY title ASC');
|
||||
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($locations)) {
|
||||
error_log("Die Tabelle 'tl_location' ist leer.");
|
||||
} else {
|
||||
foreach ($locations as $location) {
|
||||
error_log("Gefundene Location: ID={$location['id']}, Title={$location['title']}");
|
||||
}
|
||||
}
|
||||
|
||||
return $locations;
|
||||
} catch (\PDOException $e) {
|
||||
error_log("PDO-Fehler: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Mummert\KsNossenerlandBundle\Service;
|
||||
|
||||
use SoapClient;
|
||||
use SoapHeader;
|
||||
use Exception;
|
||||
use StdClass;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
class SoapClientService
|
||||
{
|
||||
private $client;
|
||||
private $apiKey;
|
||||
private $vid;
|
||||
private $connection;
|
||||
|
||||
public function __construct(Connection $connection, $wsdlUrl, $endpointUrl, $apiKey, $vid)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->apiKey = $apiKey;
|
||||
$this->vid = $vid;
|
||||
|
||||
$options = [
|
||||
'location' => $endpointUrl,
|
||||
'trace' => false,
|
||||
'exception' => true,
|
||||
];
|
||||
|
||||
try {
|
||||
$this->client = new SoapClient($wsdlUrl, $options);
|
||||
$auth = new StdClass;
|
||||
$auth->apiKey = $this->apiKey;
|
||||
$auth->vid = $this->vid;
|
||||
$header = new SoapHeader($endpointUrl, "APIValidate", $auth);
|
||||
$this->client->__setSoapHeaders($header);
|
||||
} catch (Exception $e) {
|
||||
error_log('SOAP Client initialization failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getEventTypeByPid($pid)
|
||||
{
|
||||
return match ($pid) {
|
||||
1 => '1',
|
||||
2 => '4',
|
||||
3 => '9',
|
||||
default => '9',
|
||||
};
|
||||
}
|
||||
|
||||
private function transformEventData(array $eventData): array
|
||||
{
|
||||
// ggf. verkürzt für Übersichtlichkeit – hier bleibt deine Mapping-Logik erhalten
|
||||
$placeIdMapping = [
|
||||
65 => 4694, // Diakoniestation Dittmannsdorf
|
||||
61 => 4695, // Ev. Kindergarten Dittmannsdorf
|
||||
53 => 4699, // Friedhof Bieberstein
|
||||
52 => 4678, // Friedhof Burkhardswalde
|
||||
51 => 4672, // Friedhof Deutschenbora
|
||||
50 => 4696, // Friedhof Dittmannsdorf
|
||||
49 => 4681, // Friedhof Heynitz
|
||||
48 => 4702, // Friedhof Hirschfeld
|
||||
47 => 4685, // Friedhof Krögis
|
||||
34 => 4660, // Friedhof Leuben
|
||||
81 => 4688, // Friedhof Miltitz
|
||||
46 => 4706, // Friedhof Neukirchen
|
||||
44 => 4667, // Friedhof Nossen
|
||||
43 => 4709, // Friedhof Obergruna
|
||||
36 => 4663, // Friedhof Planitz
|
||||
31 => 4656, // Friedhof Raußlitz
|
||||
42 => 4712, // Friedhof Reinsberg
|
||||
41 => 4675, // Friedhof Rothschönberg
|
||||
33 => 4648, // Friedhof Rüsseina
|
||||
39 => 4715, // Friedhof Siebenlehn
|
||||
38 => 4690, // Friedhof Tanneberg
|
||||
37 => 4692, // Friedhof Taubenheim
|
||||
30 => 4651, // Friedhof Wendischbora
|
||||
35 => 4665, // Friedhof Ziegenhain
|
||||
45 => 4668, // Friedhofskapelle Nossen
|
||||
68 => 4673, // Gemeindehaus Deutschenbora
|
||||
27 => 4652, // Gemeindehaus Wendischbora
|
||||
73 => 4700, // Gemeinderaum Bieberstein
|
||||
71 => 4697, // Gemeinderaum Dittmannsdorf
|
||||
63 => 4682, // Gemeinderaum Heynitz
|
||||
77 => 4703, // Gemeinderaum Hirschfeld
|
||||
55 => 4686, // Gemeinderaum in der Kirche Krögis
|
||||
78 => 4707, // Gemeinderaum Neukirchen
|
||||
67 => 4669, // Gemeinderaum Nossen
|
||||
72 => 4710, // Gemeinderaum Obergruna
|
||||
26 => 4657, // Gemeinderaum Raußlitz
|
||||
75 => 4713, // Gemeinderaum Reinsberg
|
||||
69 => 4676, // Gemeinderaum Rothschönberg
|
||||
60 => 4716, // Gemeinderaum Siebenlehn
|
||||
28 => 4653, // Großer Gemeinderaum Wendischbora
|
||||
70 => 4719, // Heimatstube
|
||||
14 => 4701, // Kirche Bieberstein
|
||||
8 => 4679, // Kirche Burkhardswalde
|
||||
2 => 4674, // Kirche Deutschenbora
|
||||
15 => 4698, // Kirche Dittmannsdorf
|
||||
5 => 4683, // Kirche Heynitz
|
||||
22 => 4704, // Kirche Hirschfeld
|
||||
6 => 4687, // Kirche Krögis
|
||||
10 => 4661, // Kirche Leuben
|
||||
4 => 1409, // Kirche Miltitz
|
||||
16 => 4708, // Kirche Neukirchen
|
||||
1 => 4670, // Kirche Nossen
|
||||
21 => 4711, // Kirche Obergruna
|
||||
12 => 4664, // Kirche Planitz
|
||||
18 => 4658, // Kirche Raußlitz
|
||||
13 => 4714, // Kirche Reinsberg
|
||||
3 => 4677, // Kirche Rothschönberg
|
||||
17 => 4647, // Kirche Rüsseina
|
||||
20 => 4717, // Kirche Siebenlehn
|
||||
9 => 4691, // Kirche Tanneberg
|
||||
7 => 4693, // Kirche Taubenheim
|
||||
19 => 4654, // Kirche Wendischbora
|
||||
11 => 4666, // Kirche Ziegenhain
|
||||
32 => 4649, // Kirchfriedhof Rüsseina
|
||||
29 => 4655, // Kleiner Gemeinderaum Wendischbora
|
||||
83 => 4720, // Ludwig-Richter-Saal
|
||||
56 => 4680, // Pfarrhaus Burkhardswalde
|
||||
62 => 4684, // Pfarrhaus Heynitz
|
||||
74 => 4705, // Pfarrhaus Hirschfeld
|
||||
58 => 4662, // Pfarrhaus Leuben
|
||||
59 => 4689, // Pfarrhaus Miltitz
|
||||
54 => 4671, // Pfarrhaus Nossen
|
||||
25 => 4659, // Pfarrhaus Raußlitz
|
||||
23 => 4650, // Pfarrhaus Rüsseina
|
||||
76 => 4718, // Pfarrhaus Siebenlehn
|
||||
96 => 5032, // BadePark Reinsberg
|
||||
95 => 5191, // Kloster Altzella
|
||||
101 => 5508, // Schlosskapelle Rothschönberg
|
||||
|
||||
|
||||
];
|
||||
|
||||
$placeid = $placeIdMapping[$eventData['catalog_ort']] ?? null;
|
||||
$eventType = isset($eventData['pid']) ? $this->getEventTypeByPid($eventData['pid']) : '9';
|
||||
|
||||
$kontaktIds = explode(',', $eventData['catalog_kontakt'] ?? '');
|
||||
$contacts = [];
|
||||
$email = '';
|
||||
|
||||
foreach ($kontaktIds as $index => $kontaktId) {
|
||||
$kontaktId = trim($kontaktId);
|
||||
if ($kontaktId) {
|
||||
$contactData = $this->connection->fetchAssociative(
|
||||
'SELECT contactsTerm, contactsFirstname, contactsLastname, contactsEmail FROM ctlg_contacts WHERE id = ?',
|
||||
[$kontaktId]
|
||||
);
|
||||
if ($contactData) {
|
||||
$contacts[] = $contactData['contactsTerm'] . ' ' . $contactData['contactsFirstname'] . ' ' . $contactData['contactsLastname'];
|
||||
if ($index === 0 && !empty($contactData['contactsEmail'])) {
|
||||
$email = $contactData['contactsEmail'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contributors = !empty($eventData['contributors']) ? explode(',', $eventData['contributors']) : [];
|
||||
$allPeople = array_merge($contacts, $contributors);
|
||||
$people = !empty($allPeople) ? implode(', ', $allPeople) : '';
|
||||
|
||||
return [
|
||||
'placeid' => $placeid,
|
||||
'eventType' => $eventType,
|
||||
'people' => $people,
|
||||
'email' => $email
|
||||
];
|
||||
}
|
||||
|
||||
public function sendEventToSoapAPI($eventData)
|
||||
{
|
||||
if ($eventData['published'] == '1') {
|
||||
$destination = 'extern';
|
||||
$status = 'ok';
|
||||
} else {
|
||||
$destination = 'intern';
|
||||
$status = 'standby';
|
||||
}
|
||||
|
||||
$transformed = $this->transformEventData($eventData);
|
||||
$placeid = $transformed['placeid'];
|
||||
$eventType = $transformed['eventType'];
|
||||
$people = $transformed['people'];
|
||||
$email = $transformed['email'];
|
||||
|
||||
if ($placeid === null) {
|
||||
error_log('❌ Keine gültige placeid für Event ' . $eventData['id']);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($eventData['addTime']) && $eventData['addTime'] == '1') {
|
||||
$start = date('Y-m-d', $eventData['startDate']) . ' ' . (!empty($eventData['startTime']) ? date('H:i:s', $eventData['startTime']) : '00:00:00');
|
||||
$end = date('Y-m-d', $eventData['endDate']) . ' ' . (!empty($eventData['endTime']) ? date('H:i:s', $eventData['endTime']) : '00:00:00');
|
||||
} else {
|
||||
$start = date('Y-m-d', $eventData['startDate']) . ' 00:00:00';
|
||||
$end = date('Y-m-d', $eventData['endDate']) . ' 00:00:00';
|
||||
}
|
||||
|
||||
$link = 'https://kirchspiel-nossener-land.de/termine/' . $eventData['alias'];
|
||||
$menue1 = (isset($eventData['godi_options']) && in_array('2', explode(',', $eventData['godi_options']))) ? 100 : null;
|
||||
|
||||
$event = [
|
||||
'externalid' => strval($eventData['id']),
|
||||
'eventexternalid' => strval($eventData['id']),
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'title' => html_entity_decode($eventData['title'], ENT_QUOTES, 'UTF-8'),
|
||||
'inputmaskid' => 1,
|
||||
'personid' => 2001,
|
||||
'destination' => $destination,
|
||||
'placeid' => $placeid,
|
||||
'status' => $status,
|
||||
'shortdescription' => html_entity_decode(strip_tags($eventData['subTitle'] ?? ''), ENT_QUOTES, 'UTF-8'),
|
||||
'longdescription' => html_entity_decode(strip_tags($eventData['teaser'] ?? ''), ENT_QUOTES, 'UTF-8'),
|
||||
'link' => $link,
|
||||
'email' => $email,
|
||||
'eventtype' => $eventType,
|
||||
'kat' => '3',
|
||||
'kat2' => null,
|
||||
'textline1' => $people,
|
||||
'people' => null,
|
||||
'menue1' => $menue1,
|
||||
'kollekte' => 0,
|
||||
];
|
||||
|
||||
try {
|
||||
return $this->client->saveEvent(array_merge(['apiKey' => $this->apiKey], $event));
|
||||
} catch (Exception $e) {
|
||||
error_log('❌ Fehler bei Export von Event ' . $eventData['id'] . ': ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteEventByExternalId(string $externalId): bool
|
||||
{
|
||||
try {
|
||||
$response = $this->client->deleteEvent($externalId, true);
|
||||
|
||||
// Optional prüfen, ob eine success-Eigenschaft existiert
|
||||
if (is_object($response) && property_exists($response, 'success')) {
|
||||
return $response->success === true;
|
||||
}
|
||||
|
||||
// Falls keine success-Eigenschaft, dann success annehmen, wenn kein Fehler auftrat
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("❌ Fehler beim Löschen von Event $externalId: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchRemoteExternalIds(): array
|
||||
{
|
||||
$url = 'https://kalender.evlks.de/json?vid=' . $this->vid;
|
||||
|
||||
$json = @file_get_contents($url);
|
||||
if (!$json) {
|
||||
error_log("❌ Fehler beim Abrufen der JSON-Daten von $url");
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = json_decode($json, true);
|
||||
if (!is_array($data)) {
|
||||
error_log("❌ Ungültiges JSON-Format für Kalenderdaten");
|
||||
return [];
|
||||
}
|
||||
|
||||
$externalIds = [];
|
||||
foreach ($data as $entry) {
|
||||
if (!empty($entry['Veranstaltung']['_event_EXTERNAL_ID'])) {
|
||||
$externalIds[] = $entry['Veranstaltung']['_event_EXTERNAL_ID'];
|
||||
}
|
||||
}
|
||||
|
||||
return $externalIds;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user