# CalDAV Sync Bundle (Contao 5.7) Produktionsreifes 2-Way-CalDAV-Sync-Bundle fuer Contao 5.7 (PHP 8.4). Es synchronisiert Events zwischen `tl_calendar_events` und einem oder mehreren CalDAV-Kalendern. ## Umfang (V1) - Kein RRULE/EXDATE/RECURRENCE-ID-Support - Kein Backend-Button, Sync nur ueber Command/Cron - Harte Loeschung auf beiden Seiten, wenn das Gegenstueck fehlt - Konfliktaufloesung pro Event ueber Last-Modified-Wins (mit Toleranz) - Contao-only-Felder werden nicht in den Sync-Hash aufgenommen ## Voraussetzungen - PHP `^8.4` - Contao `^5.7` - `symfony/http-client` `^7.3` - `sabre/vobject` `^4.5` ## Installation ### 1) Bundle als Composer-Abhaengigkeit einbinden Bei lokalem Bundle-Checkout z. B. im Projekt-Composer: ```json { "repositories": [ { "type": "path", "url": "bundles/caldav-sync-bundle", "options": { "symlink": true } } ], "require": { "mummert/caldav-sync-bundle": "*@dev" } } ``` Dann installieren: ```bash composer update mummert/caldav-sync-bundle php vendor/bin/contao-console contao:migrate php vendor/bin/contao-console cache:clear ``` ## Backend-Konfiguration (Kalender) In `System -> Kalender` den gewuenschten Kalender oeffnen und unter CalDAV konfigurieren: - `CalDAV Sync aktivieren` - `CalDAV URL` - `CalDAV Benutzername` - `CalDAV Passwort` - `CalDAV Autor` (Pflichtfeld, wird fuer importierte Events gesetzt) - optional `CalDAV Zeitzone` (Fallback: `UTC`) - `Remote-Kalender` als Mehrfachauswahl (CheckboxWizard) Hinweis zur Mehrfachauswahl: - Es koennen mehrere Remote-Kalender unter einer Verbindung ausgewaehlt werden. - Events aus abgewaehlten Remote-Kalendern werden beim naechsten Pull fuer den betroffenen Contao-Kalender entfernt. ## Sync ausfuehren ```bash php bin/console contao:caldav:sync --direction=both php bin/console contao:caldav:sync --calendar=4 --direction=pull php bin/console contao:caldav:sync --direction=push --dry-run ``` Optionen: - `--calendar=ID`: nur einen `tl_calendar` synchronisieren - `--direction=pull|push|both`: Richtung waehlen (Default `both`) - `--dry-run`: keine Schreiboperationen lokal/remote ## Sync-Verhalten im Detail ### Reihenfolge bei `--direction=both` 1. Push (lokal -> remote) 2. Pull (remote -> lokal) ### Konfliktregel (Last-Modified-Wins) - Lokal wird ueber `tl_calendar_events.tstamp` bewertet. - Remote wird ueber `LAST-MODIFIED` bzw. `DTSTAMP` bewertet. - Es gilt eine Toleranz von 120 Sekunden. - Wenn Remote-Zeitstempel fehlen, wird konservativ lokal bevorzugt. ### Loeschverhalten - Pull: lokale Gegenstuecke ohne Remote-Pendant werden geloescht. - Push: Remote-Events ohne lokales Pendant werden geloescht. - Zusaetzlich entfernt der Runner bei Pull importierte Events aus inzwischen abgewaehlten Remote-Kalendern. ### Publikationsverhalten - Remote-importierte Events werden auf `published = 1` gesetzt. ## Feldmapping Bidirektional (hash-relevant): - `title` - `start`/`end` - `all-day` (`addTime`) - `description` <-> `teaser` (Plaintext/HTML-Konvertierung) - `location` - `url` (optional) Technische Sync-Felder in `tl_calendar_events`: - `caldavCalendarHref` - `caldavUid` - `caldavHref` - `caldavEtag` - `caldavSyncHash` - `caldavLastSync` - `caldavOrigin` - `caldavSyncState` Alias-Verhalten: - Alias wird bei Remote-Neuanlage als `YYYY-MM-DD_slug` erzeugt. - Maximale Laenge: 40 Zeichen. - Kollisionen werden mit Suffix (`_2`, `_3`, ...) aufgeloest. ## Wichtige Architekturpunkte - `SyncRunner`: Orchestrierung pro Kalender und Richtung - `RemoteToLocalSynchronizer`: Pull + lokale Upserts/Deletes - `LocalToRemoteSynchronizer`: Push + remote Upserts/Deletes - `SyncFieldExtractor`: Mapping, Datumslogik, Teaser-Konvertierung, Aliasbildung - `RemoteCalendarReader` / `RemoteCalendarWriter`: CalDAV Lesen/Schreiben - `ContaoCalendarEventRepository`: DB-Zugriff inkl. schema-toleranter Writes ## Grenzen und Hinweise - V1 behandelt keine Serienereignisse. - Sync ist command-getrieben; ein Cronjob sollte das Command zyklisch starten. - CalDAV-Passwort wird als Klartext fuer Authentifizierung verwendet. ## Troubleshooting - Keine Kalender in der Mehrfachauswahl: - URL, Benutzername, Passwort pruefen - Server muss CalDAV-Discovery/PROPFIND erlauben - Unerwartete Konflikte: - `tstamp` lokal und `LAST-MODIFIED` remote vergleichen - Zeitzonen-Setup im Kalender pruefen - Schreibfehler beim Push: - ETag-Precondition und Zugriffsrechte am CalDAV-Server pruefen