# Coding Conventions **Analysis Date:** 2026-05-05 ## PHP Style **Standard:** WordPress Coding Standards (not PSR-12). - 1 tab indentation - `array()` long-form always — never `[]` short syntax in plugin code - Spaces inside control structure parens: `if ( $condition )`, `foreach ( $items as $item )` - No spaces inside function call parens: `get_post_meta( $id, '_key', true )` - Every PHP file starts with direct-access guard: ```php if ( ! defined( 'ABSPATH' ) ) { exit; } ``` - File-level docblock with `@package YachtBooking` ## Naming | Thing | Convention | Example | |-------|-----------|---------| | Classes | PascalCase (underscore for multi-word) | `Rest_Controller`, `Yacht_Booking` | | Methods | snake_case | `get_yacht_id()`, `mark_as_booked()` | | Variables | snake_case | `$booking_id`, `$start_date` | | Constants | SCREAMING_SNAKE_CASE | `YACHT_BOOKING_VERSION` | | Post meta keys | underscore-prefixed, namespaced | `_booking_start_date`, `_yacht_capacity` | | WP option keys | `yacht_booking_` prefix | `yacht_booking_version` | | Boolean getters | `is_` / `has_` prefix | `is_available()`, `has_yacht_calendar_widget()` | | Static getters | `get_{thing}($id)` | `Booking::get_customer_name($id)` | | Static updaters | `update_{thing}($id, $value)` | `Yacht::update_capacity($id, $v)` | ## Class Patterns **Singleton** — for hook-registering classes (must only run once): ```php private static $instance = null; public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { /* register hooks here */ } ``` Used by: `Yacht_Booking`, `Admin`, `Shortcode`, `Sync_Controller` **Static utility** — for pure data accessors with no instance state: ```php public static function get_customer_name( $booking_id ) { return get_post_meta( $booking_id, '_booking_customer_name', true ); } ``` Used by: `Booking`, `Yacht`, `Availability`, `Settings`, `Email_Templates` **All WP hook registration goes in constructor or `init_hooks()` method.** ## Security Patterns - **Nonces in admin forms:** `wp_nonce_field()` on output, `check_admin_referer()` on submit - **Nonces in REST endpoints:** `X-WP-Nonce` header verified with `wp_verify_nonce($header, 'wp_rest')` - **iCal feed:** per-yacht token in `_yacht_ical_token` meta, compared with `hash_equals()` - **Admin REST endpoints:** `current_user_can('yacht_booking_manage_bookings')` - **Sanitization on input:** `sanitize_text_field()`, `sanitize_email()`, `absint()`, `wp_kses_post()` - **Escaping on output:** `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()` - **All `$wpdb` queries:** use `$wpdb->prepare()` — no raw SQL ## WordPress Integration Patterns - CPT registration via static `register()` method called by orchestrator - Options: `add_option()` (idempotent) on activation, `get_option()` / `update_option()` for runtime - Capability checks use custom caps (`yacht_booking_manage_bookings`), not `manage_options` - Admin form submissions: POST → `admin_init` handler → `wp_safe_redirect()` (PRG pattern) - Error feedback: `?error=message` query string + `admin_notices` hook display ## JavaScript Style - **Pattern:** IIFE `(function($) { 'use strict'; ... })(jQuery)` in both JS files - **Variable naming:** camelCase; jQuery-wrapped objects prefixed with `$` (`$form`, `$submitBtn`) - **`calendar.js`:** Named inner functions (`initYachtCalendar`, `formatDate`, `escapeHtml`); uses `const`/`let` - **`admin.js`:** Object-literal module pattern (`const YachtBookingAdmin = { init, bindEvents, handleManualSync }`) - **Data bridge:** `wp_localize_script()` injects `yachtBookingData` (apiUrl, nonce, bookingEnabled, i18n) and `yachtBookingAdmin` - **Error display:** Inline HTML into `.yacht-booking-response` / `.yacht-inquiry-response` divs; messages from `yachtBookingData.i18n` ## CSS / Markup - BEM-like kebab-case with `yacht-` prefix: `.yacht-calendar-wrapper`, `.yacht-booking-form-container` - Modifier suffixes: `.yacht-calendar-view-only`, `.yacht-legend-swatch-past` - JS state classes: `.is-active`, `.yacht-day-available`, `.yacht-day-booked` - Mobile-first approach ## Internationalisation - All user-visible strings: `__()`, `esc_html__()`, `esc_attr_e()` with domain `'yacht-booking'` - Strings with variables: `printf( esc_html__( 'Text %s', 'yacht-booking' ), esc_html( $var ) )` — never concatenation - Translators hint: `/* translators: %s: yacht name */` before `printf` ## Documentation - Public/protected methods always get docblocks; private helpers sometimes omitted - Inline block comments label logical sections: `// Get yacht title`, `// Save booking meta` - HTML comments label template sections in Polish: `` - JS functions get JSDoc blocks: `/** Format date to YYYY-MM-DD */` - PHPDoc generics notation for complex types: `@return array>` ## Error Handling - PHP: return `false` or `WP_Error` on failure - REST: return `new \WP_Error( 'code', message, array( 'status' => 4xx ) )` - No exceptions used anywhere in the plugin - GCal API: `is_wp_error()` check only — HTTP error codes are NOT checked (known gap) - Logging: `error_log()` with prefixes only when `WP_DEBUG === true`; nothing in production