# CalDAV Sync Bundle (Contao 5.7) Internes Bundle fuer 2-Way-CalDAV-Sync in Contao 5.7 (PHP 8.4). Diese README ist bewusst technisch gehalten (Prinzip, Logik, Betriebs-Commands) und kein oeffentliches Tutorial. ## 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 ## Betriebsrelevante Konfiguration (tl_calendar) Verwendete CalDAV-Felder pro Kalender: - `caldavSyncEnabled` - `caldavUrl` - `caldavUsername` - `caldavPassword` - `caldavAuthorId` - `caldavTimezone` (Fallback `UTC`) - `caldavCalendarHrefs` (Mehrfachauswahl) - `caldavPastSyncRange` (`none|all|1y|2y`) - `caldavFutureSyncRange` (`all|1y|2y`) - `caldavSyncCtags` (technischer Cache pro Remote-URL) Mehrfachauswahl-Logik: - Ein Contao-Kalender kann mehrere Remote-Kalender referenzieren. - Beim Pull werden importierte Events aus abgewaehlten Remote-Kalendern 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 Ergaenzende Betriebs-Commands: ```bash php vendor/bin/contao-console contao:migrate --no-interaction php vendor/bin/contao-console cache:clear ``` ## Sync-Verhalten im Detail ### Reihenfolge bei `--direction=both` 1. Push (lokal -> remote) 2. Pull (remote -> lokal) ### Delta-Sync (CTAG + ETAG) - CTAG wird per `PROPFIND` gelesen und pro Kalender-URL in `caldavSyncCtags` gespeichert. - Wenn CTAG unveraendert ist, wird Pull fruehzeitig uebersprungen. - Delta-Inventar per `REPORT` (href + etag). - `GET` nur fuer neue/geaenderte Remote-Objekte. ### 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. - Alle Vergleiche erfolgen auf Unix-Timestamps. ### Kalenderbindung beim Matching - Events werden pro `tl_calendar_events.pid` geladen und nur in diesem Kalender verarbeitet. - Matcher nutzt `caldavHref` und `caldavUid`, optional mit explizitem Guard auf erwartete `calendarId`. - Zielkalender fuer Push wird ueber `caldavCalendarHref` auf die aktuelle Config-URL aufgeloest. ### 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. - Bei aktivem Zeitfenster werden Events ausserhalb des Fensters nicht aktiv geloescht. ### 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) Hash-Regel: - SyncHash basiert ausschliesslich auf den oben genannten bidirektionalen Feldern. - Push erfolgt nur bei Hash-Aenderung (oder Initialzustand ohne gespeicherten Hash). 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. ## Kernkomponenten - `SyncRunner`: Orchestrierung je Kalender und Richtung - `LocalToRemoteSynchronizer`: Push + LMW + Hash-Guard + Deletes - `RemoteToLocalSynchronizer`: Pull + LMW + Delta + Deletes - `RemoteCalendarReader`: CTAG/REPORT/GET-Deltalogik - `SyncFieldExtractor`: Feldmapping inkl. all-day-Umrechnung ## 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