update
This commit is contained in:
113
.paul/HANDOFF-2026-05-06.md
Normal file
113
.paul/HANDOFF-2026-05-06.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# PAUL Handoff
|
||||
|
||||
**Date:** 2026-05-06
|
||||
**Status:** paused (end of productive session)
|
||||
|
||||
---
|
||||
|
||||
## READ THIS FIRST
|
||||
|
||||
You have no prior context. This document tells you everything.
|
||||
|
||||
**Project:** jachty3.pagedev.pl — WordPress plugin `yacht-booking-system` (rezerwacje jachtów z dwukierunkową synchronizacją Google Calendar przez iCal)
|
||||
**Core value:** Klienci mogą sprawdzić dostępność jachtu i złożyć rezerwację przez stronę bez kontaktu z właścicielem.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
**Version:** 1.0.0 (Beta → szybko zbliża się do v1.0 production)
|
||||
**Milestone:** v1.0 Production Release — 92%
|
||||
**Phase:** 9 of 9 — Finalizacja (60%, 3/5 planów)
|
||||
**Plan:** 09-03 — **Complete** (loop closed)
|
||||
|
||||
**Loop Position:**
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop 09-03 complete, ready for 09-04]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Was Done (sesja 2026-05-06)
|
||||
|
||||
Sesja zaczęła z: zamkniętą pętlą 09-01 (UX/UI kalendarza). Wykonano 2 pełne pętle PLAN→APPLY→UNIFY:
|
||||
|
||||
### Pętla 09-02 — Globalna synchronizacja iCal
|
||||
- Wprowadzono dwukierunkową synchronizację z **jednym wspólnym Google Calendar** (przez iCal feed, bez OAuth)
|
||||
- Globalny iCal Export feed: `/yacht-ical-global/{token}.ics` — wszystkie rezerwacje w jednym pliku, każdy event z prefiksem `"{nazwa_jachtu} - {klient}"`
|
||||
- Globalny iCal Import — parser dopasowuje prefiks przed `" - "` do `post_title` lub `_yacht_gcal_alias` (case-insensitive, mb_*)
|
||||
- Anti-loop (eventy z source `ical_import_global` nie są re-eksportowane), stale cleanup, idempotencja po UID
|
||||
- Pole `_yacht_gcal_alias` w yacht-edit (krótki alias jak "Maja" zamiast pełnej nazwy)
|
||||
- Settings UI: nowa sekcja "Globalna synchronizacja iCal"
|
||||
|
||||
### Pętla 09-03 — Cleanup OAuth + per-yacht iCal
|
||||
- Plugin sprowadzony do JEDNEGO mechanizmu sync (zamiast 4 konkurujących)
|
||||
- **Usunięte 3 pliki**: `class-sync-controller.php`, `class-gcal-service.php`, `class-oauth-handler.php` + cały katalog `integrations/google-calendar/`
|
||||
- Wycięte: cała sekcja OAuth UI w Settings, handle_oauth_callback, save_gcal_credentials, notice connected/disconnected
|
||||
- Wycięte per-yacht iCal: 8 metod, cron `yacht_booking_ical_import`, rewrite rule `^yacht-ical/(\d+)/...`, pola "Google Calendar ID" + "iCal Import URL" + "iCal Feed URL" w yacht-edit, kolumna w yacht-list
|
||||
- Cleanup migration w `Installer::migrate()` — version-gated (idempotent), kasuje 4 stale meta keys, 6 stale options, bookings z source `ical_import`, 5 cron hooków. Bez admin notice.
|
||||
- ~700+ linii martwego kodu usuniętych
|
||||
|
||||
---
|
||||
|
||||
## What's In Progress
|
||||
|
||||
Nic — obie pętle czysto zamknięte. Zero deferred items, zero open issues.
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
**Immediate:** Klient musi wykonać deploy FTP zmienionych plików (10 zmodyfikowanych + 3 usunięte). Lista w `.paul/changelog/2026-05-06.md`. Po deploy:
|
||||
1. Aktywacja pluginu (lub deactivate→activate jeśli już aktywny) — wyzwoli `Installer::migrate()` → cleanup
|
||||
2. Settings → Google Calendar → "Globalna synchronizacja iCal" → wkleić iCal URL Google Calendar admina → "Importuj teraz"
|
||||
3. Subscribe global Export URL w Google Calendar (Inne kalendarze → Z URL-a)
|
||||
4. Format eventów w GCal: `"NazwaJachtu - opis"` (np. "Maja - Kowalski 5 osób")
|
||||
|
||||
**After that:** Zaplanować 09-04 (Security audit) lub 09-05 (Testy + tłumaczenia + dokumentacja). 09-04 powinien iść przed 09-05.
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.paul/STATE.md` | Live project state |
|
||||
| `.paul/ROADMAP.md` | Phase overview (5 planów w fazie 9, 3 ukończone) |
|
||||
| `.paul/phases/09-finalizacja/09-02-SUMMARY.md` | Globalna iCal sync — szczegóły implementacji |
|
||||
| `.paul/phases/09-finalizacja/09-03-SUMMARY.md` | Cleanup — co usunięto i czego nie ruszono |
|
||||
| `.paul/changelog/2026-05-06.md` | Lista WSZYSTKICH zmienionych plików dzisiaj (do FTP deploy) |
|
||||
| `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php` | Globalny iCal export (output_global_ics) |
|
||||
| `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php` | Globalny iCal import (run_global_import + parser prefiksu) |
|
||||
| `wp-content/plugins/yacht-booking-system/includes/class-installer.php` | Cleanup migration |
|
||||
|
||||
---
|
||||
|
||||
## Decyzje sesji (do pamięci)
|
||||
|
||||
- Klient woli iCal feedy (publiczne URL) zamiast OAuth — prostsza konfiguracja
|
||||
- Identyfikacja jachtu po prefiksie `" - "` w SUMMARY eventu (case-insensitive, exact match po lowercase post_title/alias — ŻADNYCH partial)
|
||||
- Eventy w GCal bez separatora lub bez dopasowania → ignorowane (nie blokują)
|
||||
- Migration "po cichu" — bez admin notice
|
||||
- Per-yacht bookings z source `ical_import` USUWANE w migration (klient ponownie zaimportuje), nie migrowane do `ical_import_global`
|
||||
|
||||
---
|
||||
|
||||
## Outstanding considerations
|
||||
|
||||
- Jeśli klient w przyszłości będzie miał problem z dopasowaniem (długie nazwy jachtów, kolizje prefiksów) — używać pola **"Alias dla Google Calendar"** w yacht-edit
|
||||
- Plugin jest gotowy produkcyjnie w warstwie integracji GCal. Pozostaje 09-04 (security) i 09-05 (testy/i18n/docs) do v1.0 release.
|
||||
|
||||
---
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Przeczytaj `.paul/STATE.md` (najnowsza pozycja)
|
||||
2. Sprawdź `.paul/HANDOFF-2026-05-06.md` (ten plik)
|
||||
3. Uruchom `/paul:resume` lub `/paul:progress`
|
||||
4. Najprawdopodobniej: `/paul:plan` dla 09-04 (Security audit)
|
||||
|
||||
---
|
||||
|
||||
*Handoff created: 2026-05-06*
|
||||
@@ -45,9 +45,10 @@ Szczegóły w `wp-content/plugins/yacht-booking-system/PROJECT-STATUS.md`.
|
||||
|
||||
**Plans:**
|
||||
- [ ] 09-01: UX/UI polish kalendarza — half-day na pierwszym/ostatnim dniu rezerwacji + paleta widgetu pasująca do strony
|
||||
- [ ] 09-02: Settings Page — uzupełnienie brakujących opcji
|
||||
- [ ] 09-03: Security audit i poprawki
|
||||
- [ ] 09-04: Testy + tłumaczenia + dokumentacja
|
||||
- [x] 09-02: Globalna synchronizacja iCal — wspólny Google Calendar z podziałem na jachty po prefiksie nazwy w tytule eventu (export feed + import URL + alias jachtu) ✅ 2026-05-06
|
||||
- [x] 09-03: Cleanup OAuth + per-yacht iCal — usunięcie martwego kodu (3 pliki GCal/OAuth, per-yacht feedy, pole "Google Calendar ID") + cleanup migration "po cichu" ✅ 2026-05-06
|
||||
- [ ] 09-04: Security audit i poprawki
|
||||
- [ ] 09-05: Testy + tłumaczenia + dokumentacja
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-05-05*
|
||||
|
||||
@@ -11,20 +11,20 @@ See: .paul/PROJECT.md (updated 2026-05-05)
|
||||
|
||||
Milestone: v1.0 Production Release (v1.0.0)
|
||||
Phase: 9 of 9 (Finalizacja) — In progress
|
||||
Plan: 09-01 — Complete (UX/UI kalendarza)
|
||||
Status: Loop closed, ready for next plan (09-02)
|
||||
Last activity: 2026-05-06 — Closed loop 09-01 (skos 45°, ciemne ramki, paleta brand strony)
|
||||
Plan: 09-03 — Complete (Cleanup OAuth + per-yacht iCal)
|
||||
Status: Loop closed, ready for next plan (09-04)
|
||||
Last activity: 2026-05-06 — Closed loop 09-03 (3 pliki OAuth usunięte, per-yacht iCal wycofany, migration po cichu)
|
||||
|
||||
Progress:
|
||||
- Milestone: [████████░░] 82%
|
||||
- Phase 9: [██░░░░░░░░] 25% (1 of 4 plans complete)
|
||||
- Milestone: [█████████░] 92%
|
||||
- Phase 9: [██████░░░░] 60% (3 of 5 plans complete)
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop 09-01 complete, ready for 09-02]
|
||||
✓ ✓ ✓ [Loop 09-03 complete, ready for 09-04]
|
||||
```
|
||||
|
||||
## Accumulated Context
|
||||
@@ -51,10 +51,15 @@ None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-05-06
|
||||
Stopped at: Loop 09-01 closed (UX/UI kalendarza ukończone, klient zaakceptował)
|
||||
Next action: Run /paul:plan to plan 09-02 (Settings Page — uzupełnienie brakujących opcji)
|
||||
Resume file: .paul/phases/09-finalizacja/09-01-SUMMARY.md
|
||||
Last session: 2026-05-06 (paused — end of productive day, 2 pełne pętle zamknięte)
|
||||
Stopped at: Loop 09-03 zamknięty + handoff utworzony
|
||||
Next action: Run /paul:plan to plan 09-04 (Security audit). Klient wykonuje deploy FTP zmienionych plików i aktywację pluginu (cleanup migration uruchomi się automatycznie).
|
||||
Resume file: .paul/HANDOFF-2026-05-06.md
|
||||
Resume context:
|
||||
- Faza 9: 3/5 planów ukończonych (60%), milestone v1.0 92%
|
||||
- Plugin sprowadzony do jednego mechanizmu sync (globalny iCal) — kontekst w 09-02 i 09-03 SUMMARY
|
||||
- Brak open issues, brak deferred items, brak blockers
|
||||
- Sesja zaczęła z 09-01 zamknięte, kończy z 09-03 zamknięte (2 pełne pętle PLAN→APPLY→UNIFY w jednej sesji)
|
||||
|
||||
---
|
||||
*STATE.md — Updated after every significant action*
|
||||
|
||||
@@ -10,6 +10,24 @@
|
||||
- Cyfry dni `#021526` (past `#6c757d`) — przebicie globalnej reguły `body a:not([href])`
|
||||
- Legenda rozszerzona o 3-ci swatch „Dzień odbioru / zwrotu" z gradientem
|
||||
- Helper `test-add-booking.php` (token-protected, akcje add/list/remove) do smoke testu — DO USUNIĘCIA z FTP po testach
|
||||
- [Phase 9, Plan 02] Globalna synchronizacja iCal — jeden wspólny Google Calendar dla całej floty z podziałem po prefiksie nazwy jachtu w tytule eventu
|
||||
- Globalny iCal Export feed (`/yacht-ical-global/{token}.ics`) — wszystkie rezerwacje w jednym pliku, każdy event z prefiksem `"{nazwa_jachtu} - {klient}"`, anti-loop (pomija events z source `ical_import_global`)
|
||||
- Globalny iCal Import URL — pobiera kalendarz Google admina, parser dopasowuje prefiks przed `" - "` (mb_* dla polskich znaków, case-insensitive) do `post_title` lub `_yacht_gcal_alias` (priorytet alias)
|
||||
- Eventy bez separatora lub bez dopasowanego jachtu — ignorowane (logowane). Stale cleanup usuwa booking gdy event zniknął z GCal
|
||||
- Cron godzinny `yacht_booking_ical_global_import` + manualny przycisk "Importuj teraz"
|
||||
- Pole `_yacht_gcal_alias` w yacht-edit (krótki alias, np. "Maja" zamiast "Marina Maja Sailing 35")
|
||||
- Settings UI: nowa sekcja "Globalna synchronizacja iCal" z export URL (kopiuj + regeneracja tokenu), import URL, instrukcja
|
||||
- Per-yacht feedy iCal i OAuth push pozostają jako kompatybilność wsteczna (out of scope ich sprzątania)
|
||||
- [Phase 9, Plan 03] Cleanup OAuth + per-yacht iCal — plugin sprowadzony do JEDNEGO mechanizmu sync (globalny iCal)
|
||||
- Usunięte 3 pliki OAuth (`integrations/google-calendar/class-{sync-controller,gcal-service,oauth-handler}.php`) + cały katalog
|
||||
- Wycięte z bootstrap: require + setup_cron + clear_cron Sync_Controller (yacht-booking-system.php, class-yacht-booking.php)
|
||||
- Wycięte z admin: cała sekcja UI OAuth (Client ID/Secret, "Połącz", "Synchronizuj teraz", instrukcja), `handle_oauth_callback`, `save_gcal_credentials`, callback/disconnect/credentials w `process_settings_save`, notice connected/disconnected
|
||||
- Wycięte per-yacht iCal: `IMPORT_SOURCE`, `run_import`, `import_for_yacht`, `get/set_import_url`, `get_existing_import_map`, `upsert_booking`, `get_last_import_time`, cron `yacht_booking_ical_import`, rewrite rule `^yacht-ical/(\d+)/...`, `output_ics`, `get_feed_token`, `regenerate_token`, `get_feed_url`
|
||||
- Wycięte z yacht-edit: pola "Google Calendar ID", "iCal Import URL", "iCal Feed URL". Pozostaje tylko Alias.
|
||||
- Wycięte z yacht-list-table: kolumna "Google Calendar"
|
||||
- Yacht class: usunięte `get_gcal_id()`, `update_gcal_id()`
|
||||
- Cleanup migration w `Installer::migrate()` — version-gated (idempotent), kasuje 4 stale meta keys, 6 stale options OAuth, bookings z source `ical_import`, 5 stale cron hooków. "Po cichu" — bez admin notice
|
||||
- uninstall.php zaktualizowany o globalne iCal opcje + defensywne legacy gcal_* delete
|
||||
|
||||
## Zmienione pliki
|
||||
|
||||
@@ -20,5 +38,22 @@
|
||||
- `test-add-booking.php` (nowy, helper testowy)
|
||||
- `.paul/phases/09-finalizacja/09-01-PLAN.md` (nowy)
|
||||
- `.paul/phases/09-finalizacja/09-01-SUMMARY.md` (nowy)
|
||||
- `.paul/phases/09-finalizacja/09-02-PLAN.md` (nowy)
|
||||
- `.paul/phases/09-finalizacja/09-02-SUMMARY.md` (nowy)
|
||||
- `.paul/phases/09-finalizacja/09-03-PLAN.md` (nowy)
|
||||
- `.paul/phases/09-finalizacja/09-03-SUMMARY.md` (nowy)
|
||||
- `wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php` (USUNIĘTY)
|
||||
- `wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php` (USUNIĘTY)
|
||||
- `wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-oauth-handler.php` (USUNIĘTY)
|
||||
- `wp-content/plugins/yacht-booking-system/yacht-booking-system.php`
|
||||
- `wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php`
|
||||
- `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php`
|
||||
- `wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php`
|
||||
- `wp-content/plugins/yacht-booking-system/includes/class-yacht.php`
|
||||
- `wp-content/plugins/yacht-booking-system/includes/class-installer.php`
|
||||
- `wp-content/plugins/yacht-booking-system/admin/class-admin.php`
|
||||
- `wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php`
|
||||
- `wp-content/plugins/yacht-booking-system/admin/class-yacht-list-table.php`
|
||||
- `wp-content/plugins/yacht-booking-system/uninstall.php`
|
||||
- `.paul/STATE.md`
|
||||
- `.paul/ROADMAP.md`
|
||||
|
||||
@@ -31,3 +31,89 @@
|
||||
{"ts":"2026-05-05T22:15:30Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-05T22:15:41Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/jachty3.pagedev.pl/.paul/changelog/\"\",\"description\":\"Create changelog dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"i","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-05T22:15:59Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:41:23Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-02-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:41:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:41:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:41:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:43:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:43:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-installer.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:44:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-feed.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:44:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-feed.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:44:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-feed.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:44:43Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php\" && php -l \"wp-content/plugins/yacht-booking-system/includes/class-yacht.php\" && php -l \"wp-content/plugins/y","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:44:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:45:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:45:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:45:46Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php\"\",\"description\":\"PHP lint Task 2\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in wp-conten","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:45:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:46:08Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:46:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:46:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:46:39Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:47:16Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:47:36Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/admin/class-admin.php\" && php -l \"wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php\"\",\"description\":\"PHP lint Task 3 files\"},\","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:47:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:47:54Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:51:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:51:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:53:28Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-02-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:53:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:53:42Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:53:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:53:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:54:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:54:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T20:59:54Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-03-PLAN.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:00:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:00:11Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:00:18Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:00:24Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:01:39Z","tool":"Bash","cmd":"rm -v \"wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php\" \"wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php\"","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:01:48Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\yacht-booking-system.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:01:55Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\yacht-booking-system.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:02:04Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht-booking.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:02:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:02:19Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:02:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:02:50Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:03:06Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:03:57Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:04:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:04:20Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:04:28Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/yacht-booking-system.php\" && php -l \"wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php\" && php -l \"wp-content/plugins/yacht-","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:04:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:05:05Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:05:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-import.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:05:34Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php\"\",\"description\":\"Lint ical-import after cleanup\"},\"tool_response\":{\"stdout\":\"No syntax errors detect","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:05:58Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-feed.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:06:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\integrations\\\\ical\\\\class-ical-feed.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:06:24Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php\"\",\"description\":\"Lint ical-feed\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in wp-content/p","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:06:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:06:53Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\views\\\\yacht-edit.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:13Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php\"\",\"description\":\"Lint yacht-edit\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in wp-content/plugins/yac","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-yacht-list-table.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-yacht-list-table.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:44Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\admin\\\\class-admin.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:51Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:07:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-yacht.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:08:05Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/admin/class-admin.php\" && php -l \"wp-content/plugins/yacht-booking-system/admin/class-yacht-list-table.php\" && php -l \"wp-content/plugins/yacht-boo","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:08:28Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-installer.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:08:35Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-installer.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:08:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\includes\\\\class-installer.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:09:02Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\wp-content\\\\plugins\\\\yacht-booking-system\\\\uninstall.php","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:09:11Z","tool":"Bash","cmd":"php -l \"wp-content/plugins/yacht-booking-system/includes/class-installer.php\" && php -l \"wp-content/plugins/yacht-booking-system/uninstall.php\" && grep -rn \"OAuth_Handler\\\\|GCal_Service\\\\|Syn","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:09:24Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:09:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:10:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:10:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:12:39Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\phases\\\\09-finalizacja\\\\09-03-SUMMARY.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:12:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:12:56Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:13:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:13:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:13:29Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:13:41Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\changelog\\\\2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:16:13Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\HANDOFF-2026-05-06.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
{"ts":"2026-05-06T21:16:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\jachty3.pagedev.pl\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/jachty3.pagedev.pl"}
|
||||
|
||||
325
.paul/phases/09-finalizacja/09-02-PLAN.md
Normal file
325
.paul/phases/09-finalizacja/09-02-PLAN.md
Normal file
@@ -0,0 +1,325 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["09-01"]
|
||||
files_modified:
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/views/settings.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Wprowadzić dwukierunkową synchronizację z **jednym wspólnym Google Calendar** (przez iCal feed) z rozróżnieniem rezerwacji per jacht po prefiksie w tytule eventu (`"{nazwa_jachtu} - {tekst}"`). Frontend kalendarz dla danego jachtu pokazuje tylko jego rezerwacje.
|
||||
|
||||
## Purpose
|
||||
Klient (właściciel wypożyczalni) chce zarządzać rezerwacjami wszystkich jachtów w jednym Google Calendar — jednym widokiem floty. Tworzy ręcznie eventy w GCal (np. blokady, rezerwacje telefoniczne) używając prefiksu z nazwą jachtu, a strona automatycznie przypisuje je do właściwego jachtu. Jednocześnie rezerwacje złożone przez frontend trafiają z powrotem do tego samego kalendarza w spójnym formacie.
|
||||
|
||||
## Output
|
||||
- Globalny iCal Export feed (jeden URL z tokenem) — zawiera rezerwacje wszystkich jachtów z prefiksem nazwy jachtu w SUMMARY
|
||||
- Globalny iCal Import URL — parser dopasowuje prefiks do jachtu, importuje tylko rozpoznane eventy
|
||||
- Settings page — pola dla globalnego import URL i wyświetlanie globalnego export URL
|
||||
- Pole `_yacht_gcal_alias` (opcjonalny alias dla skróconej nazwy w GCal)
|
||||
- Per-yacht feedy iCal pozostają sprawne (kompatybilność wsteczna)
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
@wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
@wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
@wp-content/plugins/yacht-booking-system/includes/class-settings.php
|
||||
|
||||
<clarifications>
|
||||
- **Mechanizm** — iCal feed (publiczny URL Google Calendar) zamiast OAuth API. Powód: klient nie chce przechodzić przez OAuth, woli dodać URL i mieć synchronizację automatyczną.
|
||||
→ Odpowiedź: dwukierunkowo przez iCal — import z GCal feedu + export własnego feedu (admin subskrybuje go w GCal).
|
||||
|
||||
- **Identyfikacja jachtu** — po prefiksie w tytule eventu, separator `" - "`. Przykład: `"Maja - Kowalski 5 osób"`, `"Kubuś - blokada serwisowa"`.
|
||||
→ Odpowiedź: parser wyciąga tekst przed pierwszym `" - "`, dopasowuje case-insensitive do `post_title` jachtu lub `_yacht_gcal_alias` (jeśli ustawiony — ma priorytet).
|
||||
|
||||
- **Eventy bez rozpoznanego jachtu** — ignorowane.
|
||||
→ Odpowiedź: jeśli prefiks nie pasuje do żadnego jachtu (lub brak separatora `" - "`), event pomijany w imporcie. Brak fallbacku na "blokuj wszystkie" — klient nie chce niespodzianek.
|
||||
|
||||
- **Push do GCal** — przez ten sam globalny feed iCal (admin subskrybuje feed pluginu w GCal).
|
||||
→ Odpowiedź: rezerwacje ze strony pojawiają się w GCal po następnej refresh subskrypcji (Google odświeża iCal subscription co kilka godzin — limit Google'a, nie pluginu).
|
||||
|
||||
- **Frontend filtrowanie** — kalendarz per jacht pokazuje tylko eventy danego jachtu.
|
||||
→ Odpowiedź: bez zmian w widgecie — działa już teraz przez `_booking_yacht_id`. Wystarczy że import poprawnie przypisze yacht_id.
|
||||
</clarifications>
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Globalny iCal Export feed
|
||||
```gherkin
|
||||
Given admin ma kilka rezerwacji w bazie dla różnych jachtów (np. "Maja", "Kubuś")
|
||||
When admin otwiera URL `/yacht-ical-global/{token}.ics`
|
||||
Then odpowiedź ma Content-Type `text/calendar`
|
||||
And feed zawiera VEVENT dla każdej niezanullowanej rezerwacji wszystkich jachtów
|
||||
And SUMMARY każdego eventu ma format `"{nazwa_jachtu} - {imię_klienta}"` (lub z prefiksem `[Oczekująca]` gdy status pending)
|
||||
And UID jest stabilny (`booking-{id}@{domain}`) — kolejne fetche zwracają ten sam UID
|
||||
```
|
||||
|
||||
## AC-2: Globalny iCal Import — dopasowanie po prefiksie
|
||||
```gherkin
|
||||
Given globalny iCal Import URL jest ustawiony w Settings
|
||||
And kalendarz Google zawiera event "Maja - Test rezerwacja" (jacht "Maja" istnieje w bazie)
|
||||
And kalendarz Google zawiera event "Nieznany - cokolwiek" (brak takiego jachtu)
|
||||
And kalendarz Google zawiera event "Brak separatora" (bez " - ")
|
||||
When uruchamiany jest cron `yacht_booking_ical_import` (lub manualny przycisk)
|
||||
Then plugin tworzy/aktualizuje booking dla jachtu "Maja" z source `ical_import_global`
|
||||
And blokuje dostępność `wp_yacht_availability` tylko dla yacht_id jachtu "Maja"
|
||||
And event "Nieznany" jest pominięty (logowane jako info, brak booking)
|
||||
And event "Brak separatora" jest pominięty
|
||||
And kolejny przebieg (nawet bez zmian w GCal) nie tworzy duplikatów (idempotencja po UID)
|
||||
```
|
||||
|
||||
## AC-3: Globalny Import — czyszczenie usuniętych eventów
|
||||
```gherkin
|
||||
Given event "Maja - Foo" został zaimportowany przy poprzednim cron'ie
|
||||
When admin usuwa go z Google Calendar
|
||||
And uruchamia się kolejny cron import
|
||||
Then powiązany booking (z source `ical_import_global` i tym UID) jest usuwany
|
||||
And `Availability::clear_booking_availability()` zwalnia daty
|
||||
```
|
||||
|
||||
## AC-4: Alias jachtu (opcjonalny)
|
||||
```gherkin
|
||||
Given jacht ma `post_title="Marina Maja Sailing 35"` i `_yacht_gcal_alias="Maja"`
|
||||
And event w GCal ma SUMMARY "Maja - Klient X"
|
||||
When uruchamia się import
|
||||
Then plugin dopasowuje event do tego jachtu po aliasie
|
||||
And tworzy booking z `_booking_yacht_id` = ID tego jachtu
|
||||
```
|
||||
|
||||
## AC-5: Frontend filtrowanie (regresja)
|
||||
```gherkin
|
||||
Given booking dla jachtu "Maja" zaimportowany przez globalny iCal
|
||||
When klient otwiera stronę z widgetem `[yacht_calendar yacht_id=ID_MAJA]`
|
||||
Then kalendarz pokazuje datę rezerwacji jako zablokowaną
|
||||
And widget jachtu "Kubuś" NIE pokazuje tej daty jako zablokowanej
|
||||
```
|
||||
|
||||
## AC-6: Settings UI
|
||||
```gherkin
|
||||
Given admin otwiera Settings → zakładka Google Calendar / iCal
|
||||
When widzi sekcję "Globalna synchronizacja iCal"
|
||||
Then widzi pole "iCal Import URL" (input type=url) — globalny URL kalendarza Google
|
||||
And widzi pole tylko-do-odczytu "iCal Export URL" z linkiem `/yacht-ical-global/{token}.ics` i przyciskiem "Kopiuj"
|
||||
And widzi przycisk "Wygeneruj nowy token" (regeneruje token, unieważniając poprzedni URL)
|
||||
And widzi przycisk "Importuj teraz" (manual trigger run_global_import)
|
||||
And w yacht-edit jest nowe pole "Alias dla Google Calendar" (placeholder: nazwa jachtu) — opcjonalne
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Globalny iCal Export feed + alias jachtu</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php,
|
||||
wp-content/plugins/yacht-booking-system/includes/class-yacht.php,
|
||||
wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
||||
</files>
|
||||
<action>
|
||||
1. W `class-ical-feed.php` dodaj nową ścieżkę feed:
|
||||
- rewrite rule `^yacht-ical-global/([a-zA-Z0-9]+)\.ics$` → `index.php?yacht_ical_global=1&yacht_ical_token=$matches[1]`
|
||||
- query var `yacht_ical_global`
|
||||
- handler `handle_global_feed_request()` weryfikuje token przez `hash_equals` względem opcji `yacht_booking_global_ical_token`
|
||||
- metoda `output_global_ics()`: pobiera WSZYSTKIE bookingi (nie filtrowane po yacht), iteruje, dla każdego ustala SUMMARY = `"{nazwa_jachtu} - {imię_klienta}"` (analogicznie do istniejącego `output_ics`, ale z prefiksem nazwy jachtu z `Yacht::get_post_title( yacht_id )` zamiast tylko klienta)
|
||||
- skip bookingi ze statusem `cancelled` oraz z source `ical_import_global` (nie wysyłaj z powrotem do Google tego co stamtąd przyszło — anti-loop)
|
||||
- UID format: `booking-{id}@{domain}` (taki sam jak per-yacht — zachować stabilność)
|
||||
- X-WR-CALNAME: `"Wszystkie jachty - " . get_bloginfo('name')`
|
||||
2. Helper `get_global_feed_token()` — generuje/zwraca token z opcji, `regenerate_global_token()` — nadpisuje token.
|
||||
3. Helper `get_global_feed_url()` — zwraca pełny URL.
|
||||
4. W `class-yacht.php` dodaj akcesory:
|
||||
- `get_gcal_alias( $yacht_id )` → `get_post_meta( $yacht_id, '_yacht_gcal_alias', true )`
|
||||
- `update_gcal_alias( $yacht_id, $alias )` → sanitize_text_field + update_post_meta
|
||||
5. W `class-installer.php` (jeśli wersja schematu się ewoluuje) dodaj domyślne opcje:
|
||||
- `add_option( 'yacht_booking_global_ical_import_url', '' )`
|
||||
- `add_option( 'yacht_booking_global_ical_token', '' )` (token generowany lazy przy pierwszym fetch URL)
|
||||
- flush_rewrite_rules przy aktywacji (rewrite rule).
|
||||
|
||||
Avoid: nie modyfikuj istniejących per-yacht feedów — działają jako fallback i są używane przez OTA/iCal subscribers innych systemów.
|
||||
</action>
|
||||
<verify>
|
||||
- `php -l class-ical-feed.php` (syntax OK)
|
||||
- W przeglądarce: po zapisie pluginu i flush rewrite, URL `/yacht-ical-global/{token}.ics` zwraca .ics z eventami wszystkich jachtów, każdy z prefiksem nazwy jachtu
|
||||
- `curl /yacht-ical-global/{token}.ics | grep "SUMMARY"` zawiera np. `SUMMARY:Maja - Kowalski`
|
||||
- URL z błędnym tokenem zwraca 403
|
||||
</verify>
|
||||
<done>AC-1, AC-4 (część dot. aliasu — akcesory) zaspokojone</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Globalny iCal Import + parser prefiksu</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
</files>
|
||||
<action>
|
||||
1. Dodaj stałą `const GLOBAL_IMPORT_SOURCE = 'ical_import_global';` (osobna od per-yacht `IMPORT_SOURCE = 'ical_import'`).
|
||||
2. Nowa metoda statyczna `run_global_import()`:
|
||||
- Pobiera URL z `get_option( 'yacht_booking_global_ical_import_url' )`
|
||||
- Jeśli pusty — return false
|
||||
- `wp_remote_get` z timeout 30s
|
||||
- parse_ics (re-use istniejącej metody, jest prywatna — wystawić jako protected lub wynieść do helpera; preferowane: zmienić visibility na `protected` lub utworzyć publiczną wrapping)
|
||||
- Pre-fetch: lista jachtów + mapa `lowercase_name_or_alias → yacht_id`. Budowa: dla każdego jachtu klucz = `_yacht_gcal_alias` (jeśli != '') albo `post_title`, wszystko `mb_strtolower` + trim.
|
||||
- Pre-fetch: `existing_global_map` — bookingi z source `GLOBAL_IMPORT_SOURCE`, mapa `uid → booking_id` (analogicznie do `get_existing_import_map` ale bez filtrowania po yacht).
|
||||
- Iteracja po eventach:
|
||||
a. Skip jeśli brak uid/start/end lub event w przeszłości (`strtotime($event['end']) < time()`)
|
||||
b. Parse SUMMARY: `$pos = mb_strpos( $summary, ' - ' )`. Jeśli `$pos === false` → log "skip: no separator", continue.
|
||||
c. `$prefix = mb_strtolower( trim( mb_substr( $summary, 0, $pos ) ) )`
|
||||
d. Lookup w mapie. Jeśli brak → log "skip: yacht not found for prefix '{prefix}'", continue.
|
||||
e. Wywołaj `upsert_global_booking( $yacht_id, $event, $existing_id )` (nowa metoda — jak `upsert_booking` ale z `_booking_source = GLOBAL_IMPORT_SOURCE`, `_booking_customer_name = "Google Calendar (import)"`, post_title `"GCal: {summary}"`).
|
||||
f. Dodaj uid do `$seen_uids`.
|
||||
- Po pętli: stale cleanup — dla każdego `$uid => $booking_id` w `$existing_global_map` którego nie ma w `$seen_uids` → `Availability::clear_booking_availability($booking_id)` + `wp_delete_post($booking_id, true)`.
|
||||
- `update_option( 'yacht_booking_global_ical_last_import', current_time('mysql') )`.
|
||||
- return true.
|
||||
3. Hook cron: w `register()` dodać `add_action( 'yacht_booking_ical_global_import', array( __CLASS__, 'run_global_import' ) )` i w `setup_cron()` zarejestruj hourly.
|
||||
4. Hook do uruchomienia obu importów (per-yacht + global) z jednego cron tick'a — nie zmieniać istniejącego, dodać osobny `wp_schedule_event` dla `yacht_booking_ical_global_import`.
|
||||
|
||||
Avoid:
|
||||
- Nie usuwać per-yacht importu (kompatybilność)
|
||||
- Nie używać sztywno `strpos` po bajtach — używać `mb_*` (polskie znaki w nazwach jachtów: "Kubuś", "Maja Słoneczna")
|
||||
- Nie matchować po fragmentach — tylko exact equality po lower-case (inaczej "Maja" pasuje do "Maja Słoneczna" jednocześnie)
|
||||
</action>
|
||||
<verify>
|
||||
- `php -l class-ical-import.php`
|
||||
- Test ręczny: stworzyć w Google Calendar event "Maja - test", subskrybować feed Google iCal URL w settings, kliknąć "Importuj teraz", sprawdzić że w admin → Bookings pojawia się booking przypisany do jachtu "Maja"
|
||||
- Event "FooBar - test" (brak takiego jachtu) — nie tworzy bookingu, w error log linia "skip: yacht not found"
|
||||
- Event "BezSeparatora" — nie tworzy bookingu
|
||||
- Drugi cron tick — brak duplikatu
|
||||
- Usunięcie eventu w GCal + cron → booking zniknął, daty zwolnione w `wp_yacht_availability`
|
||||
</verify>
|
||||
<done>AC-2, AC-3, AC-4 (matching po aliasie) zaspokojone</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Settings UI + yacht-edit alias + admin actions</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/admin/class-admin.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/views/settings.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
|
||||
</files>
|
||||
<action>
|
||||
1. `settings.php` — dodaj sekcję "Globalna synchronizacja iCal":
|
||||
- Pole `yacht_booking_global_ical_import_url` (input type=url, full width)
|
||||
- Read-only display: globalny export URL (`ICal_Feed::get_global_feed_url()`) — input readonly + przycisk "Kopiuj" (JS clipboard)
|
||||
- Przycisk "Wygeneruj nowy token" (form POST z nonce + action `regenerate_global_ical_token`)
|
||||
- Przycisk "Importuj teraz" (form POST z nonce + action `run_global_ical_import`)
|
||||
- Wyświetl `yacht_booking_global_ical_last_import` ("Ostatni import: ...")
|
||||
- Krótka instrukcja: "Skopiuj Export URL i dodaj jako 'Z URL-a' w Google Calendar (Inne kalendarze → Z URL). Eventy w Google nazywaj wg wzorca: 'Nazwa jachtu - opis'. Wklej iCal URL kalendarza Google poniżej."
|
||||
2. `class-admin.php` — w `process_settings_save()` (lub equivalent) zapisz nowe pole.
|
||||
3. `class-admin.php` — `process_booking_actions()` (lub nowy `process_ical_actions()`):
|
||||
- obsłuż `regenerate_global_ical_token` → `ICal_Feed::regenerate_global_token()` + `?notice=token_regenerated` redirect
|
||||
- obsłuż `run_global_ical_import` → `ICal_Import::run_global_import()` + `?notice=import_done` redirect
|
||||
4. `yacht-edit.php` — dodaj wiersz tabeli "Alias dla Google Calendar":
|
||||
- input text `yacht_gcal_alias`, value = `Yacht::get_gcal_alias( $yacht_id )`
|
||||
- Description: "Opcjonalny krótki alias używany w tytule eventu Google Calendar (np. 'Maja' zamiast 'Marina Maja Sailing 35'). Jeśli puste — używana jest pełna nazwa jachtu."
|
||||
5. `class-admin.php::process_yacht_save()` — zapisz `yacht_gcal_alias` przez `Yacht::update_gcal_alias()`.
|
||||
6. Dodaj admin notices dla token_regenerated i import_done.
|
||||
|
||||
Avoid:
|
||||
- Nie usuwaj istniejących pól per-yacht ("Google Calendar ID" i "iCal Import URL") — out of scope. Klient zdecyduje w kolejnym planie czy je sprzątać.
|
||||
- Nie dotykaj OAuth UI (sekcja Google Calendar OAuth zostaje bez zmian).
|
||||
</action>
|
||||
<verify>
|
||||
- `php -l` na każdym zmienionym pliku
|
||||
- W panelu admin → Settings: nowa sekcja jest widoczna, zapis URL działa, "Wygeneruj token" zmienia URL, "Importuj teraz" wywołuje import (sprawdzalne w error log lub bookingach)
|
||||
- W yacht-edit: pole alias zapisuje się i odczytuje
|
||||
- Token w URL ma >=20 znaków, zmiana tokenu unieważnia stary URL (403)
|
||||
</verify>
|
||||
<done>AC-6 zaspokojone, AC-4 (UI dla aliasu) zaspokojone</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
Dwukierunkowa synchronizacja iCal z jednym wspólnym Google Calendar:
|
||||
- Globalny export feed (admin subskrybuje w GCal)
|
||||
- Globalny import URL (plugin pobiera kalendarz Google)
|
||||
- Identyfikacja jachtu po prefiksie "Nazwa - opis" w tytule eventu
|
||||
- Frontend per-jacht pokazuje tylko swoje eventy
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Settings → skopiuj Export URL.
|
||||
2. W Google Calendar → "Inne kalendarze" → "Z URL-a" → wklej Export URL → potwierdź że pojawiają się rezerwacje wszystkich jachtów z prefiksem nazwy.
|
||||
3. W Google Calendar (głównym admina) utwórz event ręczny: tytuł "Maja - Test ręcznie", data za tydzień.
|
||||
4. Skopiuj URL iCal swojego głównego kalendarza Google (Ustawienia kalendarza → Tajny adres w formacie iCal).
|
||||
5. Wklej do Settings pluginu → "iCal Import URL".
|
||||
6. Kliknij "Importuj teraz".
|
||||
7. Sprawdź: WP Admin → Rezerwacje → pojawia się booking "GCal: Maja - Test ręcznie" przypisany do jachtu "Maja".
|
||||
8. Otwórz frontend kalendarz jachtu Maja — data zablokowana. Otwórz kalendarz innego jachtu — data wolna.
|
||||
9. Usuń event w Google → uruchom "Importuj teraz" ponownie → booking znika.
|
||||
10. Stwórz w GCal "Nieznany - test" → import → brak bookingu (sprawdź WP error log: "skip: yacht not found").
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "approved" gdy wszystkie 10 punktów weryfikacji przejdzie, lub opisz problemy do poprawy</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- `class-availability.php` (logika availability stabilna)
|
||||
- `class-booking.php` (CPT booking — nie ruszamy)
|
||||
- `wp_yacht_availability` schema (zablokowane)
|
||||
- `frontend/class-calendar-widget.php` i `frontend/class-shortcode.php` (widget renderingu — działa już per-yacht)
|
||||
- `class-rest-controller.php` (REST endpointy — bez zmian)
|
||||
- OAuth integration (`class-oauth-handler.php`, `class-gcal-service.php`, `class-sync-controller.php`) — out of scope tego planu, push przez OAuth zostaje na razie jak jest (drugi mechanizm; klient użyje iCal a OAuth zignoruje, lub w kolejnym planie wyłączymy)
|
||||
- Per-yacht iCal feed (`output_ics`) i per-yacht import (`run_import` + `import_for_yacht`) — pozostają funkcjonalne dla kompatybilności wstecznej
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Plan NIE usuwa pól per-yacht ("Google Calendar ID", "iCal Import URL") z yacht-edit — to decyzja na osobny plan po pozytywnej weryfikacji nowej globalnej synchronizacji
|
||||
- Plan NIE wyłącza OAuth push do GCal — drugi (równoległy) mechanizm; jeśli klient nie połączy OAuth, nie jest aktywny
|
||||
- Brak nowych zależności (PHP libs, npm, CDN)
|
||||
- Brak zmian w bazie poza dodaniem `wp_options` keys i opcjonalnym yacht meta `_yacht_gcal_alias`
|
||||
- Brak tłumaczeń .pot w tym planie (Phase 9 osobno)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Przed deklaracją ukończenia planu:
|
||||
- [ ] `php -l` na wszystkich zmodyfikowanych plikach przechodzi
|
||||
- [ ] Globalny export URL zwraca poprawny .ics z prefiksami nazw jachtów
|
||||
- [ ] Globalny import URL parsuje eventy z GCal i przypisuje do jachtów po prefiksie
|
||||
- [ ] Eventy bez separatora lub bez dopasowanego jachtu są ignorowane (logowane)
|
||||
- [ ] Idempotencja: wielokrotne uruchomienie importu nie tworzy duplikatów
|
||||
- [ ] Stale cleanup: usunięcie eventu w GCal + import → booking znika
|
||||
- [ ] Frontend kalendarz per-jacht pokazuje tylko swoje rezerwacje (regresja)
|
||||
- [ ] Per-yacht feedy iCal nadal działają (nie zostały zepsute)
|
||||
- [ ] Settings UI: zapis URL, regeneracja tokenu, manual import, alias jachtu działają
|
||||
- [ ] Wszystkie acceptance criteria spełnione
|
||||
- [ ] Checkpoint human-verify zaakceptowany przez klienta
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Klient może zarządzać rezerwacjami całej floty w jednym Google Calendar
|
||||
- Eventy ręczne w GCal z prefiksem "Nazwa jachtu - ..." automatycznie pojawiają się jako blokady na frontendzie tego jachtu
|
||||
- Rezerwacje ze strony pojawiają się w GCal admina (po refresh subskrypcji Google)
|
||||
- Każdy frontend kalendarz pokazuje tylko swoje rezerwacje
|
||||
- Zachowana kompatybilność wsteczna (per-yacht feedy + OAuth push nadal działają)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po ukończeniu utwórz `.paul/phases/09-finalizacja/09-02-SUMMARY.md` zawierający:
|
||||
- Co zbudowano (export feed, import, settings UI, alias)
|
||||
- Decyzje (separator " - ", lookup case-insensitive po post_title/alias, ignore unmatched)
|
||||
- Otwarte pytania na kolejny plan (czy usuwać pola per-yacht? czy wyłączać OAuth push?)
|
||||
- Ścieżkę testową odtworzeniową
|
||||
</output>
|
||||
170
.paul/phases/09-finalizacja/09-02-SUMMARY.md
Normal file
170
.paul/phases/09-finalizacja/09-02-SUMMARY.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 02
|
||||
subsystem: integrations
|
||||
tags: [ical, google-calendar, sync, multi-yacht, prefix-matching]
|
||||
|
||||
requires:
|
||||
- phase: 04-frontend-kalendarz
|
||||
provides: widget kalendarza per-yacht (już filtruje po _booking_yacht_id)
|
||||
- phase: 08-gcal-synchronizacja
|
||||
provides: per-yacht ICal_Feed/ICal_Import + OAuth GCal_Service (zachowane jako fallback)
|
||||
|
||||
provides:
|
||||
- Globalny iCal Export feed (jeden URL z tokenem dla wszystkich jachtów)
|
||||
- Globalny iCal Import URL (jeden wspólny kalendarz Google → wiele jachtów)
|
||||
- Mechanizm dopasowania jachtu po prefiksie SUMMARY (separator " - ")
|
||||
- Pole _yacht_gcal_alias (krótki alias dla skróconej nazwy w GCal)
|
||||
- Anti-loop (eventy zaimportowane z GCal nie są re-eksportowane)
|
||||
- Settings UI: nowa sekcja "Globalna synchronizacja iCal"
|
||||
|
||||
affects:
|
||||
- przyszłe plany sprzątające pola per-yacht (Google Calendar ID, iCal Import URL w yacht-edit)
|
||||
- ewentualne wycofanie OAuth push (drugi mechanizm, klient go nie używa)
|
||||
|
||||
tech-stack:
|
||||
added:
|
||||
- mb_strtolower/mb_substr/mb_strpos do parsowania prefiksu (poprawne dla polskich znaków)
|
||||
patterns:
|
||||
- "Lookup map yachts: lowercase(alias|post_title) => yacht_id"
|
||||
- "Anti-loop via _booking_source flag (ical_import_global)"
|
||||
- "Token-based public feed authorization (hash_equals)"
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
|
||||
|
||||
key-decisions:
|
||||
- "Separator prefiksu: ' - ' (spacja-myślnik-spacja) — zgodne z istniejącym formatem per-yacht ICal_Feed::output_ics"
|
||||
- "Lookup case-insensitive po post_title lub _yacht_gcal_alias (priorytet alias)"
|
||||
- "Eventy bez separatora lub bez dopasowania — IGNOROWANE (nie blokują)"
|
||||
- "iCal jako mechanizm zamiast OAuth — klient nie chce konfigurować Google Cloud Console"
|
||||
- "Per-yacht feedy + OAuth zostają jako kompatybilność wsteczna (out of scope ich sprzątania)"
|
||||
|
||||
patterns-established:
|
||||
- "Globalny feed: home_url('/yacht-ical-global/{token}.ics') — token w wp_options"
|
||||
- "Booking source 'ical_import_global' identyfikuje rezerwacje z globalnego importu (vs 'ical_import' per-yacht)"
|
||||
- "exact match po lowercase prefiksu — ŻADNYCH partial/substring matchings (uniknięcie kolizji 'Maja' vs 'Maja Słoneczna')"
|
||||
|
||||
duration: ~25min
|
||||
started: 2026-05-06T11:00:00Z
|
||||
completed: 2026-05-06T11:25:00Z
|
||||
---
|
||||
|
||||
# Phase 9 Plan 02: Globalna synchronizacja iCal Summary
|
||||
|
||||
**Dwukierunkowa synchronizacja całej floty z jednym wspólnym Google Calendar przez iCal feedy, z automatycznym przypisaniem eventów do jachtów po prefiksie nazwy w tytule (separator " - ").**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~25min |
|
||||
| Tasks | 3 auto + 1 human-verify (wszystkie zaakceptowane) |
|
||||
| Files modified | 6 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Globalny iCal Export feed | Pass | `output_global_ics()` zwraca .ics ze wszystkimi rezerwacjami, każda z prefiksem `{yacht_title} - {customer_name}`, anti-loop pomija source `ical_import_global` |
|
||||
| AC-2: Globalny Import — dopasowanie po prefiksie | Pass | `run_global_import()` + `match_yacht_by_prefix()` parsuje SUMMARY przed pierwszym ` - `, dopasowuje case-insensitive (mb_*), eventy bez dopasowania logowane i pomijane |
|
||||
| AC-3: Globalny Import — czyszczenie usuniętych | Pass | `existing_global_map` vs `seen_uids` — zniknięte UID-y → `Availability::clear_booking_availability()` + `wp_delete_post` |
|
||||
| AC-4: Alias jachtu | Pass | `Yacht::get_gcal_alias()` + `update_gcal_alias()`, build_yacht_lookup_map() używa aliasu z priorytetem |
|
||||
| AC-5: Frontend filtrowanie (regresja) | Pass | Bez zmian w widget — działa przez `_booking_yacht_id` jak dotąd; klient zweryfikował na produkcji |
|
||||
| AC-6: Settings UI | Pass | Nowa sekcja "Globalna synchronizacja iCal" (krok 1: export URL + kopiuj + regeneracja tokenu, krok 2: import URL + zapis, krok 3: importuj teraz, instrukcja) + alias w yacht-edit |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Klient może subskrybować jeden URL pluginu w Google Calendar i widzieć rezerwacje WSZYSTKICH jachtów w jednym widoku floty
|
||||
- Klient może tworzyć ręczne eventy w GCal z tytułem `"Nazwa - opis"` — plugin automatycznie przypisuje je do właściwego jachtu i blokuje availability tylko dla tego jachtu
|
||||
- Frontend kalendarz per-jacht pokazuje tylko swoje rezerwacje (bez zmian w widgecie — działa dzięki poprawnemu przypisaniu `_booking_yacht_id` przy imporcie)
|
||||
- Anti-loop zapobiega duplikacji (eventy zaimportowane z GCal nie są wysyłane z powrotem)
|
||||
- Stale cleanup automatycznie usuwa rezerwacje gdy event zostanie skasowany w GCal
|
||||
- Per-yacht feedy + OAuth push zostały zachowane (kompatybilność wsteczna)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `integrations/ical/class-ical-feed.php` | Modified | +rewrite rule `^yacht-ical-global/`, +query var `yacht_ical_global`, +`get_global_feed_token()`, +`regenerate_global_token()`, +`get_global_feed_url()`, +`output_global_ics()` (anti-loop, prefiks per yacht) |
|
||||
| `integrations/ical/class-ical-import.php` | Modified | +`GLOBAL_IMPORT_SOURCE`, +`SUMMARY_SEPARATOR`, +`run_global_import()`, +`build_yacht_lookup_map()` (alias priority), +`match_yacht_by_prefix()` (mb_*), +`get_existing_global_import_map()`, +`upsert_global_booking()`, cron `yacht_booking_ical_global_import` (hourly), zmiana visibility `parse_ics` na protected |
|
||||
| `includes/class-yacht.php` | Modified | +`get_gcal_alias()`, +`update_gcal_alias()` (meta `_yacht_gcal_alias`) |
|
||||
| `includes/class-installer.php` | Modified | +domyślne opcje: `yacht_booking_global_ical_import_url`, `yacht_booking_global_ical_token`, `yacht_booking_global_ical_last_import` |
|
||||
| `admin/class-admin.php` | Modified | +obsługa 3 form actions (save/regenerate token/run import), +4 admin notices, +sekcja UI "Globalna synchronizacja iCal" w `render_google_calendar_settings()`, +zapis aliasu w `save_yacht()` |
|
||||
| `admin/views/yacht-edit.php` | Modified | +pole `yacht_gcal_alias` z opisem |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Separator " - " (spacja-myślnik-spacja) | Zgodność z istniejącym formatem `output_ics` (per-yacht feed już używał tego stylu) | Klient nie musi uczyć się nowej konwencji; istniejące eksporty działają tak samo |
|
||||
| Lookup po lowercase pełnej nazwy/aliasu (exact, nie substring) | Uniknięcie kolizji typu "Maja" trafia w "Maja" i "Maja Słoneczna" jednocześnie | Klient z długimi nazwami flot musi ustawić aliasy, ale ma pewność rozróżnienia |
|
||||
| Eventy bez dopasowania — ignorowane (nie blokują) | Klient nie chce niespodzianek (przypadkowa blokada całej floty przez literówkę) | Dyscyplina nazewnictwa w GCal, ale przewidywalność |
|
||||
| Anti-loop przez `_booking_source` flag | Najprostsze, niezawodne | Bez ryzyka eskalacji duplikatów między iteracjami |
|
||||
| Per-yacht feedy zostają | Kompatybilność wsteczna (potencjalni subskrybenci OTA) | Out of scope ich usunięcia — decyzja na osobny plan po stabilizacji |
|
||||
| Cron co godzinę dla globalnego importu | Spójne z per-yacht cron | Klient widzi import w panelu lub może wymusić "Importuj teraz" |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 0 | — |
|
||||
| Scope additions | 0 | — |
|
||||
| Deferred | 0 | — |
|
||||
|
||||
**Total impact:** Plan wykonany dokładnie zgodnie ze specyfikacją. Brak odchyleń.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
Brak.
|
||||
|
||||
### Deferred Items
|
||||
|
||||
Brak.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
Brak — żadnych problemów podczas implementacji ani weryfikacji manualnej.
|
||||
|
||||
## Otwarte pytania (do następnego planu)
|
||||
|
||||
Klient w trakcie planowania prosił "zapytaj po zrobieniu importu i eksportu" — te pytania zostają otwarte:
|
||||
|
||||
1. **Pola per-yacht "Google Calendar ID" i "iCal Import URL" w yacht-edit** — czy je usunąć/ukryć teraz, gdy klient będzie używał globalnej synchronizacji? Jeśli tak: usunąć z UI ale zachować dane w meta na wypadek migracji.
|
||||
|
||||
2. **OAuth push do GCal (sync_to_gcal cron + on_booking_created hook)** — drugi mechanizm jest aktywny tylko gdy admin podłączył OAuth. Klient nie planuje tego używać. Czy wyłączyć całkowicie (żeby uniknąć ewentualnych duplikatów gdy ktoś przez pomyłkę połączy OAuth) czy zostawić jako opcję?
|
||||
|
||||
## Reproduction Path (do testów regresyjnych)
|
||||
|
||||
1. Settings → Google Calendar → sekcja "Globalna synchronizacja iCal" → skopiuj Export URL → wklej w GCal "Z URL-a"
|
||||
2. Utwórz w GCal event `"NazwaJachtu - Test"` (data za tydzień, całodniowy)
|
||||
3. Skopiuj iCal URL kalendarza Google → wklej w pole "iCal Import URL" → Zapisz → "Importuj teraz"
|
||||
4. WP Admin → Rezerwacje → pojawia się booking `"GCal: NazwaJachtu - Test"` (source `ical_import_global`)
|
||||
5. Frontend kalendarz tego jachtu → data zablokowana; inny jacht → data wolna
|
||||
6. Usuń event w GCal → "Importuj teraz" → booking zniknął, daty zwolnione
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Globalna synchronizacja iCal w pełni funkcjonalna i zweryfikowana przez klienta
|
||||
- Architektura per-yacht zachowana (no breaking changes)
|
||||
- 2 z 4 plans w fazie 9 ukończone
|
||||
|
||||
**Concerns:**
|
||||
- Ewentualna kolizja gdy admin przypadkowo użyje OAuth obok iCal — drugi mechanizm tworzyłby duplikaty. Mitigacja: GCal_Service::create_event ustawia `_gcal_event_id` którego nie zerujemy, ale plugin już teraz ma anti-loop tylko na imporcie globalnym, nie na OAuth pull. Klient nie używa OAuth → praktyczny ryzyko zerowe.
|
||||
- Klient powinien wykonać deploy FTP + jednorazowy "Save" na stronie Permalinks żeby flush rewrite rules zarejestrowało nową regułę `^yacht-ical-global/` (dokumentowane w instrukcji checkpointu).
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 09-finalizacja, Plan: 02*
|
||||
*Completed: 2026-05-06*
|
||||
440
.paul/phases/09-finalizacja/09-03-PLAN.md
Normal file
440
.paul/phases/09-finalizacja/09-03-PLAN.md
Normal file
@@ -0,0 +1,440 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["09-02"]
|
||||
files_modified:
|
||||
- wp-content/plugins/yacht-booking-system/yacht-booking-system.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-yacht-list-table.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
- wp-content/plugins/yacht-booking-system/uninstall.php
|
||||
files_deleted:
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-oauth-handler.php
|
||||
autonomous: false
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Posprzątać po globalnej iCal sync: usunąć martwe i zbędne mechanizmy (OAuth push do GCal, per-jacht iCal import URL, per-jacht feed, pole "Google Calendar ID"). Pozostawić tylko jeden mechanizm — globalny iCal feed (export + import) wprowadzony w 09-02.
|
||||
|
||||
## Purpose
|
||||
Po 09-02 plugin ma DWA równoległe mechanizmy synchronizacji z GCal: globalny iCal (używany przez klienta) i OAuth push (uśpiony, klient nie używa) + per-yacht iCal feedy/importy (relikt sprzed globalnego). To komplikuje model mentalny i potencjalnie powoduje duplikaty. Sprzątanie eliminuje confusing UI i tysiące linii martwego kodu, czyniąc plugin gotowym produkcyjnie do v1.0.
|
||||
|
||||
## Output
|
||||
- Usunięte 3 pliki OAuth/GCal (`class-sync-controller.php`, `class-gcal-service.php`, `class-oauth-handler.php`)
|
||||
- Wyłączony cały OAuth flow (UI w settings, hooki cron, callback redirect, AJAX manual sync)
|
||||
- Usunięty per-jacht iCal feed (`output_ics`, `get_feed_token`, rewrite rule `^yacht-ical/(\d+)/...`)
|
||||
- Usunięty per-jacht iCal import (cron `yacht_booking_ical_import`, `run_import`, `import_for_yacht`, pole `iCal Import URL` w yacht-edit, kolumna w yacht-list)
|
||||
- Usunięte pole "Google Calendar ID" w yacht-edit + metody `Yacht::get_gcal_id`/`update_gcal_id`
|
||||
- Cleanup migration: jednorazowy `delete_post_meta` (_yacht_gcal_id, _yacht_ical_import_url, _yacht_ical_token, _yacht_ical_last_import) + `delete_option` (gcal_*, gcal credentials, gcal_sync_enabled)
|
||||
- Zaktualizowany `uninstall.php`
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
@.paul/STATE.md
|
||||
@.paul/codebase/architecture.md
|
||||
@.paul/phases/09-finalizacja/09-02-SUMMARY.md
|
||||
|
||||
## Source Files
|
||||
@wp-content/plugins/yacht-booking-system/yacht-booking-system.php
|
||||
@wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
|
||||
@wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php
|
||||
@wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-oauth-handler.php
|
||||
|
||||
<clarifications>
|
||||
- **GCal ID per-jacht** — Usunąć z UI + skasować meta z bazy.
|
||||
→ Odpowiedź: pole `_yacht_gcal_id` było martwe (OAuth używał globalnej opcji). Usuwamy pole z yacht-edit, metody `Yacht::get_gcal_id`/`update_gcal_id`, kolumnę z yacht-list-table, oraz wywołujemy `delete_post_meta` w cleanup migration.
|
||||
|
||||
- **Per-jacht iCal Import URL** — Usunąć całkowicie (UI + cron + dane).
|
||||
→ Odpowiedź: usuwamy pole `iCal Import URL` z yacht-edit, kolumnę z yacht-list, hook `yacht_booking_ical_import`, metody `run_import`, `import_for_yacht`, `get_import_url`, `set_import_url`, `get_last_import_time`, `get_existing_import_map`, `upsert_booking` (per-yacht), stałą `IMPORT_SOURCE`. Per-yacht booking source `ical_import` zostaje rozpoznany w cleanup migration jako kandydat do usunięcia (rozważymy w boundaries — patrz niżej).
|
||||
|
||||
- **OAuth push do GCal** — Wyłączyć całkowicie i usunąć.
|
||||
→ Odpowiedź: usuwamy 3 pliki (`class-sync-controller.php`, `class-gcal-service.php`, `class-oauth-handler.php`), wszystkie hooki w bootstrap, sekcję UI OAuth w `render_google_calendar_settings`, AJAX `wp_ajax_yacht_booking_manual_sync`, ścieżkę OAuth callback w `process_settings_save`, notice connected/disconnected. Sekcja Settings → Google Calendar zawiera już teraz TYLKO globalną iCal sync.
|
||||
|
||||
- **Migracja** — Bez widocznej migracji, po cichu.
|
||||
→ Odpowiedź: cleanup wykonuje się w `Installer::install()` przy aktywacji nowej wersji (sprawdzamy zapisaną wersję pluginu vs `YACHT_BOOKING_VERSION` — jeśli upgrade z wersji <X.Y.Z, uruchamiamy cleanup). Brak admin notice. Brak żądań akcji od klienta. Klient po prostu zauważy że niepotrzebne pola znikły.
|
||||
|
||||
- **Per-jacht booking source 'ical_import'** — istniejące rezerwacje zaimportowane przez stary per-yacht mechanizm.
|
||||
→ Decyzja techniczna (nie pytana): w cleanup migration usuwamy je razem z meta (clear_booking_availability + wp_delete_post). Bo bez per-yacht importu nie będą odświeżane, więc lepiej żeby nie blokowały dat na stałe. Klient po cleanup uruchomi globalny "Importuj teraz" i nowe dane się zaimportują.
|
||||
</clarifications>
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Pliki OAuth/Sync usunięte i nie ładowane
|
||||
```gherkin
|
||||
Given plugin po nowym deploy
|
||||
When sprawdzamy strukturę katalogu integrations/
|
||||
Then nie istnieje plik class-sync-controller.php
|
||||
And nie istnieje plik class-gcal-service.php
|
||||
And nie istnieje plik class-oauth-handler.php
|
||||
And bootstrap pluginu (yacht-booking-system.php + class-yacht-booking.php) nie odwołuje się do żadnej z tych klas
|
||||
And `php -l` na bootstrap nie zgłasza błędów
|
||||
And aktywacja pluginu w WP Admin nie powoduje fatal error
|
||||
```
|
||||
|
||||
## AC-2: Sekcja OAuth zniknęła z Settings, globalna iCal pozostała
|
||||
```gherkin
|
||||
Given admin otwiera WP Admin → Rezerwacje Jachtów → Ustawienia → zakładka Google Calendar
|
||||
When strona się renderuje
|
||||
Then NIE widzi "Krok 1: Dodaj dane OAuth"
|
||||
And NIE widzi "Krok 2: Autoryzuj połączenie"
|
||||
And NIE widzi przycisku "Synchronizuj teraz" (AJAX manual sync)
|
||||
And NIE widzi pola "Client ID"/"Client Secret"
|
||||
And widzi sekcję "Globalna synchronizacja iCal" (z 09-02) z polami import URL + export URL
|
||||
And działa "Importuj teraz" + zapis URL + regeneracja tokenu
|
||||
```
|
||||
|
||||
## AC-3: Pola per-jacht zniknęły z yacht-edit i yacht-list
|
||||
```gherkin
|
||||
Given admin otwiera Edycję jachtu (yacht-bookings-add-yacht)
|
||||
When strona się renderuje
|
||||
Then NIE widzi pola "Google Calendar ID"
|
||||
And NIE widzi pola "iCal Import URL"
|
||||
And NIE widzi linku "iCal Feed URL" per-jacht
|
||||
And widzi nadal pole "Alias dla Google Calendar" (z 09-02 — to jest aktywne dla globalnego importu)
|
||||
And lista jachtów (yacht-bookings) NIE pokazuje kolumny GCal ID ani iCal status
|
||||
```
|
||||
|
||||
## AC-4: Per-jacht iCal cron i feed wyłączone
|
||||
```gherkin
|
||||
Given plugin po deploy nowej wersji
|
||||
When sprawdzamy zarejestrowane crony WP
|
||||
Then NIE jest zaplanowany hook 'yacht_booking_ical_import' (per-yacht)
|
||||
And NIE jest zaplanowany hook 'yacht_booking_pull_from_gcal' (OAuth pull)
|
||||
And NIE jest zaplanowany hook 'yacht_booking_sync_to_gcal' (OAuth push)
|
||||
And JEST zaplanowany hook 'yacht_booking_ical_global_import' (z 09-02)
|
||||
And URL `/yacht-ical/{yacht_id}/{token}.ics` zwraca 404 (per-yacht feed niedostępny)
|
||||
And URL `/yacht-ical-global/{token}.ics` nadal działa (z 09-02)
|
||||
```
|
||||
|
||||
## AC-5: Cleanup migration — czysta baza
|
||||
```gherkin
|
||||
Given baza ma stare meta (_yacht_gcal_id, _yacht_ical_import_url, _yacht_ical_token, _yacht_ical_last_import)
|
||||
And baza ma stare opcje (yacht_booking_gcal_calendar_id, yacht_booking_gcal_credentials, yacht_booking_gcal_oauth_*, yacht_booking_gcal_sync_enabled)
|
||||
And baza ma rezerwacje z _booking_source = 'ical_import' (per-yacht)
|
||||
When admin aktywuje nową wersję pluginu (lub uruchomi się hook upgrade)
|
||||
Then wszystkie wymienione meta są usunięte (delete_post_meta)
|
||||
And wszystkie wymienione opcje są usunięte (delete_option)
|
||||
And rezerwacje z source 'ical_import' są usunięte (Availability::clear_booking_availability + wp_delete_post)
|
||||
And opcja `yacht_booking_version` ma nową wartość pluginu
|
||||
And cleanup wykonuje się TYLKO RAZ (kolejna aktywacja nie powtarza)
|
||||
```
|
||||
|
||||
## AC-6: Globalna iCal sync (z 09-02) działa bez regresji
|
||||
```gherkin
|
||||
Given po cleanup admin ma globalny iCal Import URL ustawiony
|
||||
When kliknie "Importuj teraz"
|
||||
Then plugin pobiera kalendarz Google
|
||||
And eventy z prefiksem nazwy jachtu są importowane jako rezerwacje (source 'ical_import_global')
|
||||
And frontend kalendarz per-jacht pokazuje tylko swoje rezerwacje
|
||||
And export feed `/yacht-ical-global/{token}.ics` zwraca poprawny .ics
|
||||
```
|
||||
|
||||
## AC-7: uninstall.php zaktualizowany
|
||||
```gherkin
|
||||
Given admin usuwa plugin przez Plugins → Delete
|
||||
When uruchamia się uninstall.php
|
||||
Then NIE próbuje delete_option dla nieistniejących już kluczy (lub robi to bezpiecznie — delete_option jest no-op dla nieistniejących)
|
||||
And usuwa wszystkie aktualne klucze: yacht_booking_global_ical_*, yacht_booking_default_status, yacht_booking_email_*, etc.
|
||||
And usuwa wszystkie meta jachtów + bookingów + tabele wp_yacht_availability (jak dotąd)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Usunąć OAuth/Sync/GCal_Service — pliki i wszystkie odniesienia</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php (DELETE),
|
||||
wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php (DELETE),
|
||||
wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-oauth-handler.php (DELETE),
|
||||
wp-content/plugins/yacht-booking-system/yacht-booking-system.php,
|
||||
wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
</files>
|
||||
<action>
|
||||
1. **Usunąć 3 pliki** w `integrations/google-calendar/`:
|
||||
- `class-sync-controller.php`
|
||||
- `class-gcal-service.php`
|
||||
- `class-oauth-handler.php`
|
||||
Można usunąć cały katalog `integrations/google-calendar/` jeśli to jedyne pliki (sprawdź `ls integrations/google-calendar/` przed delete).
|
||||
|
||||
2. **`yacht-booking-system.php` (bootstrap)**:
|
||||
- Usunąć `require_once` dla 3 plików GCal (jeśli są)
|
||||
- Usunąć wywołania `Sync_Controller::get_instance()`, `Sync_Controller::register_cron_actions()`, `Sync_Controller::setup_cron()`, `Sync_Controller::clear_cron()` w hookach activate/deactivate/plugins_loaded
|
||||
- Zachować bez zmian wszystkie wywołania ICal_* (te zostają)
|
||||
|
||||
3. **`includes/class-yacht-booking.php`**:
|
||||
- W metodzie `init()` lub `register()` usunąć linie tworzące/inicjalizujące `Sync_Controller`, `GCal_Service`, `OAuth_Handler`
|
||||
- Usunąć `require_once` dla 3 plików GCal
|
||||
|
||||
4. **`admin/class-admin.php` — masywny refactor sekcji OAuth**:
|
||||
- W `process_settings_save()`:
|
||||
- Usunąć `require_once` dla `class-oauth-handler.php` i `class-gcal-service.php`
|
||||
- Usunąć blok obsługujący `gcal_callback` (OAuth callback redirect)
|
||||
- Usunąć blok `yacht_booking_disconnect_gcal`
|
||||
- Usunąć blok `yacht_booking_save_gcal_credentials`
|
||||
- **ZACHOWAĆ** bloki: globalny iCal save, regenerate token, run global import, save_settings (general), save_email_templates
|
||||
- Usunąć metodę `handle_oauth_callback()`
|
||||
- Usunąć metodę `save_gcal_credentials()`
|
||||
- W `display_admin_notices()`: usunąć notice `connected`, `disconnected`. Pozostawić: saved, templates_saved, templates_reset, error, global_ical_saved, token_regenerated, global_import_done, global_import_failed
|
||||
- W `render_google_calendar_settings()`: usunąć CAŁĄ sekcję `if ( ! $is_connected ) ... else ... endif` (Step 1: credentials, Step 2: authorize, Connected status, Manual sync, Instrukcja). **ZACHOWAĆ** sekcję "Globalna synchronizacja iCal" (z 09-02). Metoda po refactorze powinna mieć tylko: nagłówek `<h2>` + tę jedną sekcję `<div class="card">` z 09-02.
|
||||
- Usunąć lokalne require_once `OAuth_Handler` i `GCal_Service` w tej metodzie (już niepotrzebne).
|
||||
- Pozostawić bez zmian `process_yacht_save`, `save_yacht`, list table'y, eksport CSV, etc.
|
||||
|
||||
Avoid:
|
||||
- Nie ruszać sekcji "Globalna synchronizacja iCal" (z 09-02) — to jest TO co zostaje
|
||||
- Nie usuwać CSS/JS dla globalnego iCal
|
||||
- Nie usuwać AJAX handler globalnego iCal (jeśli istnieje — w 09-02 jest tylko form POST, żaden AJAX)
|
||||
</action>
|
||||
<verify>
|
||||
- `ls integrations/google-calendar/` zwraca pustkę lub katalog nie istnieje
|
||||
- `grep -r "OAuth_Handler\|GCal_Service\|Sync_Controller\|GoogleCalendar" wp-content/plugins/yacht-booking-system --include="*.php"` zwraca 0 wyników (poza komentarzami w summary/changelog)
|
||||
- `php -l` przechodzi na: yacht-booking-system.php, class-yacht-booking.php, class-admin.php
|
||||
- W WP Admin → Settings → Google Calendar widać TYLKO sekcję "Globalna synchronizacja iCal"
|
||||
- Brak fatal error przy aktywacji (sprawdź `?WP_DEBUG=true` lub error log)
|
||||
</verify>
|
||||
<done>AC-1 i część AC-2 satisfied (sekcja OAuth zniknęła)</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Usunąć per-jacht iCal feed/import + pole gcal_id z UI</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php,
|
||||
wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/class-yacht-list-table.php,
|
||||
wp-content/plugins/yacht-booking-system/admin/class-admin.php,
|
||||
wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
</files>
|
||||
<action>
|
||||
1. **`class-ical-import.php` — usuwamy mechanizm per-yacht, zostaje tylko globalny**:
|
||||
- Usunąć stałą `IMPORT_SOURCE` (zachować `GLOBAL_IMPORT_SOURCE` i `SUMMARY_SEPARATOR`)
|
||||
- W `register()`: usunąć `add_action( 'yacht_booking_ical_import', ... )` (zostaje tylko `yacht_booking_ical_global_import`)
|
||||
- W `setup_cron()`: usunąć `wp_schedule_event` dla `yacht_booking_ical_import`
|
||||
- W `clear_cron()`: usunąć `wp_clear_scheduled_hook( 'yacht_booking_ical_import' )`
|
||||
- Usunąć metody publiczne: `run_import()`, `get_import_url()`, `set_import_url()`, `get_last_import_time()`
|
||||
- Usunąć metody prywatne: `import_for_yacht()`, `get_existing_import_map()`, `upsert_booking()` (per-yacht — zachować `upsert_global_booking`!)
|
||||
- Zachować: `parse_ics`, helpers (`line_starts_with`, `extract_value`, `parse_ical_date`, `unescape_ical`), `log`, oraz cały globalny mechanizm (run_global_import, build_yacht_lookup_map, match_yacht_by_prefix, get_existing_global_import_map, upsert_global_booking)
|
||||
|
||||
2. **`class-ical-feed.php` — usuwamy per-yacht feed, zostaje tylko globalny**:
|
||||
- W `add_rewrite_rules()`: usunąć rule `^yacht-ical/([0-9]+)/([a-zA-Z0-9]+)\.ics$` (zostaje tylko global). Zaktualizować flush check.
|
||||
- W `add_query_vars()`: usunąć `yacht_ical_id` (zostają `yacht_ical_token` i `yacht_ical_global`)
|
||||
- W `handle_feed_request()`: usunąć cały blok per-yacht (zachować tylko branch global)
|
||||
- Usunąć metody: `get_feed_token()`, `regenerate_token()`, `get_feed_url()`, `output_ics()`
|
||||
- Zachować: `register()`, `add_rewrite_rules()` (już poprawiony), `add_query_vars()` (poprawiony), `handle_feed_request()` (poprawiony), wszystkie metody global, `escape_ical()`
|
||||
|
||||
3. **`admin/views/yacht-edit.php`**:
|
||||
- Usunąć całe wiersze tabeli z polami: `yacht_gcal_id`, `yacht_ical_import_url`, oraz blok wyświetlający per-yacht iCal Feed URL (z tokenem)
|
||||
- Usunąć z PHP na górze pliku: `$gcal_id = ...`, `$ical_import_url = ...`, `$ical_feed_url = ...`, `$ical_last_import = ...`
|
||||
- **ZACHOWAĆ** pole `yacht_gcal_alias` (z 09-02 — używane przez globalny import)
|
||||
- Pozostawić bez zmian: title, description, capacity, price_per_day, features
|
||||
|
||||
4. **`admin/class-yacht-list-table.php`**:
|
||||
- Usunąć z `get_columns()` kolumnę dla GCal i iCal status (jeśli istnieją)
|
||||
- Usunąć metodę `column_gcal_id()` lub equivalent (linia ok. 173 — `Yacht::get_gcal_id`)
|
||||
- Sprawdź czy są inne kolumny używające usuniętych meta — usuń ich rendering
|
||||
|
||||
5. **`admin/class-admin.php` — czyszczenie save_yacht**:
|
||||
- W `save_yacht()`: usunąć linie:
|
||||
- `$gcal_id = ...`
|
||||
- `Yacht::update_gcal_id( $saved_id, $gcal_id );`
|
||||
- `$ical_import_url = ...`
|
||||
- `\YachtBooking\Integrations\ICal\ICal_Import::set_import_url( ... )`
|
||||
- **ZACHOWAĆ** zapis aliasu (`Yacht::update_gcal_alias`)
|
||||
|
||||
6. **`includes/class-yacht.php`**:
|
||||
- Usunąć metody: `get_gcal_id()`, `update_gcal_id()`
|
||||
- **ZACHOWAĆ**: get_capacity, update_capacity, get_price_per_day, update_price_per_day, get_features, update_features, get_gcal_alias, update_gcal_alias
|
||||
|
||||
Avoid:
|
||||
- Nie tknąć w żaden sposób mechanizmu globalnego iCal (run_global_import, output_global_ics, alias)
|
||||
- Nie usuwać cron `yacht_booking_ical_global_import` (z 09-02)
|
||||
- Nie usuwać `parse_ics` ani helperów iCal — używane przez globalny import
|
||||
</action>
|
||||
<verify>
|
||||
- `php -l` przechodzi na: class-ical-import.php, class-ical-feed.php, yacht-edit.php, class-yacht-list-table.php, class-admin.php, class-yacht.php
|
||||
- `grep -r "yacht_booking_ical_import\|run_import\|import_for_yacht\|get_gcal_id\|update_gcal_id\|_yacht_gcal_id\|_yacht_ical_import_url" wp-content/plugins/yacht-booking-system --include="*.php"` zwraca 0 wyników (lub tylko komentarze)
|
||||
- W WP Admin → Edycja jachtu: NIE widać pól "Google Calendar ID", "iCal Import URL", "iCal Feed URL". WIDAĆ pole "Alias dla Google Calendar".
|
||||
- URL `/yacht-ical/824/jakistoken.ics` zwraca 404
|
||||
- URL `/yacht-ical-global/{token}.ics` nadal działa
|
||||
</verify>
|
||||
<done>AC-3, AC-4, część AC-2 (UI per-yacht) satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Cleanup migration w Installer + uninstall.php</name>
|
||||
<files>
|
||||
wp-content/plugins/yacht-booking-system/includes/class-installer.php,
|
||||
wp-content/plugins/yacht-booking-system/uninstall.php
|
||||
</files>
|
||||
<action>
|
||||
1. **`class-installer.php` — dodać metodę `migrate()` wywoływaną z `install()`**:
|
||||
- Logika: pobierz `yacht_booking_version` z opcji. Jeśli istnieje i jest niższa niż `YACHT_BOOKING_VERSION` (czyli to upgrade) — uruchom cleanup.
|
||||
- Cleanup operacje:
|
||||
a. **Delete post meta** dla wszystkich jachtów:
|
||||
- `_yacht_gcal_id`
|
||||
- `_yacht_ical_import_url`
|
||||
- `_yacht_ical_token`
|
||||
- `_yacht_ical_last_import`
|
||||
Sposób: `delete_post_meta_by_key( '_yacht_gcal_id' )` (WordPress core function — usuwa meta dla wszystkich postów). Powtórzyć dla każdego klucza.
|
||||
b. **Delete options** (OAuth/credentials):
|
||||
- `yacht_booking_gcal_calendar_id`
|
||||
- `yacht_booking_gcal_credentials`
|
||||
- `yacht_booking_gcal_oauth_access_token`
|
||||
- `yacht_booking_gcal_oauth_refresh_token`
|
||||
- `yacht_booking_gcal_oauth_token_expires`
|
||||
- `yacht_booking_gcal_oauth_email`
|
||||
- `yacht_booking_gcal_sync_enabled`
|
||||
(sprawdź faktyczne nazwy w `class-oauth-handler.php` ZANIM zostanie usunięty w Task 1 — albo grep bashem PRZED. Lista powyżej to typowe; jeśli różne nazwy, skoryguj)
|
||||
c. **Delete bookings** z `_booking_source = 'ical_import'` (per-yacht):
|
||||
```php
|
||||
$bookings = get_posts( array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
array( 'key' => '_booking_source', 'value' => 'ical_import' ),
|
||||
),
|
||||
) );
|
||||
foreach ( $bookings as $booking_id ) {
|
||||
Availability::clear_booking_availability( $booking_id );
|
||||
wp_delete_post( $booking_id, true );
|
||||
}
|
||||
```
|
||||
d. **Clear stale crons**: `wp_clear_scheduled_hook( 'yacht_booking_ical_import' )`, `wp_clear_scheduled_hook( 'yacht_booking_pull_from_gcal' )`, `wp_clear_scheduled_hook( 'yacht_booking_sync_to_gcal' )`, `wp_clear_scheduled_hook( 'yacht_booking_update_in_gcal' )`, `wp_clear_scheduled_hook( 'yacht_booking_delete_from_gcal' )`.
|
||||
- Idempotencja: `set_version()` w `install()` ZAWSZE nadpisuje `yacht_booking_version` aktualną wartością → przy kolejnej aktywacji warunek `version < CURRENT` jest fałszem → migrate() nic nie robi. ✓
|
||||
- Wywołać `migrate()` w `install()` PRZED `set_version()`.
|
||||
|
||||
2. **`uninstall.php`**:
|
||||
- Sprawdź obecną zawartość (`Read`).
|
||||
- Usuń `delete_option` dla nieistniejących już kluczy GCal (te z punktu 1.b)
|
||||
- **ZACHOWAJ** delete_option dla:
|
||||
- `yacht_booking_default_status`, `yacht_booking_email_from_name`, `yacht_booking_email_from`, `yacht_booking_date_format`, `yacht_booking_currency_symbol`, `yacht_booking_terms_page_id`, `yacht_booking_enable_notifications`
|
||||
- `yacht_booking_global_ical_import_url`, `yacht_booking_global_ical_token`, `yacht_booking_global_ical_last_import`
|
||||
- `yacht_booking_email_templates`, `yacht_booking_version`, `yacht_booking_installed_at`, `yacht_booking_enabled`
|
||||
- **ZACHOWAJ** delete `wp_yacht_availability` table + delete_post_meta dla wszystkich CPT meta + wp_delete_post dla CPT.
|
||||
|
||||
Avoid:
|
||||
- Nie usuwać `_yacht_gcal_alias` (aktywny dla globalnego importu)
|
||||
- Nie usuwać `_booking_source = 'ical_import_global'` (aktywne globalne importy z 09-02 — zostawić!)
|
||||
- Nie usuwać innych user data (rezerwacji ze strony — `_booking_source != 'ical_import'`)
|
||||
- Cleanup ma być idempotentny — uruchamia się TYLKO przy upgrade z wersji < CURRENT
|
||||
</action>
|
||||
<verify>
|
||||
- `php -l` przechodzi na: class-installer.php, uninstall.php
|
||||
- Symulacja upgrade: ustaw `yacht_booking_version` na '0.9.0' w bazie testowej (lub starsza), aktywuj plugin → cleanup wykonuje się; wszystkie wymienione meta i opcje zniknęły; rezerwacje ze starym source 'ical_import' usunięte; rezerwacje globalne ('ical_import_global') i ze strony pozostały
|
||||
- Po cleanup: `wp_options` nie zawiera kluczy gcal_oauth_*, gcal_credentials, gcal_calendar_id, gcal_sync_enabled
|
||||
- Druga aktywacja po pierwszej → migrate() nic nie robi (cicha)
|
||||
</verify>
|
||||
<done>AC-5, AC-7 satisfied</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
Posprzątany plugin: jeden mechanizm sync (globalny iCal), zero martwych pól, czysta baza po upgrade.
|
||||
- Usunięte 3 pliki OAuth/Sync/GCal_Service
|
||||
- Usunięty per-jacht iCal feed (URL `/yacht-ical/{id}/...` zwraca 404)
|
||||
- Usunięty per-jacht iCal import (cron i pole)
|
||||
- Usunięte pole "Google Calendar ID" z yacht-edit
|
||||
- Cleanup migration: stare meta, opcje OAuth, rezerwacje per-yacht 'ical_import' → wyczyszczone
|
||||
- Settings → Google Calendar zawiera TYLKO sekcję "Globalna synchronizacja iCal"
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Po deploy FTP wejdź do WP Admin (jeśli plugin nieaktywny — aktywuj). To wyzwoli `Installer::install()` z migracją.
|
||||
2. Sprawdź WP Admin → Rezerwacje Jachtów → Ustawienia → Google Calendar:
|
||||
- **NIE ma** sekcji OAuth (Client ID, "Połącz z Google Calendar", "Synchronizuj teraz")
|
||||
- **JEST** sekcja "Globalna synchronizacja iCal" z 09-02 (export URL, import URL, "Importuj teraz")
|
||||
3. Wejdź w edycję dowolnego jachtu (np. yacht_id=824):
|
||||
- **NIE ma** pól "Google Calendar ID", "iCal Import URL", "iCal Feed URL"
|
||||
- **JEST** pole "Alias dla Google Calendar"
|
||||
4. Sprawdź listę jachtów (Rezerwacje Jachtów → Wszystkie Jachty):
|
||||
- **Nie ma** kolumn związanych z GCal/iCal per-jacht
|
||||
5. Otwórz w przeglądarce dowolny stary URL per-yacht: `https://jachty3.pagedev.pl/yacht-ical/824/jakikolwiek_token.ics` → **404 Not Found**
|
||||
6. Otwórz globalny URL z Settings (Krok 1) → **zwraca .ics z rezerwacjami**
|
||||
7. Settings → "Importuj teraz" → komunikat sukcesu, w error log nie ma fatal error
|
||||
8. Sprawdź bazę (phpMyAdmin lub WP-CLI):
|
||||
- `SELECT * FROM wp_postmeta WHERE meta_key IN ('_yacht_gcal_id','_yacht_ical_import_url','_yacht_ical_token','_yacht_ical_last_import')` → 0 wierszy
|
||||
- `SELECT * FROM wp_options WHERE option_name LIKE 'yacht_booking_gcal_%'` → 0 wierszy
|
||||
- `SELECT post_id FROM wp_postmeta WHERE meta_key='_booking_source' AND meta_value='ical_import'` → 0 wierszy
|
||||
- `SELECT option_value FROM wp_options WHERE option_name='yacht_booking_global_ical_token'` → JEST (zachowane)
|
||||
9. Frontend: otwórz stronę z kalendarzem dowolnego jachtu → kalendarz się ładuje, dostępność pokazuje (test regresji widget)
|
||||
10. Złóż testową rezerwację na frontendzie → trafia do bazy, pojawia się w admin → po refresh GCal subscription pojawi się w globalnym kalendarzu Google admina
|
||||
</how-to-verify>
|
||||
<resume-signal>Wpisz "approved" gdy 10 punktów przejdzie, lub opisz problemy do naprawy</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- **Globalna synchronizacja iCal (09-02)** — `output_global_ics`, `run_global_import`, `build_yacht_lookup_map`, `match_yacht_by_prefix`, `upsert_global_booking`, settings UI sekcja "Globalna synchronizacja iCal", `_yacht_gcal_alias`, opcje `yacht_booking_global_ical_*`, cron `yacht_booking_ical_global_import`
|
||||
- **`Yacht::get_gcal_alias`/`update_gcal_alias`** — używane przez globalny import
|
||||
- **CPT yacht_booking i yacht** — schemat, capabilities, registration
|
||||
- **`wp_yacht_availability`** — tabela i logika `Availability`
|
||||
- **Frontend widget i shortcode** — działają dzięki `_booking_yacht_id`
|
||||
- **REST API** (`Rest_Controller`) — endpoint POST /bookings używa wewnętrznych funkcji booking::create, niezależny od GCal
|
||||
- **Email templates + Inquiry CPT + CSV export** — out of scope
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Plan NIE dotyka security audit (to 09-04)
|
||||
- Plan NIE dotyka tłumaczeń .pot/.po/.mo (to 09-05)
|
||||
- Plan NIE refactoruje `parse_ics` ani helperów iCal (działają — zostają)
|
||||
- Plan NIE zmienia formatu prefiksu w SUMMARY (separator `" - "` z 09-02 zostaje)
|
||||
- Brak nowych dependencies
|
||||
- Brak admin notice o migracji (klient wybrał "po cichu")
|
||||
- Plan NIE migruje rezerwacji typu 'ical_import' do 'ical_import_global' — usuwa je (klient ponownie zaimportuje przez globalny mechanizm)
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Przed deklaracją ukończenia planu:
|
||||
- [ ] `php -l` przechodzi na wszystkich zmodyfikowanych plikach
|
||||
- [ ] `grep -r "OAuth_Handler\|GCal_Service\|Sync_Controller"` w plugin dir → 0 wyników (poza logami/changelog)
|
||||
- [ ] `grep -r "yacht_booking_ical_import[^_]"` (bez `_global`) → 0 wyników
|
||||
- [ ] `grep -r "_yacht_gcal_id\|_yacht_ical_import_url\|_yacht_ical_token"` → 0 wyników w runtime code
|
||||
- [ ] Aktywacja pluginu w WP nie powoduje fatal error (sprawdź error log)
|
||||
- [ ] Cleanup migration wykonuje się raz (przy upgrade) i jest idempotentne
|
||||
- [ ] Globalna iCal sync działa bez regresji (export + import + frontend)
|
||||
- [ ] URL per-yacht feed zwraca 404
|
||||
- [ ] uninstall.php usuwa wszystkie aktualne klucze (nic więcej, nic mniej)
|
||||
- [ ] Wszystkie 7 acceptance criteria spełnione
|
||||
- [ ] Checkpoint human-verify zaakceptowany przez klienta
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Plugin ma JEDEN mechanizm synchronizacji z GCal: globalny iCal feed (eksport + import)
|
||||
- Zero martwego kodu (3 pliki usunięte, ~500+ linii zbędnego UI/cron usuniętych)
|
||||
- Czysta baza po upgrade (klient nie widzi starych pól, baza nie zawiera nieaktywnych meta/opcji)
|
||||
- Brak regresji w globalnej iCal sync ani frontendzie
|
||||
- Plugin gotowy produkcyjnie w warstwie integracji GCal (pozostaje security audit + i18n + docs)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Po ukończeniu utwórz `.paul/phases/09-finalizacja/09-03-SUMMARY.md` zawierający:
|
||||
- Co usunięto (lista plików + linii kodu jeśli istotna)
|
||||
- Co zachowano (globalna iCal sync, alias, frontend)
|
||||
- Wyniki cleanup migration (ile meta/opcji/rezerwacji zostało skasowanych — z error_log)
|
||||
- Lista usuniętych klas/metod/hooków
|
||||
- Otwarte kwestie (jeśli wyszły w trakcie)
|
||||
</output>
|
||||
184
.paul/phases/09-finalizacja/09-03-SUMMARY.md
Normal file
184
.paul/phases/09-finalizacja/09-03-SUMMARY.md
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
phase: 09-finalizacja
|
||||
plan: 03
|
||||
subsystem: cleanup
|
||||
tags: [cleanup, oauth-removal, ical-consolidation, migration]
|
||||
|
||||
requires:
|
||||
- phase: 09-finalizacja/09-02
|
||||
provides: globalna iCal sync (export feed + import + alias) — staje się jedynym mechanizmem po usunięciu konkurencji
|
||||
|
||||
provides:
|
||||
- Plugin z jednym mechanizmem GCal sync (globalny iCal)
|
||||
- Cleanup migration "po cichu" (Installer::migrate, idempotent, version-aware)
|
||||
- Czysta baza po upgrade (4 stale meta keys + 6 stale options + per-yacht ical_import bookings + 5 cron hooków usuniętych)
|
||||
- Skrócony codebase (~700 linii martwego kodu usuniętych, 3 pliki OAuth znikają)
|
||||
|
||||
affects:
|
||||
- 09-04 Security audit — mniej powierzchni do auditu
|
||||
- 09-05 Testy + i18n + dokumentacja — mniej do udokumentowania
|
||||
- przyszłe milestone'y v1.x — czysty fundament bez OAuth dependencies
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Idempotent version-gated migration: Installer::migrate() porównuje get_option('yacht_booking_version') z YACHT_BOOKING_VERSION i wykonuje cleanup tylko przy upgrade"
|
||||
- "Cleanup po cichu — bez admin notice, klient nie musi wykonywać żadnych akcji ręcznych"
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- wp-content/plugins/yacht-booking-system/yacht-booking-system.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht-booking.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-yacht.php
|
||||
- wp-content/plugins/yacht-booking-system/includes/class-installer.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-admin.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/views/yacht-edit.php
|
||||
- wp-content/plugins/yacht-booking-system/admin/class-yacht-list-table.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-import.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/ical/class-ical-feed.php
|
||||
- wp-content/plugins/yacht-booking-system/uninstall.php
|
||||
deleted:
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-sync-controller.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-gcal-service.php
|
||||
- wp-content/plugins/yacht-booking-system/integrations/google-calendar/class-oauth-handler.php
|
||||
|
||||
key-decisions:
|
||||
- "Per-yacht bookings z source 'ical_import' — usuwamy w migration (klient ponownie zaimportuje przez globalny mechanizm), zamiast migrować je do source 'ical_import_global'"
|
||||
- "Migration idempotentna przez version_compare — kolejna aktywacja nie powtarza cleanup"
|
||||
- "Bez admin notice po migration — klient wybrał 'po cichu'"
|
||||
- "Per-yacht feedy iCal usunięte całkowicie (nie zostawione jako fallback) — model mentalny: jeden mechanizm"
|
||||
|
||||
patterns-established:
|
||||
- "Wersja-aware cleanup w Installer::install() — pattern do reuse w przyszłych planach refaktorujących/usuwających legacy"
|
||||
- "FQN namespace + lazy require_once dla klas używanych w activation hook (przed ładowaniem bootstrap)"
|
||||
|
||||
duration: ~30min
|
||||
started: 2026-05-06T11:30:00Z
|
||||
completed: 2026-05-06T12:00:00Z
|
||||
---
|
||||
|
||||
# Phase 9 Plan 03: Cleanup OAuth + per-yacht iCal Summary
|
||||
|
||||
**Wycofanie OAuth (3 pliki + cała sekcja UI), per-yacht iCal feed/import (pole "Google Calendar ID", "iCal Import URL", kolumna w yacht-list, cron) z bezgłośną migracją bazy — plugin sprowadzony do jednego mechanizmu sync (globalny iCal z 09-02).**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~30min |
|
||||
| Tasks | 3 auto + 1 human-verify (wszystkie zaakceptowane) |
|
||||
| Files modified | 10 |
|
||||
| Files deleted | 3 |
|
||||
| Lines removed | ~700+ (3 OAuth files + per-yacht iCal + UI + handlers) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Pliki OAuth usunięte i nie ładowane | Pass | 3 pliki + katalog `integrations/google-calendar/` usunięte. Bootstrap (`yacht-booking-system.php`, `class-yacht-booking.php`) bez require/wiring. PHP lint OK. |
|
||||
| AC-2: Sekcja OAuth zniknęła z Settings | Pass | `render_google_calendar_settings()` zawiera tylko nagłówek + sekcję "Globalna synchronizacja iCal". Usunięte: handle_oauth_callback, save_gcal_credentials, callback/disconnect/credentials w process_settings_save, notice connected/disconnected. |
|
||||
| AC-3: Pola per-jacht zniknęły z yacht-edit i yacht-list | Pass | yacht-edit nie ma "Google Calendar ID", "iCal Import URL", "iCal Feed URL". Ma tylko Alias. Lista jachtów bez kolumny "Google Calendar". |
|
||||
| AC-4: Per-jacht iCal cron i feed wyłączone | Pass | Cron `yacht_booking_ical_import` nie rejestrowany. Rewrite rule `^yacht-ical/(\d+)/...` usunięta — URL per-yacht zwróci 404. Globalny `^yacht-ical-global/...` aktywny. |
|
||||
| AC-5: Cleanup migration | Pass | `Installer::migrate()` — version-gated. Kasuje 4 meta keys (delete_post_meta_by_key), 6 opcji OAuth, bookings z source 'ical_import' (Availability::clear_booking_availability + wp_delete_post), 5 cron hooków. |
|
||||
| AC-6: Globalna iCal sync bez regresji | Pass | Boundary chronił z 09-02 — wszystkie globalne metody/UI/cron nietknięte, alias zachowany. |
|
||||
| AC-7: uninstall.php zaktualizowany | Pass | Dodane delete_option dla `yacht_booking_global_ical_*`, `yacht_booking_enabled`. Defensywne delete_option dla legacy gcal_* keys. |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Plugin ma JEDEN spójny mechanizm sync z GCal (globalny iCal), zamiast 4 (OAuth push, OAuth pull, per-yacht iCal feed, per-yacht iCal import) — drastyczne zmniejszenie powierzchni mentalnej i bug surface
|
||||
- Czysta baza po automatycznej migracji — klient nie wykonuje żadnych akcji ręcznych, niepotrzebne meta i opcje znikają przy następnej aktywacji pluginu
|
||||
- Codebase odchudzony o ~700+ linii martwego kodu (3 pliki OAuth/Sync + UI/CRUD per-yacht iCal)
|
||||
- Zero regresji w globalnej iCal sync ani frontendzie (kalendarz widget, REST API, CPT, availability) — boundaries respektowane
|
||||
- Plugin gotowy produkcyjnie w warstwie integracji GCal — pozostają tylko 09-04 (security audit) i 09-05 (testy + i18n + docs) do zamknięcia milestone v1.0
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `integrations/google-calendar/class-sync-controller.php` | **Deleted** | OAuth orchestrator usunięty całkowicie |
|
||||
| `integrations/google-calendar/class-gcal-service.php` | **Deleted** | Google Calendar API calls usunięte |
|
||||
| `integrations/google-calendar/class-oauth-handler.php` | **Deleted** | OAuth tokens + credentials usunięte |
|
||||
| `yacht-booking-system.php` | Modified | Wycięte require + setup_cron + clear_cron Sync_Controller w activate/deactivate hooks |
|
||||
| `includes/class-yacht-booking.php` | Modified | Wycięte 3 require_once + Sync_Controller::get_instance/register_cron_actions w `load_dependencies()` |
|
||||
| `includes/class-yacht.php` | Modified | Usunięte `get_gcal_id()`, `update_gcal_id()` |
|
||||
| `includes/class-installer.php` | Modified | Dodana `migrate()` (version-gated, idempotent), usunięty `yacht_booking_gcal_sync_enabled` z create_options |
|
||||
| `admin/class-admin.php` | Modified | Wycięte: handle_oauth_callback, save_gcal_credentials, OAuth bloki w process_settings_save i display_admin_notices, cała sekcja OAuth w render_google_calendar_settings, save_yacht zapisuje tylko alias |
|
||||
| `admin/views/yacht-edit.php` | Modified | Usunięte 3 wiersze tabeli (Google Calendar ID, iCal Import URL, iCal Feed URL) + 4 zmienne PHP. Pozostaje Alias. |
|
||||
| `admin/class-yacht-list-table.php` | Modified | Usunięta kolumna `gcal` z get_columns + metoda column_gcal |
|
||||
| `integrations/ical/class-ical-import.php` | Modified | Usunięte: const IMPORT_SOURCE, run_import, get_import_url, set_import_url, import_for_yacht, get_existing_import_map, upsert_booking, get_last_import_time. Cron 'yacht_booking_ical_import' nie rejestrowany. |
|
||||
| `integrations/ical/class-ical-feed.php` | Modified | Usunięte: rewrite rule per-yacht, query var yacht_ical_id, branch per-yacht w handle_feed_request, output_ics, get_feed_token, regenerate_token, get_feed_url. Pozostaje globalna ścieżka. |
|
||||
| `uninstall.php` | Modified | Dodane: yacht_booking_global_ical_*, yacht_booking_enabled. Defensywne legacy gcal_* delete_option. |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Usunięcie per-yacht bookings 'ical_import' w migration | Bez per-yacht importu nie będą odświeżane — lepsze UX usunąć i pozwolić ponowny globalny import niż zostawić "skamieniałe" blokady | Klient po deploy uruchamia "Importuj teraz" raz i ma świeże dane |
|
||||
| Migration version-gated z version_compare | Idempotencja — kolejna aktywacja po pierwszej nie powtarza cleanup, bezpieczne dla repeated activate/deactivate | Zero ryzyka przy ręcznych re-aktywacjach lub WP auto-update |
|
||||
| FQN dla `\YachtBooking\Availability` w migrate() | Activation hook ładuje Installer ręcznie przed bootstrap — autoloader może być nieaktywny | Lazy require_once jako fallback wewnątrz pętli `if (!class_exists)` |
|
||||
| Defensywne delete_option dla legacy gcal_* w uninstall.php | Migration usuwa je przy upgrade, ale uninstall.php może być uruchomiony bezpośrednio na starszej wersji bez upgrade | Wszystkie ścieżki sprzątają legacy keys |
|
||||
| Brak admin notice po migration | Klient w fazie planowania wybrał "po cichu" — żeby nie zaprzątać uwagi | Płynne UX, klient zauważy tylko że stare pola znikły |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Auto-fixed | 1 | Drobny |
|
||||
| Scope additions | 0 | — |
|
||||
| Deferred | 0 | — |
|
||||
|
||||
**Total impact:** Plan wykonany w pełni, jeden drobny auto-fix dla bezpieczeństwa.
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Migration safety] Availability class loading w activation hook**
|
||||
- **Found during:** Task 3 (Installer::migrate)
|
||||
- **Issue:** Migration wywołuje `\YachtBooking\Availability::clear_booking_availability()`, ale Installer jest ładowany przez activation hook PRZED `plugins_loaded` (gdzie autoloader spl_autoload_register() jest rejestrowany). Pierwotna implementacja zakładała że Availability jest dostępne automatycznie.
|
||||
- **Fix:** Dodany guard `if ( ! class_exists( '\YachtBooking\Availability' ) ) { require_once YACHT_BOOKING_PLUGIN_DIR . 'includes/class-availability.php'; }` przed pętlą usuwającą stale bookings. Dodatkowo otoczone `if (!empty($stale_bookings))` żeby nie ładować pliku gdy nie ma nic do usunięcia.
|
||||
- **Files:** includes/class-installer.php
|
||||
- **Verification:** PHP lint OK; logika idempotentna; w testach klient potwierdził że upgrade nie powoduje fatal error.
|
||||
|
||||
### Deferred Items
|
||||
|
||||
Brak.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Brak konkretnych fatal errors lub regresji | — |
|
||||
|
||||
## Reproduction Path (do testów regresyjnych)
|
||||
|
||||
1. Po deploy FTP — aktywuj plugin (lub deactivate→activate na produkcji). Wyzwoli `Installer::install()` → `migrate()`.
|
||||
2. Sprawdź WP Admin → Settings → Google Calendar: tylko sekcja "Globalna synchronizacja iCal" (z 09-02).
|
||||
3. Sprawdź edycję jachtu: brak "Google Calendar ID"/"iCal Import URL"/"iCal Feed URL". Jest tylko "Alias dla Google Calendar".
|
||||
4. Lista jachtów: brak kolumny "Google Calendar".
|
||||
5. URL `/yacht-ical/X/token.ics` → 404. URL `/yacht-ical-global/{token}.ics` → poprawny .ics.
|
||||
6. phpMyAdmin: brak meta `_yacht_gcal_id`/`_yacht_ical_*`, brak opcji `yacht_booking_gcal_*` (poza globalnymi `*_global_ical_*`), brak bookingów z source `ical_import`.
|
||||
7. Druga aktywacja → migrate() nie powtarza cleanup (version match).
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Codebase czysty, 1 mechanizm GCal sync
|
||||
- Migration mechanizm gotowy do reuse w przyszłych planach refaktorujących
|
||||
- Plugin z punktu widzenia integracji GCal jest produkcyjnie skończony
|
||||
|
||||
**Concerns:**
|
||||
- Klient po deploy musi pamiętać o uruchomieniu globalnego "Importuj teraz" w Settings, żeby ponownie zaimportować eventy z Google Calendar (stare per-yacht zostały skasowane). Mitigacja: zostało udokumentowane w checkpoint verify steps.
|
||||
- `yacht_booking_capabilities_added` flag jest sprawdzany raz w `Yacht_Booking::add_custom_capabilities()` — zachowany w uninstall.php. Nie ruszamy go.
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
## Otwarte kwestie milestone v1.0
|
||||
|
||||
- 09-04: Security audit (nonce, escaping, SQL prepare, capabilities)
|
||||
- 09-05: Testy + tłumaczenia .pot/.po/.mo + dokumentacja użytkownika i dewelopera
|
||||
|
||||
---
|
||||
*Phase: 09-finalizacja, Plan: 03*
|
||||
*Completed: 2026-05-06*
|
||||
208
.vscode/ftp-kr.sync.cache.json
vendored
Normal file
208
.vscode/ftp-kr.sync.cache.json
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"ftp://host117523.hostido.net.pl@www@jachty3.pagedev.pl": {
|
||||
"public_html": {
|
||||
".htaccess": {
|
||||
"type": "-",
|
||||
"size": 665,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"index.php": {
|
||||
"type": "-",
|
||||
"size": 405,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"license.txt": {
|
||||
"type": "-",
|
||||
"size": 19903,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"__MACOSX": {},
|
||||
".planning": {
|
||||
"codebase": {
|
||||
"ARCHITECTURE.md": {
|
||||
"type": "-",
|
||||
"size": 12531,
|
||||
"lmtime": 1778017398128,
|
||||
"modified": false
|
||||
},
|
||||
"CONVENTIONS.md": {
|
||||
"type": "-",
|
||||
"size": 9024,
|
||||
"lmtime": 1778017439483,
|
||||
"modified": false
|
||||
},
|
||||
"INTEGRATIONS.md": {
|
||||
"type": "-",
|
||||
"size": 13136,
|
||||
"lmtime": 1778017420536,
|
||||
"modified": false
|
||||
},
|
||||
"STACK.md": {
|
||||
"type": "-",
|
||||
"size": 3636,
|
||||
"lmtime": 1778017353336,
|
||||
"modified": false
|
||||
},
|
||||
"STRUCTURE.md": {
|
||||
"type": "-",
|
||||
"size": 8663,
|
||||
"lmtime": 1778017445887,
|
||||
"modified": false
|
||||
},
|
||||
"TESTING.md": {
|
||||
"type": "-",
|
||||
"size": 6979,
|
||||
"lmtime": 1778017485643,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"readme.html": {
|
||||
"type": "-",
|
||||
"size": 7425,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-activate.php": {
|
||||
"type": "-",
|
||||
"size": 7349,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-admin": {},
|
||||
"wp-blog-header.php": {
|
||||
"type": "-",
|
||||
"size": 351,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-comments-post.php": {
|
||||
"type": "-",
|
||||
"size": 2323,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-config.php": {
|
||||
"type": "-",
|
||||
"size": 3764,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-config-sample.php": {
|
||||
"type": "-",
|
||||
"size": 3339,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-content": {
|
||||
"plugins": {
|
||||
"yacht-booking-system": {
|
||||
"frontend": {
|
||||
"assets": {
|
||||
"js": {
|
||||
"calendar.js": {
|
||||
"type": "-",
|
||||
"size": 15422,
|
||||
"lmtime": 1778018642248,
|
||||
"modified": false
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"calendar.css": {
|
||||
"type": "-",
|
||||
"size": 12984,
|
||||
"lmtime": 1778019168166,
|
||||
"modified": false
|
||||
},
|
||||
"calendar.css.map": {
|
||||
"type": "-",
|
||||
"size": 4874,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"calendar.scss": {
|
||||
"type": "-",
|
||||
"size": 15260,
|
||||
"lmtime": 1778019161179,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"class-calendar-widget.php": {
|
||||
"type": "-",
|
||||
"size": 21040,
|
||||
"lmtime": 1778019128363,
|
||||
"modified": false
|
||||
},
|
||||
"class-shortcode.php": {
|
||||
"type": "-",
|
||||
"size": 14528,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wp-cron.php": {
|
||||
"type": "-",
|
||||
"size": 5617,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-includes": {},
|
||||
"wp-links-opml.php": {
|
||||
"type": "-",
|
||||
"size": 2493,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-load.php": {
|
||||
"type": "-",
|
||||
"size": 3937,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-login.php": {
|
||||
"type": "-",
|
||||
"size": 51437,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-mail.php": {
|
||||
"type": "-",
|
||||
"size": 8727,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-settings.php": {
|
||||
"type": "-",
|
||||
"size": 31055,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-signup.php": {
|
||||
"type": "-",
|
||||
"size": 34516,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"wp-trackback.php": {
|
||||
"type": "-",
|
||||
"size": 5214,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
},
|
||||
"xmlrpc.php": {
|
||||
"type": "-",
|
||||
"size": 3205,
|
||||
"lmtime": 0,
|
||||
"modified": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"$version": 1
|
||||
}
|
||||
@@ -64,27 +64,6 @@ class Admin {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load Google Calendar classes if needed
|
||||
if ( file_exists( YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-oauth-handler.php' ) ) {
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-oauth-handler.php';
|
||||
}
|
||||
if ( file_exists( YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-gcal-service.php' ) ) {
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-gcal-service.php';
|
||||
}
|
||||
|
||||
// Handle OAuth callback
|
||||
if ( isset( $_GET['gcal_callback'] ) && isset( $_GET['code'] ) ) {
|
||||
$this->handle_oauth_callback();
|
||||
}
|
||||
|
||||
// Handle disconnect
|
||||
if ( isset( $_POST['yacht_booking_disconnect_gcal'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_disconnect_gcal' );
|
||||
\YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::disconnect();
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&disconnected=1' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle general settings save
|
||||
if ( isset( $_POST['yacht_booking_save_settings'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_save_settings' );
|
||||
@@ -105,10 +84,30 @@ class Admin {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle credentials save
|
||||
if ( isset( $_POST['yacht_booking_save_gcal_credentials'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_save_gcal_credentials' );
|
||||
$this->save_gcal_credentials();
|
||||
// Handle global iCal settings save.
|
||||
if ( isset( $_POST['yacht_booking_save_global_ical'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_save_global_ical' );
|
||||
$import_url = isset( $_POST['global_ical_import_url'] ) ? esc_url_raw( wp_unslash( $_POST['global_ical_import_url'] ) ) : '';
|
||||
update_option( 'yacht_booking_global_ical_import_url', $import_url );
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&global_ical_saved=1' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle global iCal token regeneration.
|
||||
if ( isset( $_POST['yacht_booking_regenerate_global_ical_token'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_regenerate_global_ical_token' );
|
||||
\YachtBooking\Integrations\ICal\ICal_Feed::regenerate_global_token();
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&token_regenerated=1' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle global iCal manual import trigger.
|
||||
if ( isset( $_POST['yacht_booking_run_global_ical_import'] ) ) {
|
||||
check_admin_referer( 'yacht_booking_run_global_ical_import' );
|
||||
$result = \YachtBooking\Integrations\ICal\ICal_Import::run_global_import();
|
||||
$arg = $result ? 'global_import_done=1' : 'global_import_failed=1';
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&' . $arg ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,24 +147,6 @@ class Admin {
|
||||
<?php
|
||||
}
|
||||
|
||||
// Success: Connected to Google Calendar
|
||||
if ( isset( $_GET['connected'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Połączono z Google Calendar pomyślnie!', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Success: Disconnected from Google Calendar
|
||||
if ( isset( $_GET['disconnected'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Rozłączono z Google Calendar.', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Error
|
||||
if ( isset( $_GET['error'] ) ) {
|
||||
?>
|
||||
@@ -174,6 +155,38 @@ class Admin {
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ( isset( $_GET['global_ical_saved'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Globalne ustawienia iCal zostały zapisane.', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ( isset( $_GET['token_regenerated'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Wygenerowano nowy token globalnego feed iCal. Poprzedni URL został unieważniony — zaktualizuj subskrypcję w Google Calendar.', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ( isset( $_GET['global_import_done'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Globalny import iCal wykonany. Sprawdź listę rezerwacji.', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
if ( isset( $_GET['global_import_failed'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-error is-dismissible">
|
||||
<p><strong><?php esc_html_e( 'Globalny import iCal nie powiódł się. Sprawdź czy URL jest poprawny i logi serwera.', 'yacht-booking' ); ?></strong></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,7 +377,6 @@ class Admin {
|
||||
// Validate
|
||||
$title = isset( $data['yacht_title'] ) ? sanitize_text_field( wp_unslash( $data['yacht_title'] ) ) : '';
|
||||
$content = isset( $data['yacht_description'] ) ? wp_kses_post( wp_unslash( $data['yacht_description'] ) ) : '';
|
||||
$gcal_id = isset( $data['yacht_gcal_id'] ) ? sanitize_text_field( wp_unslash( $data['yacht_gcal_id'] ) ) : '';
|
||||
|
||||
if ( empty( $title ) ) {
|
||||
return false;
|
||||
@@ -389,12 +401,9 @@ class Admin {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save meta
|
||||
Yacht::update_gcal_id( $saved_id, $gcal_id );
|
||||
|
||||
// Save iCal import URL
|
||||
$ical_import_url = isset( $data['yacht_ical_import_url'] ) ? esc_url_raw( wp_unslash( $data['yacht_ical_import_url'] ) ) : '';
|
||||
\YachtBooking\Integrations\ICal\ICal_Import::set_import_url( $saved_id, $ical_import_url );
|
||||
// Save GCal alias for global iCal import matching.
|
||||
$gcal_alias = isset( $data['yacht_gcal_alias'] ) ? sanitize_text_field( wp_unslash( $data['yacht_gcal_alias'] ) ) : '';
|
||||
Yacht::update_gcal_alias( $saved_id, $gcal_alias );
|
||||
|
||||
return $saved_id;
|
||||
}
|
||||
@@ -1221,181 +1230,115 @@ class Admin {
|
||||
* Render Google Calendar settings tab
|
||||
*/
|
||||
private function render_google_calendar_settings() {
|
||||
// Check if classes exist
|
||||
if ( ! class_exists( '\YachtBooking\Integrations\GoogleCalendar\OAuth_Handler' ) ||
|
||||
! class_exists( '\YachtBooking\Integrations\GoogleCalendar\GCal_Service' ) ) {
|
||||
?>
|
||||
<div class="notice notice-error">
|
||||
<p><?php esc_html_e( 'Błąd: Klasy Google Calendar nie zostały załadowane. Skontaktuj się z administratorem.', 'yacht-booking' ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
|
||||
$is_connected = \YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::is_connected();
|
||||
$connected_email = $is_connected ? \YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::get_connected_email() : false;
|
||||
$credentials = \YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::get_credentials();
|
||||
$calendar_id = \YachtBooking\Integrations\GoogleCalendar\GCal_Service::get_calendar_id();
|
||||
|
||||
?>
|
||||
<h2><?php esc_html_e( 'Konfiguracja Google Calendar', 'yacht-booking' ); ?></h2>
|
||||
<h2><?php esc_html_e( 'Synchronizacja z Google Calendar (iCal)', 'yacht-booking' ); ?></h2>
|
||||
|
||||
<?php if ( ! $is_connected ) : ?>
|
||||
<!-- Step 1: Enter credentials -->
|
||||
<div class="card">
|
||||
<h3><?php esc_html_e( 'Krok 1: Dodaj dane OAuth', 'yacht-booking' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Aby połączyć się z Google Calendar, musisz utworzyć projekt w Google Cloud Console i pobrać Client ID oraz Client Secret.', 'yacht-booking' ); ?></p>
|
||||
|
||||
<ol>
|
||||
<li><?php esc_html_e( 'Przejdź do', 'yacht-booking' ); ?> <a href="https://console.cloud.google.com/" target="_blank">Google Cloud Console</a></li>
|
||||
<li><?php esc_html_e( 'Utwórz nowy projekt lub wybierz istniejący', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Włącz Google Calendar API', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Utwórz OAuth 2.0 Client ID (typ: Web application)', 'yacht-booking' ); ?></li>
|
||||
<li>
|
||||
<?php esc_html_e( 'Dodaj Authorized redirect URI:', 'yacht-booking' ); ?>
|
||||
<br>
|
||||
<code style="background: #f0f0f0; padding: 5px 10px; display: inline-block; margin: 5px 0;">https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1</code>
|
||||
<br>
|
||||
<button type="button" class="button button-small" onclick="navigator.clipboard.writeText('https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1'); this.textContent='Skopiowano!';">
|
||||
<?php esc_html_e( 'Kopiuj', 'yacht-booking' ); ?>
|
||||
</button>
|
||||
</li>
|
||||
<li><?php esc_html_e( 'Skopiuj Client ID i Client Secret', 'yacht-booking' ); ?></li>
|
||||
</ol>
|
||||
|
||||
<form method="post" action="">
|
||||
<?php wp_nonce_field( 'yacht_booking_save_gcal_credentials' ); ?>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="gcal_client_id"><?php esc_html_e( 'Client ID', 'yacht-booking' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" name="gcal_client_id" id="gcal_client_id" class="regular-text" value="<?php echo esc_attr( isset( $credentials['client_id'] ) ? $credentials['client_id'] : '' ); ?>" required>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="gcal_client_secret"><?php esc_html_e( 'Client Secret', 'yacht-booking' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" name="gcal_client_secret" id="gcal_client_secret" class="regular-text" value="<?php echo esc_attr( isset( $credentials['client_secret'] ) ? $credentials['client_secret'] : '' ); ?>" required>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<input type="submit" name="yacht_booking_save_gcal_credentials" class="button-primary" value="<?php esc_attr_e( 'Zapisz i przejdź do autoryzacji', 'yacht-booking' ); ?>">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ( $credentials ) : ?>
|
||||
<!-- Step 2: Authorize -->
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<h3><?php esc_html_e( 'Krok 2: Autoryzuj połączenie', 'yacht-booking' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Kliknij poniższy przycisk aby autoryzować dostęp do Google Calendar.', 'yacht-booking' ); ?></p>
|
||||
|
||||
<?php
|
||||
$auth_url = \YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::get_auth_url();
|
||||
if ( $auth_url ) :
|
||||
// Parse URL to extract redirect_uri for debugging
|
||||
$parsed_url = wp_parse_url( $auth_url );
|
||||
parse_str( isset( $parsed_url['query'] ) ? $parsed_url['query'] : '', $query_params );
|
||||
$redirect_uri_sent = isset( $query_params['redirect_uri'] ) ? $query_params['redirect_uri'] : '';
|
||||
?>
|
||||
<p>
|
||||
<a href="<?php echo esc_url( $auth_url ); ?>" class="button button-primary button-hero">
|
||||
<?php esc_html_e( 'Połącz z Google Calendar', 'yacht-booking' ); ?>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- DEBUG: Show exact redirect_uri being sent -->
|
||||
<div style="margin-top: 15px; padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107;">
|
||||
<strong>DEBUG - Redirect URI wysyłany do Google:</strong>
|
||||
<br>
|
||||
<code style="display: block; margin-top: 5px; padding: 5px; background: white; word-break: break-all;">
|
||||
<?php echo esc_html( $redirect_uri_sent ); ?>
|
||||
</code>
|
||||
<small style="color: #856404;">Porównaj dokładnie z tym co masz w Google Cloud Console (character-by-character)</small>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<p class="description" style="color: #d63638;">
|
||||
<?php esc_html_e( 'Błąd generowania URL autoryzacji. Sprawdź Client ID i Secret.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else : ?>
|
||||
<!-- Connected status -->
|
||||
<div class="card">
|
||||
<h3><?php esc_html_e( 'Status połączenia', 'yacht-booking' ); ?></h3>
|
||||
<p>
|
||||
<span class="dashicons dashicons-yes-alt" style="color: #46b450; font-size: 20px;"></span>
|
||||
<strong><?php esc_html_e( 'Połączono z Google Calendar', 'yacht-booking' ); ?></strong>
|
||||
</p>
|
||||
<?php if ( $connected_email ) : ?>
|
||||
<p><?php esc_html_e( 'Konto:', 'yacht-booking' ); ?> <code><?php echo esc_html( $connected_email ); ?></code></p>
|
||||
<?php endif; ?>
|
||||
<p><?php esc_html_e( 'Kalendarz:', 'yacht-booking' ); ?> <code><?php echo esc_html( $calendar_id ); ?></code></p>
|
||||
|
||||
<form method="post" action="" onsubmit="return confirm('<?php esc_attr_e( 'Czy na pewno chcesz rozłączyć Google Calendar?', 'yacht-booking' ); ?>')">
|
||||
<?php wp_nonce_field( 'yacht_booking_disconnect_gcal' ); ?>
|
||||
<p class="submit">
|
||||
<input type="submit" name="yacht_booking_disconnect_gcal" class="button button-secondary" value="<?php esc_attr_e( 'Rozłącz', 'yacht-booking' ); ?>">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Manual sync -->
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<h3><?php esc_html_e( 'Synchronizacja ręczna', 'yacht-booking' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Synchronizacja odbywa się automatycznie, ale możesz wymusić synchronizację ręcznie.', 'yacht-booking' ); ?></p>
|
||||
<p>
|
||||
<button type="button" id="yacht-booking-manual-sync" class="button" data-nonce="<?php echo esc_attr( wp_create_nonce( 'yacht_booking_manual_sync' ) ); ?>">
|
||||
<?php esc_html_e( 'Synchronizuj teraz', 'yacht-booking' ); ?>
|
||||
</button>
|
||||
<span id="yacht-booking-sync-status" style="margin-left: 10px;"></span>
|
||||
</p>
|
||||
<div id="yacht-booking-sync-result" style="margin-top: 10px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top: 20px;">
|
||||
<h3><?php esc_html_e( 'Instrukcja: dodawanie rezerwacji po stronie Google Calendar', 'yacht-booking' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Poniższe kroki pozwalają dodać wydarzenie w Google Calendar tak, aby po synchronizacji zostało zablokowane w kalendarzu WordPress.', 'yacht-booking' ); ?></p>
|
||||
<ol style="margin-left: 20px;">
|
||||
<li><?php esc_html_e( 'Dodaj wydarzenie w tym samym kalendarzu, który jest połączony z pluginem (widoczny wyżej jako \"Kalendarz\").', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Ustaw datę rozpoczęcia i datę zakończenia (wydarzenie całodniowe lub godzinowe).', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Zapisz wydarzenie w Google Calendar.', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Kliknij \"Synchronizuj teraz\" albo poczekaj na automatyczną synchronizację (cron uruchamiany co godzinę).', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Po synchronizacji wydarzenie z Google zostanie zaimportowane do panelu jako rezerwacja techniczna (źródło: Google Calendar), którą można usunąć z poziomu listy rezerwacji.', 'yacht-booking' ); ?></li>
|
||||
</ol>
|
||||
<p class="description"><?php esc_html_e( 'Uwaga: import obejmuje wydarzenia od teraz do +1 roku i pomija wydarzenia, które zostały utworzone przez ten plugin (aby uniknąć duplikatów).', 'yacht-booking' ); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
}
|
||||
// =====================================================================
|
||||
// Globalna synchronizacja iCal (jeden wspólny kalendarz Google,
|
||||
// podział na jachty po prefiksie nazwy w tytule eventu).
|
||||
// Działa niezależnie od OAuth — wystarczy publiczny URL kalendarza Google.
|
||||
// =====================================================================
|
||||
$global_import_url = (string) get_option( 'yacht_booking_global_ical_import_url', '' );
|
||||
$global_export_url = \YachtBooking\Integrations\ICal\ICal_Feed::get_global_feed_url();
|
||||
$last_global_run = (string) get_option( 'yacht_booking_global_ical_last_import', '' );
|
||||
?>
|
||||
<div class="card" style="margin-top: 30px;">
|
||||
<h3><?php esc_html_e( 'Globalna synchronizacja iCal (jeden wspólny kalendarz)', 'yacht-booking' ); ?></h3>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Mechanizm dwukierunkowej synchronizacji wszystkich jachtów z jednym kalendarzem Google, bez OAuth. Plugin rozpoznaje jacht po prefiksie tytułu eventu w formacie: "Nazwa jachtu - opis" (np. "Maja - Kowalski 5 osób"). Eventy bez rozpoznanego prefiksu są ignorowane.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
|
||||
/**
|
||||
* Handle OAuth callback
|
||||
*/
|
||||
private function handle_oauth_callback() {
|
||||
if ( ! isset( $_GET['code'] ) ) {
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&error=1' ) );
|
||||
exit;
|
||||
}
|
||||
<h4><?php esc_html_e( 'Krok 1: Dodaj feed pluginu jako kalendarz w Google', 'yacht-booking' ); ?></h4>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Skopiuj poniższy URL i dodaj go w Google Calendar: "Inne kalendarze" → "+ Dodaj" → "Z URL-a". Rezerwacje ze strony pojawią się w Twoim Google Calendar (Google odświeża subskrypcję co kilka godzin).', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="<?php echo esc_attr( $global_export_url ); ?>"
|
||||
class="regular-text code"
|
||||
id="yacht-global-ical-export-url"
|
||||
style="width: 100%; max-width: 600px;"
|
||||
onclick="this.select();"
|
||||
/>
|
||||
<button type="button" class="button" onclick="navigator.clipboard.writeText(document.getElementById('yacht-global-ical-export-url').value); this.textContent='<?php echo esc_js( __( 'Skopiowano!', 'yacht-booking' ) ); ?>';">
|
||||
<?php esc_html_e( 'Kopiuj', 'yacht-booking' ); ?>
|
||||
</button>
|
||||
</p>
|
||||
<form method="post" action="" style="margin-top: 10px;" onsubmit="return confirm('<?php echo esc_js( __( 'Wygenerować nowy token? Poprzedni URL przestanie działać.', 'yacht-booking' ) ); ?>')">
|
||||
<?php wp_nonce_field( 'yacht_booking_regenerate_global_ical_token' ); ?>
|
||||
<button type="submit" name="yacht_booking_regenerate_global_ical_token" class="button button-secondary">
|
||||
<?php esc_html_e( 'Wygeneruj nowy token', 'yacht-booking' ); ?>
|
||||
</button>
|
||||
<span class="description" style="margin-left: 10px;">
|
||||
<?php esc_html_e( 'Unieważnia bieżący URL — używaj gdy podejrzewasz wyciek.', 'yacht-booking' ); ?>
|
||||
</span>
|
||||
</form>
|
||||
|
||||
$code = sanitize_text_field( wp_unslash( $_GET['code'] ) );
|
||||
$result = \YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::authenticate( $code );
|
||||
<h4 style="margin-top: 25px;"><?php esc_html_e( 'Krok 2: Wklej iCal URL Twojego kalendarza Google', 'yacht-booking' ); ?></h4>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'W Google Calendar otwórz ustawienia kalendarza → "Tajny adres w formacie iCal" → skopiuj URL i wklej poniżej. Plugin pobiera ten kalendarz co godzinę i importuje rozpoznane eventy jako blokady.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<form method="post" action="">
|
||||
<?php wp_nonce_field( 'yacht_booking_save_global_ical' ); ?>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="global_ical_import_url"><?php esc_html_e( 'iCal Import URL', 'yacht-booking' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="url"
|
||||
name="global_ical_import_url"
|
||||
id="global_ical_import_url"
|
||||
class="regular-text"
|
||||
value="<?php echo esc_attr( $global_import_url ); ?>"
|
||||
placeholder="https://calendar.google.com/calendar/ical/..."
|
||||
style="width: 100%; max-width: 600px;"
|
||||
/>
|
||||
<?php if ( $last_global_run ) : ?>
|
||||
<p class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: timestamp */
|
||||
esc_html__( 'Ostatni import: %s', 'yacht-booking' ),
|
||||
esc_html( $last_global_run )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p class="submit">
|
||||
<input type="submit" name="yacht_booking_save_global_ical" class="button-primary" value="<?php esc_attr_e( 'Zapisz URL', 'yacht-booking' ); ?>">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
if ( $result ) {
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&connected=1' ) );
|
||||
} else {
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&error=1' ) );
|
||||
}
|
||||
exit;
|
||||
<h4 style="margin-top: 25px;"><?php esc_html_e( 'Krok 3: Importuj teraz (opcjonalnie)', 'yacht-booking' ); ?></h4>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Cron godzinny pobiera kalendarz automatycznie. Możesz wymusić import ręcznie:', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<form method="post" action="">
|
||||
<?php wp_nonce_field( 'yacht_booking_run_global_ical_import' ); ?>
|
||||
<button type="submit" name="yacht_booking_run_global_ical_import" class="button">
|
||||
<?php esc_html_e( 'Importuj teraz', 'yacht-booking' ); ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h4 style="margin-top: 25px;"><?php esc_html_e( 'Jak to działa', 'yacht-booking' ); ?></h4>
|
||||
<ul style="margin-left: 20px; list-style: disc;">
|
||||
<li><?php esc_html_e( 'Tytuły eventów w Google Calendar muszą mieć format: "Nazwa jachtu - dowolny opis" (separator to spacja-myślnik-spacja).', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Dopasowanie po pełnej nazwie jachtu lub jego aliasie (pole "Alias dla Google Calendar" w edycji jachtu).', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Eventy bez separatora albo z nieznanym prefiksem są ignorowane (nie tworzą blokad).', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Usunięcie eventu w Google Calendar usuwa odpowiadającą blokadę po następnym imporcie.', 'yacht-booking' ); ?></li>
|
||||
<li><?php esc_html_e( 'Eventy zaimportowane z Google nie są wysyłane z powrotem (anti-loop).', 'yacht-booking' ); ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1441,23 +1384,6 @@ class Admin {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Google Calendar credentials
|
||||
*/
|
||||
private function save_gcal_credentials() {
|
||||
if ( isset( $_POST['gcal_client_id'] ) && isset( $_POST['gcal_client_secret'] ) ) {
|
||||
$credentials = array(
|
||||
'client_id' => sanitize_text_field( wp_unslash( $_POST['gcal_client_id'] ) ),
|
||||
'client_secret' => sanitize_text_field( wp_unslash( $_POST['gcal_client_secret'] ) ),
|
||||
);
|
||||
|
||||
\YachtBooking\Integrations\GoogleCalendar\OAuth_Handler::save_credentials( $credentials );
|
||||
}
|
||||
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=yacht-bookings-settings&tab=google-calendar&saved=1' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process booking actions (approve, cancel, delete)
|
||||
*/
|
||||
|
||||
@@ -44,7 +44,6 @@ class Yacht_List_Table extends \WP_List_Table {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'title' => __( 'Nazwa jachtu', 'yacht-booking' ),
|
||||
'gcal' => __( 'Google Calendar', 'yacht-booking' ),
|
||||
'bookings' => __( 'Rezerwacje', 'yacht-booking' ),
|
||||
'date' => __( 'Data utworzenia', 'yacht-booking' ),
|
||||
);
|
||||
@@ -163,28 +162,6 @@ class Yacht_List_Table extends \WP_List_Table {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column Google Calendar
|
||||
*
|
||||
* @param object $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_gcal( $item ) {
|
||||
$gcal_id = Yacht::get_gcal_id( $item->ID );
|
||||
|
||||
if ( $gcal_id ) {
|
||||
return sprintf(
|
||||
'<span class="gcal-status connected"><span class="dashicons dashicons-yes-alt"></span> %s</span>',
|
||||
esc_html__( 'Połączony', 'yacht-booking' )
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<span class="gcal-status disconnected"><span class="dashicons dashicons-dismiss"></span> %s</span>',
|
||||
esc_html__( 'Niepołączony', 'yacht-booking' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column bookings count
|
||||
*
|
||||
|
||||
@@ -11,12 +11,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
}
|
||||
|
||||
// Get yacht data
|
||||
$title = $yacht ? $yacht->post_title : '';
|
||||
$content = $yacht ? $yacht->post_content : '';
|
||||
$gcal_id = $yacht ? \YachtBooking\Yacht::get_gcal_id( $yacht->ID ) : '';
|
||||
$ical_import_url = $yacht ? \YachtBooking\Integrations\ICal\ICal_Import::get_import_url( $yacht->ID ) : '';
|
||||
$ical_feed_url = $yacht ? \YachtBooking\Integrations\ICal\ICal_Feed::get_feed_url( $yacht->ID ) : '';
|
||||
$ical_last_import = $yacht ? \YachtBooking\Integrations\ICal\ICal_Import::get_last_import_time( $yacht->ID ) : '';
|
||||
$title = $yacht ? $yacht->post_title : '';
|
||||
$content = $yacht ? $yacht->post_content : '';
|
||||
$gcal_alias = $yacht ? \YachtBooking\Yacht::get_gcal_alias( $yacht->ID ) : '';
|
||||
|
||||
$page_title = $yacht ? __( 'Edytuj Jacht', 'yacht-booking' ) : __( 'Dodaj Jacht', 'yacht-booking' );
|
||||
?>
|
||||
@@ -89,100 +86,28 @@ $page_title = $yacht ? __( 'Edytuj Jacht', 'yacht-booking' ) : __( 'Dodaj Jacht'
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Google Calendar ID -->
|
||||
<!-- Alias dla globalnej synchronizacji iCal -->
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="yacht_gcal_id">
|
||||
<?php esc_html_e( 'Google Calendar ID', 'yacht-booking' ); ?>
|
||||
<label for="yacht_gcal_alias">
|
||||
<?php esc_html_e( 'Alias dla Google Calendar', 'yacht-booking' ); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
name="yacht_gcal_id"
|
||||
id="yacht_gcal_id"
|
||||
value="<?php echo esc_attr( $gcal_id ); ?>"
|
||||
class="regular-text code"
|
||||
placeholder="nazwa@group.calendar.google.com"
|
||||
name="yacht_gcal_alias"
|
||||
id="yacht_gcal_alias"
|
||||
value="<?php echo esc_attr( $gcal_alias ); ?>"
|
||||
class="regular-text"
|
||||
placeholder="<?php echo esc_attr( $title ); ?>"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: %s: link to Google Calendar settings */
|
||||
__( 'ID kalendarza Google do synchronizacji rezerwacji. <a href="%s" target="_blank">Jak znaleźć Calendar ID?</a>', 'yacht-booking' ),
|
||||
'https://docs.simplecalendar.io/find-google-calendar-id/'
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php esc_html_e( 'Opcjonalny krótki alias używany w tytule eventu Google Calendar (np. "Maja" zamiast "Marina Maja Sailing 35"). Plugin rozpoznaje jacht po prefiksie tytułu eventu w formacie "Alias - opis". Jeśli puste — używana jest pełna nazwa jachtu.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<?php if ( $gcal_id ) : ?>
|
||||
<p class="gcal-status connected">
|
||||
<span class="dashicons dashicons-yes-alt"></span>
|
||||
<?php esc_html_e( 'Połączony z Google Calendar', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- iCal Import URL -->
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="yacht_ical_import_url">
|
||||
<?php esc_html_e( 'Import iCal (URL)', 'yacht-booking' ); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="url"
|
||||
name="yacht_ical_import_url"
|
||||
id="yacht_ical_import_url"
|
||||
value="<?php echo esc_attr( $ical_import_url ); ?>"
|
||||
class="large-text code"
|
||||
placeholder="https://calendar.google.com/calendar/ical/...basic.ics"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Wklej adres URL pliku .ics z Google Calendar (lub innego kalendarza). Wydarzenia zostaną zaimportowane jako zablokowane terminy (synchronizacja co godzinę).', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<p class="description">
|
||||
<strong><?php esc_html_e( 'Jak uzyskać link iCal z Google Calendar:', 'yacht-booking' ); ?></strong>
|
||||
<?php esc_html_e( 'Google Calendar → Ustawienia → Wybierz kalendarz → „Tajny adres w formacie iCal" → Skopiuj link.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
<?php if ( $ical_last_import ) : ?>
|
||||
<p>
|
||||
<span class="dashicons dashicons-update" style="color: #2271b1;"></span>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: date/time */
|
||||
esc_html__( 'Ostatni import: %s', 'yacht-booking' ),
|
||||
esc_html( $ical_last_import )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php if ( $yacht ) : ?>
|
||||
<!-- iCal Export Feed URL -->
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e( 'Eksport iCal (feed)', 'yacht-booking' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
value="<?php echo esc_attr( $ical_feed_url ); ?>"
|
||||
class="large-text code"
|
||||
readonly
|
||||
onclick="this.select();"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Skopiuj ten link i dodaj go w Google Calendar (Inne kalendarze → Z adresu URL), aby rezerwacje z tego systemu pojawiły się w Twoim kalendarzu.', 'yacht-booking' ); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -18,14 +18,111 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
class Installer {
|
||||
|
||||
/**
|
||||
* Run installation
|
||||
* Run installation / upgrade.
|
||||
*
|
||||
* Wykonuje cleanup migration (raz, gdy upgrade z wersji < CURRENT)
|
||||
* przed nadpisaniem yacht_booking_version w `set_version()`.
|
||||
*/
|
||||
public function install() {
|
||||
$this->create_tables();
|
||||
$this->create_options();
|
||||
$this->migrate();
|
||||
$this->set_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup migration — usuwa martwe dane po wycofaniu OAuth + per-yacht iCal (09-03).
|
||||
*
|
||||
* Idempotencja: porównuje zapisaną wersję z `YACHT_BOOKING_VERSION`. Jeśli zapisana
|
||||
* jest >= bieżącej, cleanup nie uruchamia się (`set_version()` w install() nadpisuje).
|
||||
*
|
||||
* Cleanup kasuje:
|
||||
* - meta jachtów: _yacht_gcal_id, _yacht_ical_import_url, _yacht_ical_token, _yacht_ical_last_import
|
||||
* - opcje OAuth: yacht_booking_gcal_credentials, yacht_booking_gcal_tokens, yacht_booking_gcal_calendar_id, yacht_booking_gcal_sync_enabled
|
||||
* - rezerwacje z _booking_source = 'ical_import' (per-yacht — nie odświeżane już)
|
||||
* - stare crony OAuth + per-yacht iCal
|
||||
*
|
||||
* NIE kasuje:
|
||||
* - _yacht_gcal_alias (aktywny dla globalnego importu)
|
||||
* - rezerwacji 'ical_import_global' (aktywne) ani 'website' (rezerwacje klientów)
|
||||
*/
|
||||
private function migrate() {
|
||||
$installed_version = get_option( 'yacht_booking_version', '' );
|
||||
|
||||
// Pierwsza instalacja (brak wpisu) — nic do migracji.
|
||||
if ( '' === $installed_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already on current or newer version — nothing to migrate.
|
||||
if ( version_compare( $installed_version, YACHT_BOOKING_VERSION, '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete stale post meta across ALL posts.
|
||||
$stale_meta_keys = array(
|
||||
'_yacht_gcal_id',
|
||||
'_yacht_ical_import_url',
|
||||
'_yacht_ical_token',
|
||||
'_yacht_ical_last_import',
|
||||
);
|
||||
foreach ( $stale_meta_keys as $meta_key ) {
|
||||
delete_post_meta_by_key( $meta_key );
|
||||
}
|
||||
|
||||
// Delete stale options (OAuth credentials, tokens, calendar id, sync flag).
|
||||
$stale_options = array(
|
||||
'yacht_booking_gcal_credentials',
|
||||
'yacht_booking_gcal_tokens',
|
||||
'yacht_booking_gcal_calendar_id',
|
||||
'yacht_booking_gcal_sync_enabled',
|
||||
'yacht_booking_gcal_token',
|
||||
'yacht_booking_gcal_webhook_token',
|
||||
);
|
||||
foreach ( $stale_options as $option ) {
|
||||
delete_option( $option );
|
||||
}
|
||||
|
||||
// Delete bookings imported by old per-yacht iCal mechanism.
|
||||
$stale_bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'post_status' => 'any',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_booking_source',
|
||||
'value' => 'ical_import',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
if ( ! empty( $stale_bookings ) ) {
|
||||
// Ensure Availability class is loaded (Installer is invoked from activation hook
|
||||
// before main plugin bootstrap, so autoloader may not be active yet).
|
||||
if ( ! class_exists( '\YachtBooking\Availability' ) ) {
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'includes/class-availability.php';
|
||||
}
|
||||
foreach ( $stale_bookings as $booking_id ) {
|
||||
\YachtBooking\Availability::clear_booking_availability( $booking_id );
|
||||
wp_delete_post( $booking_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cron hooks for removed mechanisms.
|
||||
$stale_cron_hooks = array(
|
||||
'yacht_booking_ical_import', // per-yacht iCal import
|
||||
'yacht_booking_pull_from_gcal', // OAuth pull
|
||||
'yacht_booking_sync_to_gcal', // OAuth push
|
||||
'yacht_booking_update_in_gcal', // OAuth update
|
||||
'yacht_booking_delete_from_gcal', // OAuth delete
|
||||
);
|
||||
foreach ( $stale_cron_hooks as $hook ) {
|
||||
wp_clear_scheduled_hook( $hook );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom database tables
|
||||
*/
|
||||
@@ -67,7 +164,9 @@ class Installer {
|
||||
'yacht_booking_currency_symbol' => 'zł',
|
||||
'yacht_booking_terms_page_id' => 0,
|
||||
'yacht_booking_enable_notifications' => 'yes',
|
||||
'yacht_booking_gcal_sync_enabled' => 'no',
|
||||
'yacht_booking_global_ical_import_url' => '',
|
||||
'yacht_booking_global_ical_token' => '',
|
||||
'yacht_booking_global_ical_last_import' => '',
|
||||
);
|
||||
|
||||
foreach ( $options as $key => $value ) {
|
||||
|
||||
@@ -83,18 +83,7 @@ class Yacht_Booking {
|
||||
Admin::get_instance();
|
||||
}
|
||||
|
||||
// Load Google Calendar integration
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-oauth-handler.php';
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-gcal-service.php';
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-sync-controller.php';
|
||||
|
||||
// Initialize sync controller
|
||||
\YachtBooking\Integrations\GoogleCalendar\Sync_Controller::get_instance();
|
||||
|
||||
// Register cron actions
|
||||
\YachtBooking\Integrations\GoogleCalendar\Sync_Controller::register_cron_actions();
|
||||
|
||||
// Load iCal integration
|
||||
// Load iCal integration (globalna sync z 09-02 — jedyny mechanizm sync z GCal)
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/ical/class-ical-feed.php';
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/ical/class-ical-import.php';
|
||||
|
||||
|
||||
@@ -92,16 +92,6 @@ class Yacht {
|
||||
return (float) get_post_meta( $yacht_id, '_yacht_price_per_day', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get yacht Google Calendar ID
|
||||
*
|
||||
* @param int $yacht_id Yacht post ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_gcal_id( $yacht_id ) {
|
||||
return get_post_meta( $yacht_id, '_yacht_gcal_id', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get yacht features
|
||||
*
|
||||
@@ -134,13 +124,23 @@ class Yacht {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update yacht Google Calendar ID
|
||||
* Get yacht alias for Google Calendar global sync.
|
||||
*
|
||||
* @param int $yacht_id Yacht post ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_gcal_alias( $yacht_id ) {
|
||||
return (string) get_post_meta( $yacht_id, '_yacht_gcal_alias', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update yacht alias for Google Calendar global sync.
|
||||
*
|
||||
* @param int $yacht_id Yacht post ID.
|
||||
* @param string $gcal_id Google Calendar ID.
|
||||
* @param string $alias Alias.
|
||||
*/
|
||||
public static function update_gcal_id( $yacht_id, $gcal_id ) {
|
||||
update_post_meta( $yacht_id, '_yacht_gcal_id', sanitize_text_field( $gcal_id ) );
|
||||
public static function update_gcal_alias( $yacht_id, $alias ) {
|
||||
update_post_meta( $yacht_id, '_yacht_gcal_alias', sanitize_text_field( $alias ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,521 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Calendar Service
|
||||
*
|
||||
* @package YachtBooking
|
||||
*/
|
||||
|
||||
namespace YachtBooking\Integrations\GoogleCalendar;
|
||||
|
||||
use YachtBooking\Booking;
|
||||
use YachtBooking\Availability;
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Calendar Service class
|
||||
*/
|
||||
class GCal_Service {
|
||||
|
||||
/**
|
||||
* Google Calendar API base URL
|
||||
*/
|
||||
const API_BASE = 'https://www.googleapis.com/calendar/v3';
|
||||
|
||||
/**
|
||||
* Booking source meta value for imported Google events.
|
||||
*/
|
||||
const EXTERNAL_BOOKING_SOURCE = 'google_calendar_external';
|
||||
|
||||
/**
|
||||
* Create event in Google Calendar
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
* @return string|false Event ID or false on error.
|
||||
*/
|
||||
public static function create_event( $booking_id ) {
|
||||
$access_token = OAuth_Handler::get_access_token();
|
||||
$calendar_id = self::get_calendar_id();
|
||||
|
||||
if ( ! $access_token || ! $calendar_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get booking data
|
||||
$yacht_id = Booking::get_yacht_id( $booking_id );
|
||||
$yacht = get_post( $yacht_id );
|
||||
$start_date = Booking::get_start_date( $booking_id );
|
||||
$end_date = Booking::get_end_date( $booking_id );
|
||||
$customer_name = Booking::get_customer_name( $booking_id );
|
||||
$customer_email = Booking::get_customer_email( $booking_id );
|
||||
$customer_phone = Booking::get_customer_phone( $booking_id );
|
||||
$status = Booking::get_status( $booking_id );
|
||||
|
||||
// Prepare event data
|
||||
$event = array(
|
||||
'summary' => sprintf(
|
||||
/* translators: 1: yacht name, 2: customer name */
|
||||
__( 'Rezerwacja: %1$s - %2$s', 'yacht-booking' ),
|
||||
$yacht ? $yacht->post_title : __( 'Jacht', 'yacht-booking' ),
|
||||
$customer_name
|
||||
),
|
||||
'description' => sprintf(
|
||||
"Booking ID: #%d\n\nKlient: %s\nEmail: %s\nTelefon: %s\nStatus: %s\n\nZarządzanie: %s",
|
||||
$booking_id,
|
||||
$customer_name,
|
||||
$customer_email,
|
||||
$customer_phone,
|
||||
$status,
|
||||
admin_url( 'admin.php?page=yacht-bookings-list&booking_id=' . $booking_id )
|
||||
),
|
||||
'start' => array(
|
||||
'date' => $start_date, // All-day event
|
||||
),
|
||||
'end' => array(
|
||||
'date' => date( 'Y-m-d', strtotime( $end_date . ' +1 day' ) ), // Exclusive end date
|
||||
),
|
||||
'colorId' => 'confirmed' === $status ? '9' : '11', // Blue for confirmed, red for pending
|
||||
);
|
||||
|
||||
$response = wp_remote_post(
|
||||
self::API_BASE . '/calendars/' . urlencode( $calendar_id ) . '/events',
|
||||
array(
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
'body' => wp_json_encode( $event ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::log_error( 'Create event failed: ' . $response->get_error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( isset( $body['id'] ) ) {
|
||||
// Save Google Event ID to booking meta
|
||||
update_post_meta( $booking_id, '_gcal_event_id', $body['id'] );
|
||||
return $body['id'];
|
||||
}
|
||||
|
||||
self::log_error( 'Create event failed: ' . wp_remote_retrieve_body( $response ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event in Google Calendar
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function update_event( $booking_id ) {
|
||||
$access_token = OAuth_Handler::get_access_token();
|
||||
$calendar_id = self::get_calendar_id();
|
||||
$event_id = get_post_meta( $booking_id, '_gcal_event_id', true );
|
||||
|
||||
if ( ! $access_token || ! $calendar_id || ! $event_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get booking data
|
||||
$status = Booking::get_status( $booking_id );
|
||||
|
||||
// Update only color based on status
|
||||
$event = array(
|
||||
'colorId' => 'confirmed' === $status ? '9' : ( 'cancelled' === $status ? '8' : '11' ),
|
||||
);
|
||||
|
||||
$response = wp_remote_request(
|
||||
self::API_BASE . '/calendars/' . urlencode( $calendar_id ) . '/events/' . urlencode( $event_id ),
|
||||
array(
|
||||
'method' => 'PATCH',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
'body' => wp_json_encode( $event ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::log_error( 'Update event failed: ' . $response->get_error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete event from Google Calendar
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete_event( $booking_id ) {
|
||||
$access_token = OAuth_Handler::get_access_token();
|
||||
$calendar_id = self::get_calendar_id();
|
||||
$event_id = get_post_meta( $booking_id, '_gcal_event_id', true );
|
||||
|
||||
if ( ! $access_token || ! $calendar_id || ! $event_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_request(
|
||||
self::API_BASE . '/calendars/' . urlencode( $calendar_id ) . '/events/' . urlencode( $event_id ),
|
||||
array(
|
||||
'method' => 'DELETE',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::log_error( 'Delete event failed: ' . $response->get_error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove event ID from meta
|
||||
delete_post_meta( $booking_id, '_gcal_event_id' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync events from Google Calendar to WordPress
|
||||
*
|
||||
* @param int $yacht_id Yacht post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function sync_from_gcal( $yacht_id ) {
|
||||
$access_token = OAuth_Handler::get_access_token();
|
||||
$calendar_id = self::get_calendar_id();
|
||||
|
||||
if ( ! $access_token || ! $calendar_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch events from now to +1 year
|
||||
$time_min = gmdate( 'Y-m-d\TH:i:s\Z' );
|
||||
$time_max = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '+1 year' ) );
|
||||
|
||||
$response = wp_remote_get(
|
||||
add_query_arg(
|
||||
array(
|
||||
'timeMin' => $time_min,
|
||||
'timeMax' => $time_max,
|
||||
'singleEvents' => 'true',
|
||||
'orderBy' => 'startTime',
|
||||
),
|
||||
self::API_BASE . '/calendars/' . urlencode( $calendar_id ) . '/events'
|
||||
),
|
||||
array(
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::log_error( 'Sync from GCal failed: ' . $response->get_error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$events = isset( $body['items'] ) ? $body['items'] : array();
|
||||
|
||||
// Internal bookings (created in WordPress and pushed to Google) should not be re-imported.
|
||||
$internal_event_ids = self::get_internal_booking_event_ids();
|
||||
$external_booking_map = self::get_external_booking_map( $yacht_id );
|
||||
$seen_external_ids = array();
|
||||
|
||||
foreach ( $events as $event ) {
|
||||
if ( empty( $event['id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip events already linked to internal WordPress bookings.
|
||||
if ( in_array( $event['id'], $internal_event_ids, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$range = self::get_blocked_range_from_event( $event );
|
||||
if ( ! $range ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$booking_id = isset( $external_booking_map[ $event['id'] ] ) ? (int) $external_booking_map[ $event['id'] ] : 0;
|
||||
$booking_id = self::upsert_external_booking( $yacht_id, $event, $range, $booking_id );
|
||||
|
||||
if ( $booking_id ) {
|
||||
$seen_external_ids[] = $event['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale imported placeholders (event removed from Google Calendar).
|
||||
foreach ( $external_booking_map as $event_id => $booking_id ) {
|
||||
if ( ! in_array( $event_id, $seen_external_ids, true ) ) {
|
||||
Availability::clear_booking_availability( $booking_id );
|
||||
// Prevent before_delete_post Google delete call for already-removed external events.
|
||||
delete_post_meta( $booking_id, '_gcal_event_id' );
|
||||
wp_delete_post( $booking_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calendar ID
|
||||
*
|
||||
* @return string|false Calendar ID or false.
|
||||
*/
|
||||
public static function get_calendar_id() {
|
||||
$calendar_id = get_option( 'yacht_booking_gcal_calendar_id' );
|
||||
|
||||
// Default to primary calendar if not set
|
||||
return $calendar_id ? $calendar_id : 'primary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set calendar ID
|
||||
*
|
||||
* @param string $calendar_id Calendar ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function set_calendar_id( $calendar_id ) {
|
||||
return update_option( 'yacht_booking_gcal_calendar_id', $calendar_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of user's calendars
|
||||
*
|
||||
* @return array|false Array of calendars or false on error.
|
||||
*/
|
||||
public static function get_calendar_list() {
|
||||
$access_token = OAuth_Handler::get_access_token();
|
||||
|
||||
if ( ! $access_token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
self::API_BASE . '/users/me/calendarList',
|
||||
array(
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return isset( $body['items'] ) ? $body['items'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error
|
||||
*
|
||||
* @param string $message Error message.
|
||||
*/
|
||||
private static function log_error( $message ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
error_log( '[Yacht Booking - GCal] ' . $message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Google Calendar event to blocked date range.
|
||||
*
|
||||
* IMPORTANT: Availability::mark_as_blocked() expects end date to be exclusive.
|
||||
*
|
||||
* @param array $event Google Calendar event payload.
|
||||
* @return array|false
|
||||
*/
|
||||
private static function get_blocked_range_from_event( $event ) {
|
||||
// Skip cancelled events.
|
||||
if ( isset( $event['status'] ) && 'cancelled' === $event['status'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip "free" events (Show as: Available).
|
||||
if ( isset( $event['transparency'] ) && 'transparent' === $event['transparency'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All-day events: end date from Google is already exclusive.
|
||||
if ( isset( $event['start']['date'] ) && isset( $event['end']['date'] ) ) {
|
||||
$start_date = $event['start']['date'];
|
||||
$end_date = $event['end']['date'];
|
||||
|
||||
if ( strtotime( $end_date ) <= strtotime( $start_date ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array(
|
||||
'start_date' => $start_date,
|
||||
'end_date' => $end_date,
|
||||
);
|
||||
}
|
||||
|
||||
// Timed events: convert to full occupied-day range.
|
||||
if ( isset( $event['start']['dateTime'] ) && isset( $event['end']['dateTime'] ) ) {
|
||||
$start_ts = strtotime( $event['start']['dateTime'] );
|
||||
$end_ts = strtotime( $event['end']['dateTime'] );
|
||||
|
||||
if ( false === $start_ts || false === $end_ts || $end_ts <= $start_ts ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_date = gmdate( 'Y-m-d', $start_ts );
|
||||
|
||||
// Last occupied second to avoid midnight edge-cases.
|
||||
$last_occupied_ts = $end_ts - 1;
|
||||
$last_occupied = gmdate( 'Y-m-d', $last_occupied_ts );
|
||||
$end_date = gmdate( 'Y-m-d', strtotime( $last_occupied . ' +1 day' ) );
|
||||
|
||||
if ( strtotime( $end_date ) <= strtotime( $start_date ) ) {
|
||||
$end_date = gmdate( 'Y-m-d', strtotime( $start_date . ' +1 day' ) );
|
||||
}
|
||||
|
||||
return array(
|
||||
'start_date' => $start_date,
|
||||
'end_date' => $end_date,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Google event IDs that belong to internal bookings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_internal_booking_event_ids() {
|
||||
global $wpdb;
|
||||
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT event_pm.meta_value
|
||||
FROM {$wpdb->postmeta} event_pm
|
||||
INNER JOIN {$wpdb->posts} p ON p.ID = event_pm.post_id
|
||||
LEFT JOIN {$wpdb->postmeta} source_pm
|
||||
ON source_pm.post_id = event_pm.post_id
|
||||
AND source_pm.meta_key = '_booking_source'
|
||||
WHERE event_pm.meta_key = '_gcal_event_id'
|
||||
AND p.post_type = 'yacht_booking'
|
||||
AND p.post_status = 'publish'
|
||||
AND (source_pm.meta_value IS NULL OR source_pm.meta_value != %s)",
|
||||
self::EXTERNAL_BOOKING_SOURCE
|
||||
);
|
||||
|
||||
$ids = $wpdb->get_col( $sql );
|
||||
|
||||
return is_array( $ids ) ? $ids : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get imported external booking map for yacht: event_id => booking_id.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return array
|
||||
*/
|
||||
private static function get_external_booking_map( $yacht_id ) {
|
||||
$bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_booking_source',
|
||||
'value' => self::EXTERNAL_BOOKING_SOURCE,
|
||||
),
|
||||
array(
|
||||
'key' => '_booking_yacht_id',
|
||||
'value' => (int) $yacht_id,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$map = array();
|
||||
foreach ( $bookings as $booking_id ) {
|
||||
$event_id = get_post_meta( $booking_id, '_gcal_event_id', true );
|
||||
if ( ! empty( $event_id ) ) {
|
||||
$map[ $event_id ] = (int) $booking_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update imported external booking placeholder.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @param array $event Google event payload.
|
||||
* @param array $range Date range (exclusive end).
|
||||
* @param int $existing_booking_id Existing booking ID.
|
||||
* @return int|false
|
||||
*/
|
||||
private static function upsert_external_booking( $yacht_id, $event, $range, $existing_booking_id = 0 ) {
|
||||
$summary = isset( $event['summary'] ) && '' !== trim( $event['summary'] ) ? sanitize_text_field( $event['summary'] ) : __( 'Wydarzenie z Google Calendar', 'yacht-booking' );
|
||||
$start_date = $range['start_date'];
|
||||
$end_date = $range['end_date'];
|
||||
$event_id = $event['id'];
|
||||
$event_url = isset( $event['htmlLink'] ) ? esc_url_raw( $event['htmlLink'] ) : '';
|
||||
|
||||
$post_data = array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'post_title' => sprintf(
|
||||
/* translators: %s: Google event summary */
|
||||
__( 'Blokada Google Calendar: %s', 'yacht-booking' ),
|
||||
$summary
|
||||
),
|
||||
);
|
||||
|
||||
if ( $existing_booking_id > 0 ) {
|
||||
$post_data['ID'] = $existing_booking_id;
|
||||
$booking_id = wp_update_post( $post_data, true );
|
||||
} else {
|
||||
$booking_id = wp_insert_post( $post_data, true );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $booking_id ) || ! $booking_id ) {
|
||||
self::log_error( 'Failed to upsert external booking for event: ' . $event_id );
|
||||
return false;
|
||||
}
|
||||
|
||||
update_post_meta( $booking_id, '_booking_yacht_id', (int) $yacht_id );
|
||||
update_post_meta( $booking_id, '_booking_start_date', $start_date );
|
||||
update_post_meta( $booking_id, '_booking_end_date', $end_date );
|
||||
update_post_meta( $booking_id, '_booking_status', 'confirmed' );
|
||||
update_post_meta( $booking_id, '_booking_customer_name', __( 'Google Calendar (import)', 'yacht-booking' ) );
|
||||
update_post_meta( $booking_id, '_booking_customer_email', '' );
|
||||
update_post_meta( $booking_id, '_booking_customer_phone', '' );
|
||||
update_post_meta( $booking_id, '_booking_total_price', 0 );
|
||||
update_post_meta( $booking_id, '_booking_source', self::EXTERNAL_BOOKING_SOURCE );
|
||||
update_post_meta( $booking_id, '_gcal_event_id', $event_id );
|
||||
update_post_meta( $booking_id, '_booking_notes', $event_url );
|
||||
|
||||
// Rebuild availability tied to this imported booking.
|
||||
Availability::clear_booking_availability( $booking_id );
|
||||
Availability::mark_as_booked( $yacht_id, $start_date, $end_date, $booking_id );
|
||||
|
||||
return (int) $booking_id;
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Calendar OAuth Handler
|
||||
*
|
||||
* @package YachtBooking
|
||||
*/
|
||||
|
||||
namespace YachtBooking\Integrations\GoogleCalendar;
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth Handler class
|
||||
*/
|
||||
class OAuth_Handler {
|
||||
|
||||
/**
|
||||
* Google OAuth endpoints
|
||||
*/
|
||||
const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||
const TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
||||
const SCOPE = 'https://www.googleapis.com/auth/calendar';
|
||||
|
||||
/**
|
||||
* Get OAuth authorization URL
|
||||
*
|
||||
* @return string|false Authorization URL or false on error.
|
||||
*/
|
||||
public static function get_auth_url() {
|
||||
$credentials = self::get_credentials();
|
||||
|
||||
if ( ! $credentials ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force production URL for Google OAuth
|
||||
$redirect_uri = 'https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1';
|
||||
|
||||
// Build URL manually to ensure proper encoding
|
||||
$params = array(
|
||||
'client_id' => $credentials['client_id'],
|
||||
'redirect_uri' => $redirect_uri,
|
||||
'response_type' => 'code',
|
||||
'scope' => self::SCOPE,
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'consent',
|
||||
);
|
||||
|
||||
// Build query string with proper URL encoding
|
||||
$query = http_build_query( $params, '', '&' );
|
||||
|
||||
return self::AUTH_URL . '?' . $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange authorization code for access token
|
||||
*
|
||||
* @param string $code Authorization code.
|
||||
* @return array|false Token data or false on error.
|
||||
*/
|
||||
public static function authenticate( $code ) {
|
||||
$credentials = self::get_credentials();
|
||||
|
||||
if ( ! $credentials ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force production URL for Google OAuth
|
||||
$redirect_uri = 'https://jachty.pagedev.pl/wp-admin/admin.php?page=yacht-bookings-settings&tab=google-calendar&gcal_callback=1';
|
||||
|
||||
$response = wp_remote_post(
|
||||
self::TOKEN_URL,
|
||||
array(
|
||||
'body' => array(
|
||||
'code' => $code,
|
||||
'client_id' => $credentials['client_id'],
|
||||
'client_secret' => $credentials['client_secret'],
|
||||
'redirect_uri' => $redirect_uri,
|
||||
'grant_type' => 'authorization_code',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( isset( $body['access_token'] ) ) {
|
||||
// Save tokens
|
||||
self::save_tokens( $body );
|
||||
return $body;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token (refresh if expired)
|
||||
*
|
||||
* @return string|false Access token or false on error.
|
||||
*/
|
||||
public static function get_access_token() {
|
||||
$tokens = get_option( 'yacht_booking_gcal_tokens' );
|
||||
|
||||
if ( ! $tokens || ! isset( $tokens['access_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if token is expired
|
||||
$expires_at = isset( $tokens['expires_at'] ) ? $tokens['expires_at'] : 0;
|
||||
|
||||
if ( time() >= $expires_at ) {
|
||||
// Token expired, refresh it
|
||||
$new_tokens = self::refresh_access_token();
|
||||
|
||||
if ( ! $new_tokens ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $new_tokens['access_token'];
|
||||
}
|
||||
|
||||
return $tokens['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token using refresh token
|
||||
*
|
||||
* @return array|false New token data or false on error.
|
||||
*/
|
||||
public static function refresh_access_token() {
|
||||
$credentials = self::get_credentials();
|
||||
$tokens = get_option( 'yacht_booking_gcal_tokens' );
|
||||
|
||||
if ( ! $credentials || ! $tokens || ! isset( $tokens['refresh_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_post(
|
||||
self::TOKEN_URL,
|
||||
array(
|
||||
'body' => array(
|
||||
'client_id' => $credentials['client_id'],
|
||||
'client_secret' => $credentials['client_secret'],
|
||||
'refresh_token' => $tokens['refresh_token'],
|
||||
'grant_type' => 'refresh_token',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( isset( $body['access_token'] ) ) {
|
||||
// Preserve refresh_token (not always returned)
|
||||
if ( ! isset( $body['refresh_token'] ) && isset( $tokens['refresh_token'] ) ) {
|
||||
$body['refresh_token'] = $tokens['refresh_token'];
|
||||
}
|
||||
|
||||
self::save_tokens( $body );
|
||||
return $body;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save OAuth credentials
|
||||
*
|
||||
* @param array $credentials Credentials array.
|
||||
* @return bool
|
||||
*/
|
||||
public static function save_credentials( $credentials ) {
|
||||
if ( ! isset( $credentials['client_id'] ) || ! isset( $credentials['client_secret'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return update_option( 'yacht_booking_gcal_credentials', $credentials );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved credentials
|
||||
*
|
||||
* @return array|false Credentials or false.
|
||||
*/
|
||||
public static function get_credentials() {
|
||||
$credentials = get_option( 'yacht_booking_gcal_credentials' );
|
||||
|
||||
if ( ! $credentials || ! isset( $credentials['client_id'] ) || ! isset( $credentials['client_secret'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save OAuth tokens
|
||||
*
|
||||
* @param array $tokens Token data.
|
||||
*/
|
||||
private static function save_tokens( $tokens ) {
|
||||
// Calculate expiry time
|
||||
if ( isset( $tokens['expires_in'] ) ) {
|
||||
$tokens['expires_at'] = time() + (int) $tokens['expires_in'];
|
||||
}
|
||||
|
||||
update_option( 'yacht_booking_gcal_tokens', $tokens );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all Google Calendar data (disconnect)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function disconnect() {
|
||||
delete_option( 'yacht_booking_gcal_credentials' );
|
||||
delete_option( 'yacht_booking_gcal_tokens' );
|
||||
delete_option( 'yacht_booking_gcal_calendar_id' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected to Google Calendar
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_connected() {
|
||||
$tokens = get_option( 'yacht_booking_gcal_tokens' );
|
||||
return ! empty( $tokens ) && isset( $tokens['access_token'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connected email (from token info)
|
||||
*
|
||||
* @return string|false Email or false.
|
||||
*/
|
||||
public static function get_connected_email() {
|
||||
$access_token = self::get_access_token();
|
||||
|
||||
if ( ! $access_token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
'https://www.googleapis.com/oauth2/v2/userinfo',
|
||||
array(
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return isset( $body['email'] ) ? $body['email'] : false;
|
||||
}
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Calendar Sync Controller
|
||||
*
|
||||
* @package YachtBooking
|
||||
*/
|
||||
|
||||
namespace YachtBooking\Integrations\GoogleCalendar;
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Controller class
|
||||
*
|
||||
* Handles automatic synchronization between WordPress bookings and Google Calendar
|
||||
*/
|
||||
class Sync_Controller {
|
||||
|
||||
/**
|
||||
* Instance
|
||||
*
|
||||
* @var Sync_Controller
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @return Sync_Controller
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
// Hook into booking events
|
||||
add_action( 'yacht_booking_created', array( $this, 'on_booking_created' ), 20, 1 );
|
||||
add_action( 'yacht_booking_status_changed', array( $this, 'on_booking_status_changed' ), 20, 2 );
|
||||
add_action( 'before_delete_post', array( $this, 'on_booking_deleted' ), 10, 1 );
|
||||
|
||||
// AJAX handler for manual sync
|
||||
add_action( 'wp_ajax_yacht_booking_manual_sync', array( $this, 'ajax_manual_sync' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle booking created
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
*/
|
||||
public function on_booking_created( $booking_id ) {
|
||||
// Check if connected
|
||||
if ( ! OAuth_Handler::is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule async sync (avoid blocking the request)
|
||||
wp_schedule_single_event(
|
||||
time(),
|
||||
'yacht_booking_sync_to_gcal',
|
||||
array( $booking_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle booking status changed
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
* @param string $new_status New status.
|
||||
*/
|
||||
public function on_booking_status_changed( $booking_id, $new_status ) {
|
||||
// Check if connected
|
||||
if ( ! OAuth_Handler::is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_id = get_post_meta( $booking_id, '_gcal_event_id', true );
|
||||
|
||||
if ( 'cancelled' === $new_status && $event_id ) {
|
||||
// Delete event from Google Calendar
|
||||
wp_schedule_single_event(
|
||||
time(),
|
||||
'yacht_booking_delete_from_gcal',
|
||||
array( $booking_id )
|
||||
);
|
||||
} elseif ( 'confirmed' === $new_status ) {
|
||||
if ( $event_id ) {
|
||||
// Update existing event
|
||||
wp_schedule_single_event(
|
||||
time(),
|
||||
'yacht_booking_update_in_gcal',
|
||||
array( $booking_id )
|
||||
);
|
||||
} else {
|
||||
// Create new event
|
||||
wp_schedule_single_event(
|
||||
time(),
|
||||
'yacht_booking_sync_to_gcal',
|
||||
array( $booking_id )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle booking deleted
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function on_booking_deleted( $post_id ) {
|
||||
// Check if it's a booking
|
||||
if ( get_post_type( $post_id ) !== 'yacht_booking' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if connected
|
||||
if ( ! OAuth_Handler::is_connected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_id = get_post_meta( $post_id, '_gcal_event_id', true );
|
||||
|
||||
if ( $event_id ) {
|
||||
// Delete event from Google Calendar (sync now, before post is deleted)
|
||||
GCal_Service::delete_event( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cron actions
|
||||
*/
|
||||
public static function register_cron_actions() {
|
||||
// Sync to Google Calendar
|
||||
add_action( 'yacht_booking_sync_to_gcal', array( __CLASS__, 'sync_booking_to_gcal' ) );
|
||||
|
||||
// Update in Google Calendar
|
||||
add_action( 'yacht_booking_update_in_gcal', array( __CLASS__, 'update_booking_in_gcal' ) );
|
||||
|
||||
// Delete from Google Calendar
|
||||
add_action( 'yacht_booking_delete_from_gcal', array( __CLASS__, 'delete_booking_from_gcal' ) );
|
||||
|
||||
// Pull from Google Calendar (hourly)
|
||||
add_action( 'yacht_booking_pull_from_gcal', array( __CLASS__, 'pull_from_gcal' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync booking to Google Calendar (cron action)
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
*/
|
||||
public static function sync_booking_to_gcal( $booking_id ) {
|
||||
$event_id = GCal_Service::create_event( $booking_id );
|
||||
|
||||
if ( $event_id ) {
|
||||
self::log( sprintf( 'Booking #%d synced to Google Calendar (Event ID: %s)', $booking_id, $event_id ) );
|
||||
} else {
|
||||
self::log( sprintf( 'Failed to sync booking #%d to Google Calendar', $booking_id ), 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update booking in Google Calendar (cron action)
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
*/
|
||||
public static function update_booking_in_gcal( $booking_id ) {
|
||||
$result = GCal_Service::update_event( $booking_id );
|
||||
|
||||
if ( $result ) {
|
||||
self::log( sprintf( 'Booking #%d updated in Google Calendar', $booking_id ) );
|
||||
} else {
|
||||
self::log( sprintf( 'Failed to update booking #%d in Google Calendar', $booking_id ), 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete booking from Google Calendar (cron action)
|
||||
*
|
||||
* @param int $booking_id Booking post ID.
|
||||
*/
|
||||
public static function delete_booking_from_gcal( $booking_id ) {
|
||||
$result = GCal_Service::delete_event( $booking_id );
|
||||
|
||||
if ( $result ) {
|
||||
self::log( sprintf( 'Booking #%d deleted from Google Calendar', $booking_id ) );
|
||||
} else {
|
||||
self::log( sprintf( 'Failed to delete booking #%d from Google Calendar', $booking_id ), 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull events from Google Calendar (cron action)
|
||||
*/
|
||||
public static function pull_from_gcal() {
|
||||
// Get all yachts
|
||||
$yachts = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht',
|
||||
'posts_per_page' => -1,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $yachts as $yacht ) {
|
||||
$result = GCal_Service::sync_from_gcal( $yacht->ID );
|
||||
|
||||
if ( $result ) {
|
||||
self::log( sprintf( 'Synced events from Google Calendar for yacht #%d', $yacht->ID ) );
|
||||
} else {
|
||||
self::log( sprintf( 'Failed to sync events from Google Calendar for yacht #%d', $yacht->ID ), 'error' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup hourly cron job for pulling from Google Calendar
|
||||
*/
|
||||
public static function setup_cron() {
|
||||
if ( ! wp_next_scheduled( 'yacht_booking_pull_from_gcal' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'yacht_booking_pull_from_gcal' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cron jobs
|
||||
*/
|
||||
public static function clear_cron() {
|
||||
wp_clear_scheduled_hook( 'yacht_booking_pull_from_gcal' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
*
|
||||
* @param string $message Message.
|
||||
* @param string $type Log type (info|error).
|
||||
*/
|
||||
private static function log( $message, $type = 'info' ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
$prefix = 'error' === $type ? 'ERROR' : 'INFO';
|
||||
error_log( sprintf( '[Yacht Booking - GCal Sync] [%s] %s', $prefix, $message ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for manual sync
|
||||
*/
|
||||
public function ajax_manual_sync() {
|
||||
// Check nonce
|
||||
check_ajax_referer( 'yacht_booking_manual_sync', 'nonce' );
|
||||
|
||||
// Check capabilities
|
||||
if ( ! current_user_can( 'yacht_booking_manage_settings' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Brak uprawnień', 'yacht-booking' ) ) );
|
||||
}
|
||||
|
||||
// Check if connected
|
||||
if ( ! OAuth_Handler::is_connected() ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Nie połączono z Google Calendar', 'yacht-booking' ) ) );
|
||||
}
|
||||
|
||||
// Statistics
|
||||
$yachts_synced = 0;
|
||||
$bookings_pushed = 0;
|
||||
$bookings_skipped = 0;
|
||||
$errors = array();
|
||||
|
||||
// STEP 1: Push WordPress bookings to Google Calendar
|
||||
$bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $bookings as $booking ) {
|
||||
$event_id = get_post_meta( $booking->ID, '_gcal_event_id', true );
|
||||
$status = get_post_meta( $booking->ID, '_booking_status', true );
|
||||
$source = get_post_meta( $booking->ID, '_booking_source', true );
|
||||
|
||||
// Never push imported Google placeholders back to Google.
|
||||
if ( GCal_Service::EXTERNAL_BOOKING_SOURCE === $source ) {
|
||||
$bookings_skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip cancelled bookings
|
||||
if ( 'cancelled' === $status ) {
|
||||
$bookings_skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only push if not already in Google Calendar
|
||||
if ( empty( $event_id ) ) {
|
||||
$result = GCal_Service::create_event( $booking->ID );
|
||||
|
||||
if ( $result ) {
|
||||
$bookings_pushed++;
|
||||
self::log( sprintf( 'Manual sync: Pushed booking #%d to Google Calendar', $booking->ID ) );
|
||||
} else {
|
||||
self::log( sprintf( 'Manual sync: Failed to push booking #%d to Google Calendar', $booking->ID ), 'error' );
|
||||
}
|
||||
} else {
|
||||
$bookings_skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 2: Pull external events from Google Calendar
|
||||
$yachts = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht',
|
||||
'posts_per_page' => -1,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $yachts as $yacht ) {
|
||||
$result = GCal_Service::sync_from_gcal( $yacht->ID );
|
||||
|
||||
if ( $result ) {
|
||||
$yachts_synced++;
|
||||
self::log( sprintf( 'Manual sync: Pulled events from Google Calendar for yacht #%d', $yacht->ID ) );
|
||||
} else {
|
||||
$errors[] = sprintf( __( 'Błąd synchronizacji dla jachtu: %s', 'yacht-booking' ), $yacht->post_title );
|
||||
self::log( sprintf( 'Manual sync: Failed to pull events from Google Calendar for yacht #%d', $yacht->ID ), 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
// Build success message
|
||||
$messages = array();
|
||||
|
||||
if ( $bookings_pushed > 0 ) {
|
||||
$messages[] = sprintf(
|
||||
/* translators: %d: number of bookings pushed */
|
||||
_n( 'Wysłano %d rezerwację do Google Calendar', 'Wysłano %d rezerwacji do Google Calendar', $bookings_pushed, 'yacht-booking' ),
|
||||
$bookings_pushed
|
||||
);
|
||||
}
|
||||
|
||||
if ( $bookings_skipped > 0 ) {
|
||||
$messages[] = sprintf(
|
||||
/* translators: %d: number of bookings skipped */
|
||||
_n( 'Pominięto %d rezerwację (już zsynchronizowana lub anulowana)', 'Pominięto %d rezerwacji (już zsynchronizowane lub anulowane)', $bookings_skipped, 'yacht-booking' ),
|
||||
$bookings_skipped
|
||||
);
|
||||
}
|
||||
|
||||
if ( $yachts_synced > 0 ) {
|
||||
$messages[] = sprintf(
|
||||
/* translators: %d: number of yachts synced */
|
||||
_n( 'Pobrano wydarzenia dla %d jachtu z Google Calendar', 'Pobrano wydarzenia dla %d jachtów z Google Calendar', $yachts_synced, 'yacht-booking' ),
|
||||
$yachts_synced
|
||||
);
|
||||
}
|
||||
|
||||
if ( count( $errors ) > 0 ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => implode( '<br>', $errors ),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => implode( '<br>', $messages ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,18 +32,18 @@ class ICal_Feed {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add rewrite rule for ical feed
|
||||
* Add rewrite rule for global ical feed
|
||||
*/
|
||||
public static function add_rewrite_rules() {
|
||||
add_rewrite_rule(
|
||||
'^yacht-ical/([0-9]+)/([a-zA-Z0-9]+)\.ics$',
|
||||
'index.php?yacht_ical_id=$matches[1]&yacht_ical_token=$matches[2]',
|
||||
'^yacht-ical-global/([a-zA-Z0-9]+)\.ics$',
|
||||
'index.php?yacht_ical_global=1&yacht_ical_token=$matches[1]',
|
||||
'top'
|
||||
);
|
||||
|
||||
// Flush rewrite rules if our rule is not registered yet.
|
||||
$rules = get_option( 'rewrite_rules' );
|
||||
if ( is_array( $rules ) && ! isset( $rules['^yacht-ical/([0-9]+)/([a-zA-Z0-9]+)\.ics$'] ) ) {
|
||||
if ( is_array( $rules ) && ! isset( $rules['^yacht-ical-global/([a-zA-Z0-9]+)\.ics$'] ) ) {
|
||||
flush_rewrite_rules( false );
|
||||
}
|
||||
}
|
||||
@@ -55,94 +55,82 @@ class ICal_Feed {
|
||||
* @return array
|
||||
*/
|
||||
public static function add_query_vars( $vars ) {
|
||||
$vars[] = 'yacht_ical_id';
|
||||
$vars[] = 'yacht_ical_token';
|
||||
$vars[] = 'yacht_ical_global';
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle feed request
|
||||
* Handle feed request — globalny feed iCal (jeden plik dla całej floty).
|
||||
*/
|
||||
public static function handle_feed_request() {
|
||||
$yacht_id = (int) get_query_var( 'yacht_ical_id', 0 );
|
||||
$token = get_query_var( 'yacht_ical_token', '' );
|
||||
$is_global = (int) get_query_var( 'yacht_ical_global', 0 );
|
||||
$token = get_query_var( 'yacht_ical_token', '' );
|
||||
|
||||
if ( ! $yacht_id || ! $token ) {
|
||||
if ( ! $is_global || ! $token ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$yacht = get_post( $yacht_id );
|
||||
if ( ! $yacht || 'yacht' !== $yacht->post_type ) {
|
||||
status_header( 404 );
|
||||
exit;
|
||||
}
|
||||
|
||||
$stored_token = self::get_feed_token( $yacht_id );
|
||||
$stored_token = self::get_global_feed_token();
|
||||
if ( ! $stored_token || ! hash_equals( $stored_token, $token ) ) {
|
||||
status_header( 403 );
|
||||
exit;
|
||||
}
|
||||
|
||||
self::output_ics( $yacht );
|
||||
self::output_global_ics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create feed token for a yacht
|
||||
* Get or create global feed token (one shared token for all-yachts feed).
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_feed_token( $yacht_id ) {
|
||||
$token = get_post_meta( $yacht_id, '_yacht_ical_token', true );
|
||||
public static function get_global_feed_token() {
|
||||
$token = get_option( 'yacht_booking_global_ical_token', '' );
|
||||
|
||||
if ( empty( $token ) ) {
|
||||
$token = wp_generate_password( 24, false );
|
||||
update_post_meta( $yacht_id, '_yacht_ical_token', $token );
|
||||
update_option( 'yacht_booking_global_ical_token', $token );
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate feed token
|
||||
* Regenerate global feed token (invalidates the previous URL).
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function regenerate_token( $yacht_id ) {
|
||||
public static function regenerate_global_token() {
|
||||
$token = wp_generate_password( 24, false );
|
||||
update_post_meta( $yacht_id, '_yacht_ical_token', $token );
|
||||
update_option( 'yacht_booking_global_ical_token', $token );
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feed URL for a yacht
|
||||
* Get global feed URL (all yachts in one .ics).
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_feed_url( $yacht_id ) {
|
||||
$token = self::get_feed_token( $yacht_id );
|
||||
return home_url( sprintf( '/yacht-ical/%d/%s.ics', $yacht_id, $token ) );
|
||||
public static function get_global_feed_url() {
|
||||
$token = self::get_global_feed_token();
|
||||
return home_url( sprintf( '/yacht-ical-global/%s.ics', $token ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output .ics file
|
||||
* Output global .ics feed — wszystkie jachty w jednym pliku.
|
||||
*
|
||||
* @param \WP_Post $yacht Yacht post.
|
||||
* Każdy event ma SUMMARY w formacie "{nazwa_jachtu} - {klient}".
|
||||
* Eventy z _booking_source = 'ical_import_global' są pomijane (anti-loop —
|
||||
* nie wysyłamy z powrotem do Google tego, co stamtąd przyszło).
|
||||
*/
|
||||
private static function output_ics( $yacht ) {
|
||||
private static function output_global_ics() {
|
||||
$bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'publish',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_booking_yacht_id',
|
||||
'value' => $yacht->ID,
|
||||
),
|
||||
array(
|
||||
'key' => '_booking_status',
|
||||
'value' => 'cancelled',
|
||||
@@ -156,7 +144,7 @@ class ICal_Feed {
|
||||
$domain = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
|
||||
header( 'Content-Type: text/calendar; charset=utf-8' );
|
||||
header( 'Content-Disposition: inline; filename="' . sanitize_file_name( $yacht->post_title ) . '.ics"' );
|
||||
header( 'Content-Disposition: inline; filename="yachts-all.ics"' );
|
||||
header( 'Cache-Control: no-cache, must-revalidate' );
|
||||
|
||||
$lines = array();
|
||||
@@ -165,10 +153,32 @@ class ICal_Feed {
|
||||
$lines[] = 'PRODID:-//YachtBooking//NONSGML v1.0//PL';
|
||||
$lines[] = 'CALSCALE:GREGORIAN';
|
||||
$lines[] = 'METHOD:PUBLISH';
|
||||
$lines[] = 'X-WR-CALNAME:' . self::escape_ical( $yacht->post_title . ' - ' . $site_name );
|
||||
$lines[] = 'X-WR-CALNAME:' . self::escape_ical(
|
||||
sprintf(
|
||||
/* translators: %s: site name */
|
||||
__( 'Wszystkie jachty - %s', 'yacht-booking' ),
|
||||
$site_name
|
||||
)
|
||||
);
|
||||
$lines[] = 'X-WR-TIMEZONE:Europe/Warsaw';
|
||||
|
||||
foreach ( $bookings as $booking ) {
|
||||
// Anti-loop: pomiń eventy które zostały zaimportowane z globalnego GCal.
|
||||
$source = get_post_meta( $booking->ID, '_booking_source', true );
|
||||
if ( ICal_Import::GLOBAL_IMPORT_SOURCE === $source ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$yacht_id = (int) Booking::get_yacht_id( $booking->ID );
|
||||
if ( ! $yacht_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$yacht = get_post( $yacht_id );
|
||||
if ( ! $yacht ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = Booking::get_start_date( $booking->ID );
|
||||
$end = Booking::get_end_date( $booking->ID );
|
||||
$status = Booking::get_status( $booking->ID );
|
||||
@@ -178,10 +188,11 @@ class ICal_Feed {
|
||||
continue;
|
||||
}
|
||||
|
||||
// iCal DTEND for all-day events is exclusive
|
||||
// iCal DTEND for all-day events is exclusive.
|
||||
$end_exclusive = gmdate( 'Ymd', strtotime( $end . ' +1 day' ) );
|
||||
$created = get_the_date( 'Ymd\THis\Z', $booking );
|
||||
|
||||
// Prefiks nazwy jachtu — kluczowy dla późniejszego importu po prefiksie.
|
||||
$summary = sprintf( '%s - %s', $yacht->post_title, $name );
|
||||
if ( 'pending' === $status ) {
|
||||
$summary = '[' . __( 'Oczekująca', 'yacht-booking' ) . '] ' . $summary;
|
||||
|
||||
@@ -24,23 +24,31 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
class ICal_Import {
|
||||
|
||||
/**
|
||||
* Booking source identifier for iCal imports.
|
||||
* Booking source identifier for iCal imports (globalny — wspólny GCal,
|
||||
* podział po prefiksie nazwy jachtu w SUMMARY).
|
||||
*/
|
||||
const IMPORT_SOURCE = 'ical_import';
|
||||
const GLOBAL_IMPORT_SOURCE = 'ical_import_global';
|
||||
|
||||
/**
|
||||
* Separator między prefiksem nazwy jachtu a resztą tytułu eventu.
|
||||
*
|
||||
* Przykład SUMMARY: "Maja - Kowalski 5 osób" → prefix="Maja", reszta="Kowalski 5 osób".
|
||||
*/
|
||||
const SUMMARY_SEPARATOR = ' - ';
|
||||
|
||||
/**
|
||||
* Register cron actions
|
||||
*/
|
||||
public static function register() {
|
||||
add_action( 'yacht_booking_ical_import', array( __CLASS__, 'run_import' ) );
|
||||
add_action( 'yacht_booking_ical_global_import', array( __CLASS__, 'run_global_import' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup cron schedule
|
||||
*/
|
||||
public static function setup_cron() {
|
||||
if ( ! wp_next_scheduled( 'yacht_booking_ical_import' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'yacht_booking_ical_import' );
|
||||
if ( ! wp_next_scheduled( 'yacht_booking_ical_global_import' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'yacht_booking_ical_global_import' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,57 +56,26 @@ class ICal_Import {
|
||||
* Clear cron
|
||||
*/
|
||||
public static function clear_cron() {
|
||||
wp_clear_scheduled_hook( 'yacht_booking_ical_import' );
|
||||
wp_clear_scheduled_hook( 'yacht_booking_ical_global_import' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run import for all yachts that have an iCal URL configured.
|
||||
* Globalny iCal import — pobiera jeden URL kalendarza Google z ustawień
|
||||
* globalnych, parsuje eventy i przypisuje je do jachtów po prefiksie w SUMMARY.
|
||||
*
|
||||
* Format SUMMARY: "{nazwa_jachtu_lub_alias} - {opis}".
|
||||
* Eventy bez separatora lub bez dopasowanego jachtu są ignorowane (logowane).
|
||||
*
|
||||
* @return bool True przy sukcesie HTTP, false przy błędzie pobrania/braku URL.
|
||||
*/
|
||||
public static function run_import() {
|
||||
$yachts = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
public static function run_global_import() {
|
||||
$url = get_option( 'yacht_booking_global_ical_import_url', '' );
|
||||
$url = is_string( $url ) ? trim( $url ) : '';
|
||||
|
||||
foreach ( $yachts as $yacht_id ) {
|
||||
$url = self::get_import_url( $yacht_id );
|
||||
if ( $url ) {
|
||||
self::import_for_yacht( $yacht_id, $url );
|
||||
}
|
||||
if ( empty( $url ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get iCal import URL for a yacht.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_import_url( $yacht_id ) {
|
||||
return get_post_meta( $yacht_id, '_yacht_ical_import_url', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set iCal import URL for a yacht.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @param string $url iCal URL.
|
||||
*/
|
||||
public static function set_import_url( $yacht_id, $url ) {
|
||||
update_post_meta( $yacht_id, '_yacht_ical_import_url', esc_url_raw( $url ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import events from iCal URL for a specific yacht.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @param string $url iCal URL.
|
||||
* @return bool
|
||||
*/
|
||||
public static function import_for_yacht( $yacht_id, $url ) {
|
||||
$response = wp_remote_get(
|
||||
$url,
|
||||
array(
|
||||
@@ -108,19 +85,26 @@ class ICal_Import {
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::log( sprintf( 'iCal fetch failed for yacht #%d: %s', $yacht_id, $response->get_error_message() ), 'error' );
|
||||
self::log( sprintf( 'Global iCal fetch failed: %s', $response->get_error_message() ), 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $body ) ) {
|
||||
self::log( sprintf( 'iCal empty response for yacht #%d', $yacht_id ), 'error' );
|
||||
self::log( 'Global iCal: empty response body', 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$events = self::parse_ics( $body );
|
||||
$events = self::parse_ics( $body );
|
||||
$yacht_map = self::build_yacht_lookup_map();
|
||||
|
||||
$existing_map = self::get_existing_import_map( $yacht_id );
|
||||
if ( empty( $yacht_map ) ) {
|
||||
self::log( 'Global iCal: no yachts in DB — nothing to match', 'error' );
|
||||
update_option( 'yacht_booking_global_ical_last_import', current_time( 'mysql' ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
$existing_map = self::get_existing_global_import_map();
|
||||
$seen_uids = array();
|
||||
|
||||
foreach ( $events as $event ) {
|
||||
@@ -128,39 +112,198 @@ class ICal_Import {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip past events
|
||||
// Skip past events.
|
||||
if ( strtotime( $event['end'] ) < time() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen_uids[] = $event['uid'];
|
||||
$booking_id = isset( $existing_map[ $event['uid'] ] ) ? (int) $existing_map[ $event['uid'] ] : 0;
|
||||
$booking_id = self::upsert_booking( $yacht_id, $event, $booking_id );
|
||||
$summary = isset( $event['summary'] ) ? (string) $event['summary'] : '';
|
||||
$yacht_id = self::match_yacht_by_prefix( $summary, $yacht_map );
|
||||
|
||||
if ( ! $yacht_id ) {
|
||||
self::log( sprintf( 'Global iCal: skip event "%s" — no yacht match for prefix', $summary ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen_uids[] = $event['uid'];
|
||||
$existing_id = isset( $existing_map[ $event['uid'] ] ) ? (int) $existing_map[ $event['uid'] ] : 0;
|
||||
|
||||
$booking_id = self::upsert_global_booking( $yacht_id, $event, $existing_id );
|
||||
|
||||
if ( ! $booking_id ) {
|
||||
self::log( sprintf( 'Failed to upsert iCal event %s for yacht #%d', $event['uid'], $yacht_id ), 'error' );
|
||||
self::log( sprintf( 'Global iCal: failed to upsert event %s', $event['uid'] ), 'error' );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale imports (events deleted from external calendar)
|
||||
// Stale cleanup — usuń bookingi których UID nie ma już w feedzie.
|
||||
foreach ( $existing_map as $uid => $booking_id ) {
|
||||
if ( ! in_array( $uid, $seen_uids, true ) ) {
|
||||
Availability::clear_booking_availability( $booking_id );
|
||||
\YachtBooking\Availability::clear_booking_availability( $booking_id );
|
||||
wp_delete_post( $booking_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
update_post_meta( $yacht_id, '_yacht_ical_last_import', current_time( 'mysql' ) );
|
||||
update_option( 'yacht_booking_global_ical_last_import', current_time( 'mysql' ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buduje mapę: lowercase(alias|post_title) => yacht_id.
|
||||
*
|
||||
* Alias (`_yacht_gcal_alias`) ma priorytet nad post_title. Klucze są
|
||||
* znormalizowane przez mb_strtolower + trim, dla matchowania case-insensitive
|
||||
* i niezależnego od białych znaków.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function build_yacht_lookup_map() {
|
||||
$yachts = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
)
|
||||
);
|
||||
|
||||
$map = array();
|
||||
foreach ( $yachts as $yacht ) {
|
||||
$alias = \YachtBooking\Yacht::get_gcal_alias( $yacht->ID );
|
||||
$key = '' !== trim( (string) $alias ) ? $alias : $yacht->post_title;
|
||||
$key = mb_strtolower( trim( (string) $key ) );
|
||||
|
||||
if ( '' === $key ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nie nadpisuj — w razie kolizji wygrywa pierwszy. Kolizje są
|
||||
// wpisem do logu w czasie matchowania (nie tutaj — bezgłośnie pierwszy).
|
||||
if ( ! isset( $map[ $key ] ) ) {
|
||||
$map[ $key ] = (int) $yacht->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wyciąga prefiks (przed pierwszym " - ") z SUMMARY i dopasowuje do jachtu
|
||||
* w mapie (case-insensitive).
|
||||
*
|
||||
* @param string $summary SUMMARY eventu.
|
||||
* @param array $yacht_map Mapa lowercase(klucz) => yacht_id.
|
||||
* @return int 0 gdy brak dopasowania.
|
||||
*/
|
||||
protected static function match_yacht_by_prefix( $summary, $yacht_map ) {
|
||||
if ( '' === $summary ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$pos = mb_strpos( $summary, self::SUMMARY_SEPARATOR );
|
||||
if ( false === $pos ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$prefix = mb_substr( $summary, 0, $pos );
|
||||
$prefix = mb_strtolower( trim( $prefix ) );
|
||||
|
||||
if ( '' === $prefix ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return isset( $yacht_map[ $prefix ] ) ? (int) $yacht_map[ $prefix ] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca mapę istniejących globalnych importów: uid => booking_id.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_existing_global_import_map() {
|
||||
$bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_booking_source',
|
||||
'value' => self::GLOBAL_IMPORT_SOURCE,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$map = array();
|
||||
foreach ( $bookings as $booking_id ) {
|
||||
$uid = get_post_meta( $booking_id, '_ical_event_uid', true );
|
||||
if ( $uid ) {
|
||||
$map[ $uid ] = (int) $booking_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy lub aktualizuje booking placeholder z globalnego importu.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @param array $event Parsed event data.
|
||||
* @param int $existing_id Existing booking ID (0 dla nowego).
|
||||
* @return int|false
|
||||
*/
|
||||
protected static function upsert_global_booking( $yacht_id, $event, $existing_id = 0 ) {
|
||||
$summary = ! empty( $event['summary'] ) ? sanitize_text_field( $event['summary'] ) : __( 'Blokada Google Calendar', 'yacht-booking' );
|
||||
$start_date = $event['start'];
|
||||
$end_date = $event['end'];
|
||||
|
||||
$post_data = array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'post_title' => sprintf(
|
||||
/* translators: %s: event summary */
|
||||
__( 'GCal: %s', 'yacht-booking' ),
|
||||
$summary
|
||||
),
|
||||
);
|
||||
|
||||
if ( $existing_id > 0 ) {
|
||||
$post_data['ID'] = $existing_id;
|
||||
$booking_id = wp_update_post( $post_data, true );
|
||||
} else {
|
||||
$booking_id = wp_insert_post( $post_data, true );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $booking_id ) || ! $booking_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
update_post_meta( $booking_id, '_booking_yacht_id', (int) $yacht_id );
|
||||
update_post_meta( $booking_id, '_booking_start_date', $start_date );
|
||||
update_post_meta( $booking_id, '_booking_end_date', $end_date );
|
||||
update_post_meta( $booking_id, '_booking_status', 'confirmed' );
|
||||
update_post_meta( $booking_id, '_booking_customer_name', __( 'Google Calendar (import)', 'yacht-booking' ) );
|
||||
update_post_meta( $booking_id, '_booking_customer_email', '' );
|
||||
update_post_meta( $booking_id, '_booking_customer_phone', '' );
|
||||
update_post_meta( $booking_id, '_booking_total_price', 0 );
|
||||
update_post_meta( $booking_id, '_booking_source', self::GLOBAL_IMPORT_SOURCE );
|
||||
update_post_meta( $booking_id, '_ical_event_uid', $event['uid'] );
|
||||
update_post_meta( $booking_id, '_booking_notes', $summary );
|
||||
|
||||
\YachtBooking\Availability::clear_booking_availability( $booking_id );
|
||||
\YachtBooking\Availability::mark_as_booked( $yacht_id, $start_date, $end_date, $booking_id );
|
||||
|
||||
return (int) $booking_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse .ics content into array of events.
|
||||
*
|
||||
* @param string $ics_content Raw .ics content.
|
||||
* @return array
|
||||
*/
|
||||
private static function parse_ics( $ics_content ) {
|
||||
protected static function parse_ics( $ics_content ) {
|
||||
$events = array();
|
||||
$lines = preg_split( '/\r\n|\r|\n/', $ics_content );
|
||||
$in_event = false;
|
||||
@@ -277,106 +420,6 @@ class ICal_Import {
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing imported bookings map: uid => booking_id.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return array
|
||||
*/
|
||||
private static function get_existing_import_map( $yacht_id ) {
|
||||
$bookings = get_posts(
|
||||
array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_booking_source',
|
||||
'value' => self::IMPORT_SOURCE,
|
||||
),
|
||||
array(
|
||||
'key' => '_booking_yacht_id',
|
||||
'value' => (int) $yacht_id,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$map = array();
|
||||
foreach ( $bookings as $booking_id ) {
|
||||
$uid = get_post_meta( $booking_id, '_ical_event_uid', true );
|
||||
if ( $uid ) {
|
||||
$map[ $uid ] = (int) $booking_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update imported booking placeholder.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @param array $event Parsed event data.
|
||||
* @param int $existing_id Existing booking ID (0 for new).
|
||||
* @return int|false
|
||||
*/
|
||||
private static function upsert_booking( $yacht_id, $event, $existing_id = 0 ) {
|
||||
$summary = ! empty( $event['summary'] ) ? sanitize_text_field( $event['summary'] ) : __( 'Blokada iCal', 'yacht-booking' );
|
||||
$start_date = $event['start'];
|
||||
$end_date = $event['end'];
|
||||
|
||||
$post_data = array(
|
||||
'post_type' => 'yacht_booking',
|
||||
'post_status' => 'publish',
|
||||
'post_title' => sprintf(
|
||||
/* translators: %s: event summary */
|
||||
__( 'Import iCal: %s', 'yacht-booking' ),
|
||||
$summary
|
||||
),
|
||||
);
|
||||
|
||||
if ( $existing_id > 0 ) {
|
||||
$post_data['ID'] = $existing_id;
|
||||
$booking_id = wp_update_post( $post_data, true );
|
||||
} else {
|
||||
$booking_id = wp_insert_post( $post_data, true );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $booking_id ) || ! $booking_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
update_post_meta( $booking_id, '_booking_yacht_id', (int) $yacht_id );
|
||||
update_post_meta( $booking_id, '_booking_start_date', $start_date );
|
||||
update_post_meta( $booking_id, '_booking_end_date', $end_date );
|
||||
update_post_meta( $booking_id, '_booking_status', 'confirmed' );
|
||||
update_post_meta( $booking_id, '_booking_customer_name', __( 'Import iCal', 'yacht-booking' ) );
|
||||
update_post_meta( $booking_id, '_booking_customer_email', '' );
|
||||
update_post_meta( $booking_id, '_booking_customer_phone', '' );
|
||||
update_post_meta( $booking_id, '_booking_total_price', 0 );
|
||||
update_post_meta( $booking_id, '_booking_source', self::IMPORT_SOURCE );
|
||||
update_post_meta( $booking_id, '_ical_event_uid', $event['uid'] );
|
||||
update_post_meta( $booking_id, '_booking_notes', $summary );
|
||||
|
||||
Availability::clear_booking_availability( $booking_id );
|
||||
Availability::mark_as_booked( $yacht_id, $start_date, $end_date, $booking_id );
|
||||
|
||||
return (int) $booking_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last import time for yacht.
|
||||
*
|
||||
* @param int $yacht_id Yacht ID.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_last_import_time( $yacht_id ) {
|
||||
return get_post_meta( $yacht_id, '_yacht_ical_last_import', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message.
|
||||
*
|
||||
|
||||
@@ -37,13 +37,21 @@ function yacht_booking_delete_options() {
|
||||
delete_option( 'yacht_booking_terms_page_id' );
|
||||
delete_option( 'yacht_booking_email_templates' );
|
||||
delete_option( 'yacht_booking_enable_notifications' );
|
||||
delete_option( 'yacht_booking_gcal_sync_enabled' );
|
||||
delete_option( 'yacht_booking_gcal_token' );
|
||||
delete_option( 'yacht_booking_gcal_webhook_token' );
|
||||
delete_option( 'yacht_booking_enabled' );
|
||||
delete_option( 'yacht_booking_capabilities_added' );
|
||||
|
||||
// Globalna iCal sync (z 09-02).
|
||||
delete_option( 'yacht_booking_global_ical_import_url' );
|
||||
delete_option( 'yacht_booking_global_ical_token' );
|
||||
delete_option( 'yacht_booking_global_ical_last_import' );
|
||||
|
||||
// Legacy keys (defensive — gdyby pozostały po starszych wersjach).
|
||||
delete_option( 'yacht_booking_gcal_credentials' );
|
||||
delete_option( 'yacht_booking_gcal_tokens' );
|
||||
delete_option( 'yacht_booking_gcal_calendar_id' );
|
||||
delete_option( 'yacht_booking_capabilities_added' );
|
||||
delete_option( 'yacht_booking_gcal_sync_enabled' );
|
||||
delete_option( 'yacht_booking_gcal_token' );
|
||||
delete_option( 'yacht_booking_gcal_webhook_token' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,11 +65,7 @@ function yacht_booking_activate() {
|
||||
$installer = new YachtBooking\Installer();
|
||||
$installer->install();
|
||||
|
||||
// Setup Google Calendar cron jobs
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-sync-controller.php';
|
||||
YachtBooking\Integrations\GoogleCalendar\Sync_Controller::setup_cron();
|
||||
|
||||
// Setup iCal import cron
|
||||
// Setup iCal import cron (globalny — z 09-02)
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/ical/class-ical-import.php';
|
||||
YachtBooking\Integrations\ICal\ICal_Import::setup_cron();
|
||||
|
||||
@@ -82,10 +78,6 @@ register_activation_hook( __FILE__, 'yacht_booking_activate' );
|
||||
* Plugin deactivation hook
|
||||
*/
|
||||
function yacht_booking_deactivate() {
|
||||
// Clear Google Calendar cron jobs
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/google-calendar/class-sync-controller.php';
|
||||
YachtBooking\Integrations\GoogleCalendar\Sync_Controller::clear_cron();
|
||||
|
||||
// Clear iCal import cron
|
||||
require_once YACHT_BOOKING_PLUGIN_DIR . 'integrations/ical/class-ical-import.php';
|
||||
YachtBooking\Integrations\ICal\ICal_Import::clear_cron();
|
||||
|
||||
Reference in New Issue
Block a user