WP Manifestindependent plugin directory
manifest / events / epa-booking-engine

EPA Booking Engine releasesself-updates

Generic WordPress booking engine for Shabbat meals, festivals, workshops and events. Built on WooCommerce.

by EPA Productions · github.com/elchanan-star/epa-booking-engine · website

0stars
5release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/elchanan-star/epa-booking-engine/releases/download/v1.7.1/epa-booking-engine-1.7.1.zip

Ships its own WordPress updater (Plugin Update Checker), so new versions show up under Dashboard → Updates.

Readme

EPA Booking Engine

A modular WordPress plugin that powers a step-by-step booking flow. Built first for Shabbat meal reservations at a Tbilisi restaurant, architected to be reused for workshops, hotels, events and other bookable products.

File structure

epa-booking-engine/
├── epa-booking-engine.php     Plugin bootstrap (headers, constants, requires)
├── assets/
│   ├── css/booking.css        Front-end styling (dark luxury / gold theme)
│   ├── css/admin.css          Admin settings page tab styling
│   ├── js/booking.js          Booking flow logic (state, validation, Hebcal, REST submit)
│   └── js/admin.js            Admin settings page tab switching
├── includes/
│   ├── class-plugin.php       Singleton, settings reader, pricing, custom post type
│   ├── class-admin.php        "EPA Booking" admin settings page (Settings API)
│   ├── class-assets.php       Conditional CSS/JS enqueue (only on pages with the shortcode)
│   ├── class-shortcodes.php   Renders [epa_booking_engine] markup
│   ├── class-rest.php               REST endpoint that stores a submitted booking. Never touches WooCommerce.
│   ├── class-woocommerce.php        WooCommerce order-lifecycle sync (cart pricing/display, order↔booking link, status sync)
│   └── class-checkout-controller.php  The only place a booking is added to the WooCommerce cart — via a normal page load, not REST
├── assets/vendor/intl-tel-input/  Self-hosted phone input library (v18.5.3, MIT) — not loaded from a CDN
└── README.md

Both WooCommerce-facing classes are self-guarding: each no-ops entirely if WooCommerce is inactive.

Installation

  1. Copy the whole epa-booking-engine folder into wp-content/plugins/.
  2. In WordPress admin, go to Plugins and activate EPA Booking Engine.
  3. Edit any page/post and add the shortcode:
    [epa_booking_engine]
  4. Publish the page. The booking form (CSS/JS) only loads on pages that contain this shortcode — it does not load site-wide.

Submitted bookings are stored under Bookings in the WordPress admin menu (a private post type, epa_booking), with meta fields for the Shabbat date, meals, guest counts, contact details and computed total.

If WooCommerce is active (and the configured product resolves — see the WooCommerce tab below), the booking is added to the WooCommerce cart with the server-calculated price and the visitor is redirected straight to WooCommerce Checkout. Payment, tax, coupons, order emails and refunds are entirely WooCommerce's — this plugin never touches any of that.

If WooCommerce is inactive, or misconfigured, nothing breaks: the booking is still saved with status pending_payment, and the visitor sees the original inline "thank you" placeholder instead of being redirected. The plugin never assumes WooCommerce exists.

Admin Settings Page

Everything below is controlled without touching code, from WordPress Admin → EPA Booking (left-hand menu, calendar icon).

The page has 5 tabs, all saved together under one option (epa_booking_engine_settings) when you click Save Settings:

Tab Controls
General Settings Currency symbol, booking form title, success message, error message
Meals & Pricing Per meal: enabled on/off, title, adult price, child price
Shabbat Settings How many upcoming Shabbatot to show (1–10), Diaspora vs. Israel mode
Texts / Labels Every step title, button label, guest/total/select labels, Step 5 field labels, summary row labels, the payment placeholder text, and all validation messages
WooCommerce The WooCommerce product ID every booking is added to cart against (default 311, "Shabbat Reservation"). Shows a live status line: whether WooCommerce is active, and whether the configured product ID actually resolves

Requires the manage_options capability (Administrator role).

How to change prices

EPA Booking → Meals & Pricing → edit Adult Price / Child Price for either meal → Save Settings. The new price is used immediately by the meal cards, the live summary total, and the server-side total recomputed on submit (the server never trusts a client-sent price).

How to enable/disable a meal

EPA Booking → Meals & Pricing → untick Enabled under that meal → Save Settings. A disabled meal disappears from the front-end meal cards entirely and is excluded from price calculation, both client- and server-side.

How to switch between Diaspora and Israel mode

EPA Booking → Shabbat SettingsDiaspora / Israel Mode → choose Diaspora or IsraelSave Settings. This flips the Hebcal i parameter (i=off for Diaspora, i=on for Israel) used when resolving Parasha names — see below.

How to change how many Shabbatot are shown

EPA Booking → Shabbat SettingsNumber of Future Shabbatot (1–10) → Save Settings.

How to change texts

EPA Booking → Texts / Labels — every step title, button label, and validation message shown on the front-end lives here. No code or translation files needed for a simple wording change.

All settings fall back to sensible defaults if nothing has been saved yet (fresh install), so the plugin works out of the box.

Booking steps

The form is a 6-step flow: 1. Shabbat → 2. Branch → 3. Meals → 4. Diners → 5. Identification → 6. Summary.

  • Branch (Step 2) is a simple single-select between the two configured branches (tbilisi / batumi by default — EPA_Booking_Engine_Plugin::get_branches(), filterable via epa_booking_engine_branches). Rendered server-side (no async fetch, unlike Step 1's Shabbat dates); booking.js's bindBranchCards() handles the click/select interaction.
  • Diners (Step 4) is built dynamically by booking.js (renderDinersStep()), not by class-shortcodes.php — how many guest-count blocks to show depends on Step 3's selection, which doesn't exist yet at PHP render time. With one meal selected it's a single Adults/Children pair, identical to before this feature existed. With both meals selected, it renders one pair per meal (labeled with that meal's title), so a customer can bring a different number of guests to each sitting — e.g. 4 people Friday night, only 2 for Shabbat lunch. Each meal is priced against its own count.

On submit, the REST payload carries branch and a meal_guests array ([{meal, adults, children}, ...], one entry per selected meal) instead of flat adults/children fields. class-rest.php validates that every selected meal has at least one diner of its own, computes the total per meal, and — alongside the new branch and meal_guests post meta — still stores adults/children as the summed totals across meals, so everywhere else in the plugin that only ever needed "how many people total" (WooCommerce cart/order summary, the admin Guests column, the Reservation meta box's Adults/Children rows) keeps working unchanged. The Reservation meta box additionally shows a Guests per Meal breakdown row whenever a booking has more than one meal.

How the Shabbat dates are calculated

booking.js computes the next N upcoming Saturdays (N = Number of Future Shabbatot from settings, client-side, based on the visitor's browser date), then calls the public Hebcal API once per calendar year needed (with caching) to resolve the Parasha name for each date:

https://www.hebcal.com/hebcal?cfg=json&v=1&year=<YEAR>&i=<off|on>&maj=off&min=off&mod=off&nx=off&s=on

i=off is used in Diaspora mode (default, correct for Tbilisi), i=on in Israel mode — controlled by EPA Booking → Shabbat Settings. If the upcoming Shabbatot span a year boundary, both years are fetched automatically.

Phone field (intl-tel-input)

Step 5's phone field is powered by intl-tel-input v18.5.3, self-hosted under assets/vendor/intl-tel-input/ (not loaded from a CDN, so the field never depends on a third-party service being reachable). Default country is Georgia (ge); booking.js first tries a hint from the visitor's browser language (navigator.language, e.g. ka-GEge) with no IP lookup or external geolocation call, and falls back to Georgia whenever that isn't available. The visitor can always pick a different country from the dropdown.

On submit, state.phone is the E.164-formatted number (iti.getNumber(), e.g. +995...) and state.countryCode is the ISO 3166-1 alpha-2 code (e.g. GE), sent to the REST endpoint as phone and country_code and stored as their own post meta fields — country_code is intended for future reporting/WhatsApp/automation use. Validation (iti.isValidNumber()) blocks Step 5 from advancing with a friendly message (EPA Booking → Texts / Labels → Validation: Invalid Phone) if the number is incomplete or malformed for the selected country.

If the library script fails to load for any reason, every part of this degrades to the original plain-text-input behavior (iti stays null, checked at every call site) — the field keeps working, just without formatting/validation, and country_code is simply left empty. Existing bookings saved before this field existed have no country_code meta at all and continue to display and function normally (get_post_meta() on a missing key returns '', handled the same way everywhere it's already read).

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.7.1 Aug 31, 2026 epa-booking-engine-1.7.1.zip 1
v1.7.0 Aug 31, 2026 epa-booking-engine-1.7.0.zip 1
v1.6.3 Aug 31, 2026 epa-booking-engine-1.6.3.zip 1
v1.6.2 Aug 31, 2026 epa-booking-engine-1.6.2.zip 1
v1.6.1 Aug 31, 2026 epa-booking-engine-1.6.1.zip 1
v1.6.0 Aug 31, 2026 epa-booking-engine-1.6.0.zip 0