131 lines
8.0 KiB
Markdown
131 lines
8.0 KiB
Markdown
# Architecture
|
||
|
||
**Analysis Date:** 2026-05-05
|
||
|
||
## Pattern Overview
|
||
|
||
WordPress plugin with layered architecture: CPT-backed data model, REST API surface, Singleton orchestrators, static utility classes.
|
||
|
||
- Entry point bootstraps a single Singleton (`Yacht_Booking`) via `plugins_loaded`
|
||
- All public-facing state changes go through REST API (`/wp-json/yacht-booking/v1/`)
|
||
- Admin state changes use WordPress PRG pattern (form POST → redirect)
|
||
- External calendar sync handled by separate Integration namespaces
|
||
|
||
## Class Inventory
|
||
|
||
| Class | File | Pattern | Responsibility |
|
||
|-------|------|---------|---------------|
|
||
| `Yacht_Booking` | `includes/class-yacht-booking.php` | Singleton | Master orchestrator — wires all subsystems |
|
||
| `Yacht` | `includes/class-yacht.php` | Static utility | `yacht` CPT registration + meta accessors |
|
||
| `Booking` | `includes/class-booking.php` | Static utility | `yacht_booking` CPT + `create()` + status |
|
||
| `Inquiry` | `includes/class-inquiry.php` | Static utility | `yacht_inquiry` CPT + `send_emails()` |
|
||
| `Availability` | `includes/class-availability.php` | Static utility | All `wp_yacht_availability` table operations |
|
||
| `Rest_Controller` | `api/class-rest-controller.php` | Extends `WP_REST_Controller` | 7 REST endpoints, booking orchestration |
|
||
| `Settings` | `includes/class-settings.php` | Static utility | Typed `wp_options` accessors + formatting helpers |
|
||
| `Installer` | `includes/class-installer.php` | Plain class | DB table creation + default options on activation |
|
||
| `Email_Templates` | `includes/class-email-templates.php` | Static utility | Template storage, compilation, tag replacement |
|
||
| `Admin` | `admin/class-admin.php` | Singleton (admin-only) | Menu, form processing, CSV export, customer emails |
|
||
| `Booking_List_Table` | `admin/class-booking-list-table.php` | Extends `WP_List_Table` | Bookings admin table |
|
||
| `Yacht_List_Table` | `admin/class-yacht-list-table.php` | Extends `WP_List_Table` | Yachts admin table |
|
||
| `Inquiry_List_Table` | `admin/class-inquiry-list-table.php` | Extends `WP_List_Table` | Inquiries admin table |
|
||
| `Sync_Controller` | `integrations/google-calendar/class-sync-controller.php` | Singleton | GCal cron sync orchestration |
|
||
| `GCal_Service` | `integrations/google-calendar/class-gcal-service.php` | Plain class | Google Calendar API calls |
|
||
| `OAuth_Handler` | `integrations/google-calendar/class-oauth-handler.php` | Plain class | OAuth 2.0 token storage + refresh |
|
||
| `ICal_Feed` | `integrations/ical/class-ical-feed.php` | Static/plain | iCal feed generation + rewrite rule |
|
||
| `ICal_Import` | `integrations/ical/class-ical-import.php` | Static/plain | External iCal URL import + cron |
|
||
| `Shortcode` | `frontend/class-shortcode.php` | Singleton | `[yacht_calendar]` shortcode |
|
||
| `Calendar_Widget` | `frontend/class-calendar-widget.php` | Extends Elementor `Widget_Base` | Elementor `yacht-calendar` widget |
|
||
|
||
## Dependency Graph
|
||
|
||
```
|
||
yacht-booking-system.php
|
||
└── Yacht_Booking (Singleton)
|
||
├── Yacht (static)
|
||
├── Booking (static)
|
||
│ └── fires: yacht_booking_created
|
||
│ yacht_booking_status_changed
|
||
├── Availability (static)
|
||
├── Settings (static)
|
||
├── Email_Templates (static)
|
||
├── Rest_Controller
|
||
│ ├── Yacht, Booking, Availability, Settings, Inquiry
|
||
│ ├── Email_Templates
|
||
│ └── listens: yacht_booking_created → send_booking_notification (admin email)
|
||
├── Admin (Singleton, admin-only)
|
||
│ ├── Booking, Availability, Yacht, Settings, Email_Templates, Inquiry
|
||
│ ├── Booking_List_Table → Booking, Availability, Settings
|
||
│ ├── Yacht_List_Table, Inquiry_List_Table
|
||
│ └── listens: yacht_booking_status_changed → send_customer_notification
|
||
├── Sync_Controller (Singleton)
|
||
│ ├── GCal_Service → OAuth_Handler
|
||
│ └── writes: Availability::mark_as_blocked
|
||
├── ICal_Import → Availability::mark_as_blocked
|
||
└── ICal_Feed (feed output)
|
||
```
|
||
|
||
## WP Hook Map
|
||
|
||
| Hook | Type | Registered by | Handler |
|
||
|------|------|---------------|---------|
|
||
| `plugins_loaded` (p10) | action | bootstrap | `yacht_booking_init()` → `Yacht_Booking::get_instance()` |
|
||
| `init` (p10) | action | Yacht_Booking | `register_post_types()` |
|
||
| `init` (p15) | action | Yacht_Booking | `register_shortcodes()` |
|
||
| `init` | action | bootstrap | `yacht_booking_load_textdomain()` |
|
||
| `wp_enqueue_scripts` | action | Yacht_Booking | `enqueue_frontend_assets()` (conditional) |
|
||
| `admin_enqueue_scripts` | action | Yacht_Booking | `enqueue_admin_assets()` (conditional) |
|
||
| `rest_api_init` | action | Yacht_Booking | `register_rest_routes()` |
|
||
| `elementor/widgets/register` | action | Yacht_Booking | `register_elementor_widgets()` |
|
||
| `admin_init` | action | Yacht_Booking | `add_custom_capabilities()` |
|
||
| `admin_menu` (p9) | action | Admin | `register_admin_menu()` |
|
||
| `admin_init` | action | Admin | `process_bulk_actions()`, `process_booking_actions()`, `process_yacht_save()`, `process_settings_save()`, `process_export_download()` |
|
||
| `admin_notices` | action | Admin | `display_admin_notices()` |
|
||
| `yacht_booking_created` | custom action | Rest_Controller | `send_booking_notification()` (admin email) |
|
||
| `yacht_booking_status_changed` | custom action | Admin | `send_customer_notification()` |
|
||
| `before_delete_post` | action | Sync_Controller | `on_booking_deleted()` (GCal event delete) |
|
||
| `wp_ajax_yacht_booking_manual_sync` | action | Sync_Controller | `ajax_manual_sync()` |
|
||
| `yacht_booking_sync_to_gcal` | cron action | Sync_Controller | `sync_booking_to_gcal()` |
|
||
| `yacht_booking_pull_from_gcal` | cron action (hourly) | Sync_Controller | `pull_from_gcal()` |
|
||
| `yacht_booking_ical_import` | cron action (hourly) | ICal_Import | `run_import()` |
|
||
| `init` | action | ICal_Feed | `add_rewrite_rules()` |
|
||
| `template_redirect` | action | ICal_Feed | `handle_feed_request()` |
|
||
|
||
## Key Data Flows
|
||
|
||
### Booking Creation (Frontend → REST → DB)
|
||
1. User picks dates on FullCalendar → `GET /availability/{yacht_id}` → `Availability::get_availability_calendar()`
|
||
2. User submits form → `POST /bookings` with `X-WP-Nonce` header
|
||
3. `Rest_Controller::create_booking()`: nonce check → `is_booking_enabled()` → `Availability::is_available()`
|
||
4. Price: `count_days() × get_price_per_day()`
|
||
5. `Booking::create()` → inserts CPT post + meta → fires `yacht_booking_created`
|
||
6. `Availability::mark_as_booked()` → inserts rows in `wp_yacht_availability`
|
||
7. Admin email sent via `Email_Templates`; JSON response to frontend
|
||
|
||
### Admin Booking Management
|
||
1. Bookings list → `Booking_List_Table::prepare_items()` (WP_Query)
|
||
2. Row action URLs: nonce-protected GET `?action=approve_booking_{id}`
|
||
3. `Admin::process_booking_actions()` → `Booking::update_status()` → fires `yacht_booking_status_changed`
|
||
4. `Admin::send_customer_notification()` → customer email
|
||
5. On cancel/delete: `Availability::clear_booking_availability($booking_id)` deletes rows by booking_id
|
||
6. PRG redirect back to list
|
||
|
||
### GCal Sync
|
||
1. Hourly WP Cron: `pull_from_gcal()` → `GCal_Service` → Google API → `Availability::mark_as_blocked()`
|
||
2. On booking create: scheduled single cron `yacht_booking_sync_to_gcal` → push to GCal
|
||
3. OAuth token refresh: `OAuth_Handler::get_access_token()` → auto-refresh if expired
|
||
|
||
## Error Handling Strategy
|
||
|
||
- REST endpoints return `WP_Error` with HTTP status codes
|
||
- Admin operations: `wp_die()` on capability failure; `?error=1` redirect on soft errors
|
||
- `Booking::create()` returns `false` on failure — caller checks
|
||
- GCal API: `is_wp_error()` check only; HTTP 4xx/5xx responses silently ignored
|
||
- No exceptions used anywhere
|
||
|
||
## Custom Capabilities
|
||
|
||
Added once to `administrator` role via `Yacht_Booking::add_custom_capabilities()`:
|
||
- `yacht_booking_manage_yachts`
|
||
- `yacht_booking_manage_bookings`
|
||
- `yacht_booking_manage_settings`
|