160 lines
5.1 KiB
Markdown
160 lines
5.1 KiB
Markdown
# 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
|