15 versions
BookingWP - #1 WordPress Appointment Booking Plugin history.
Every release of BookingWP - #1 WordPress Appointment Booking Plugin is archived here with its changelog, file size, and security scan result. Use the archive to roll back to a stable release or audit what changed between updates.
15 of 15 releases scanned clean
v5.4.2Latest
July 18, 20262.9 MBScanned
Other
- Dev - PHPStan raised to level 3 (phpVersion 7.4).
- Dev - Tests: regression coverage for range_type RRULE routing parity and orphan `rrule` field cleanup.
- Dev - Tests: lock GCal failure-path contracts (`make_gcal_request()` → `WP_Error`, `sync_event_resource()` → `false`) and rule-minutes payload shape across all `compute_rule_minutes()` branches.
Fixed
- Indexed availability: RRULE expansion follows `range_type` (same as dynamic); orphan `rrule` on non-`rrule` rules uses the non-RRULE path and purges legacy expand rows on background passes.
- Google Calendar: inbound sync clears `rrule` when an event is stored as non-recurring (`custom` / `custom:daterange`).
July 16, 20262.9 MBScanned
Improved
- PHPStan: level 2 with a clean baseline — expanded plugin and integration stubs plus PHPDoc/type fixes across core.
- Storefront calendar: `data-engine` reports rules/occupancy source (1 indexed, 2 dynamic, 3 indexed + appointment cache).
Other
- Performance - Indexed availability: occupancy reads from appointment cache when healthy (live fallback during backfill; in-cart recurring holds merged); backfill batches gap-only missing appointments.
- Dev - Orders: `get_appointment_ids_from_order_and_item_id()` deprecated — use `get_appointment_ids_from_order_item_id( $order_item_id, true, $order_id )`.
- Dev - Orders: `get_appointment_ids_from_order_item_id()` triggers `wc_doing_it_wrong` when `$order_id` is omitted (except opt-in order-line `_appointment_id` cache).
Fixed
- Indexed availability: weekday `time:N` rules count only matching days; re-enable no longer wipes inline indexing; backfill pending flag clears/re-queues stale jobs and stops on zero-gap batches.
- Google Calendar: scheduled sync is forward-only unless admin full re-sync; inbound sync skips stale events (event ID / `site_url` mismatch).
- Availability / slots: overlap day keys use Fake UTC civil midnight so 00:00 wall slots stay on the correct day on negative-offset stores.
- Orders: order-scoped line-item lookup (`$order_id` on `get_appointment_ids_from_order_item_id`) — orphaned `_appointment_order_item_id` no longer shows another customer’s appointment; line delete clears the link before ID reuse.
- Cart: session load removes holds tied to terminal orders or past start dates and releases `in-cart` appointments to `was-in-cart`.
June 9, 20262.8 MBScanned
Fixed
- Twilio SMS integration: restore hook registration removed in 5.1.0 (Appointments SMS settings, scheduling, and notifications with WooCommerce Twilio SMS Notifications 1.12+); `{appointment_date}` / time tokens use `get_start_date()` / `get_end_date()` so all-day civil dates match emails and order summary on non-UTC stores.
- All-day (day/month duration) appointments after checkout: order summary, emails, and My Account show the same civil date as cart/checkout — `get_start_date()` / `get_end_date()` and email formatters use Fake UTC civil digits (`gmdate` + `date_i18n`) instead of `wp_date()` on stored midnight tokens, which shifted the calendar day on non-UTC site timezones.
- All-day bookings: `_local_timezone` is written only for timed products in `wc_appointments_get_posted_data()`; `WC_Appointment::set_all_day()` / `set_local_timezone()` and the data store drop customer TZ meta on all-day saves — civil dates stay timezone-agnostic without per-caller guards.
v5.3.10Unavailable
June 3, 20262.8 MBScanned
Fixed
- Storefront appointment cost quote when catalog prices are entered inclusive of tax: footer HTML uses the same display amount as AJAX `raw_price` (not tax-stripped `line_net`), so logged-out customers see the correct gross total.
- Availability / add-to-cart: day- and month-unit checks use Fake UTC civil tokens (`gmdate` / shared slot-token helpers) for past-date guards, span end calculation, and day-slot capacity keys — not `wp_date()` / site-TZ `DateTime` on the posted timestamp — so same-day bookings and slot lookup stay aligned on non-UTC stores (e.g. America/Vancouver).
- Storefront calendar (no staff preference, day/month + staff): multi-staff indexed slot generation no longer runs the minute-mask loop on day/month products — use per-staff civil-day slots and union merge instead; no-preference day summary uses per-staff OR (max), not summed slot counts across merged fragments.
- Admin panel create: day/month availability check and save use Fake UTC civil midnights for date/month ranges (matches storefront slots and `get_day_civil_span_capacity` keys); admin month calendar bounds for day/month products use the same civil UTC axis.
- `is_appointable()` past-date guard for day/month products uses `wc_appointments_is_past_request_date()` (Fake UTC civil axis) instead of `date()` on `_date`, matching availability checks.
- Admin panel create success message formats saved day/month ranges from appointment timestamps via `wp_date()` (not `strtotime()` on date strings).
v5.3.9Unavailable
May 25, 20262.8 MBScanned
Improved
- Improved admin panel styling with bigger input selection.
- Admin panel create/edit: datetime row shows a loading state while product details, first-available auto-pick, day slots, or availability AJAX run; success/error only after the check completes.
- Unified `day_data`: storefront calendar is `day_data`-only — per-day `padding` / `restricted`, full per-staff occupancy fields, redundant `display_day_summaries` removed; `to_day_data()` optional fold-in; availability widget/block use `get_day_data()` with `selectable`; `resolve_schedule_bounds()` maps Unix `0` to the booking horizon.
- Padding and fully-booked month aggregation call `get_padding_days_for_product()`, `get_padding_days_for_fully_booked_dates()`, and `get_scheduled_month_slots()` (`get_day_data()`-based).
- Storefront `date-picker.js`: removed legacy four-array branch and `is_slot_appointable`; removed unused `getTzOffsetFromString` / `getCustomerTzOffsetMinutes` (offset still server-supplied on AJAX).
- `get_rest_records()` date-range mapping uses `wp_date()` / `wp_timezone()` instead of `date()` / `strtotime()`.
- **WPML / WCML:** admin appointment panel **create** — independent multicurrency selector (`integrationCurrency` on `wc_appointments_admin_panel_config`); appointment-currency cookie + `wcml_client_currency` on panel cost, add-on, and set-currency AJAX; WCML loads in admin for those actions; rate conversion via pricing-rule filters, `wc_price_args`, and WCML `get_product_addons_fields` (add-ons refetch on currency change); footer total from server `calculate_costs` HTML; `_order_currency` on `woocommerce_new_appointment_order`; legacy add-appointment page unchanged.
- **WooCommerce Payments Multi-Currency:** admin appointment panel **create** — independent multicurrency selector (same `integrationCurrency` pattern as WCML); appointment-currency cookie + `wcpay_multi_currency_override_selected_currency` filter for exchange rate conversion on panel cost AJAX; addon prices converted via `woocommerce_product_addons_*_price_raw` (WC Payments skips admin); `_order_currency` persisted on `woocommerce_new_appointment_order`. WCML takes priority when both plugins are active.
- Admin appointment panel **create**: **WooCommerce Deposits** — deposit / pay-in-full / plan UI (storefront POST keys), line totals and meta on `wc_appointments_create_appointment`, `_wc_deposits_version` when applicable; product AJAX exposes `wc_deposits`; compact amounts in `wca-slots-header` / `wca-slots-count`.
- Admin panel browse: time slot chips use a four-column equal-width grid (`.wca-slots-grid` / `.wca-slot-chip` in `appointment-admin-panel.scss`).
- Google Calendar OAuth (settings + staff profile): [Google Identity branding](https://developers.google.com/identity/branding-guidelines) — **Continue with Google**, `assets/images/google-g-mark.svg`, shared markup via `WC_Appointments_GCal::get_google_oauth_connect_button_html()`.
- Admin panel: `woocommerce_appointments_add_appointment_page_redirect` gains a 5th string `$context` (`legacy_post`, `admin_ajax_standalone`, `admin_ajax_calendar`); full admin calendar never returns `post_create_redirect` (callbacks may still read context).
- Update translations (+7 new strings, -2 strings removed).
- Settings → Indexing: during manual re-index, an admin notice explains the storefront uses dynamic availability checks until rebuild finishes (availability stays correct; performance may be slower during rebuild).
Other
- Performance - Admin panel create: first-available auto-pick scans the bookable horizon in 14-day chunks and stops at the first slot instead of loading the full range up front.
- 2026.05.20 - version 5.3.9-beta
- Breaking change - Removed `WC_Appointments_Controller::find_padding_day_slots()`, `get_padding_day_slots_for_scheduled_days()`, and `find_scheduled_month_slots()`. Use `WC_Appointment_Slots_Provider::get_padding_days_for_product()`, `get_padding_days_for_fully_booked_dates()`, and `get_scheduled_month_slots()`.
- Breaking change - Removed `WC_Appointments_Controller::find_scheduled_day_slots()`, `WC_Appointment_Slots_Provider::get_scheduled_day_slots()`, and `WC_Appointment_Slots_Collection::legacy_scheduled_arrays_from_day_data()`. Use `get_day_data()` / `to_day_data()`; padding anchors from fully booked `day_data` (`get_padding_days_for_fully_booked_dates()`, `Y-n-j` keys); months via `get_day_data(..., 'Y-n')`. `woocommerce_appointments_scheduled_day_slots` runs inside `get_day_data()` with the `to_day_data()` payload (not legacy four-array shapes).
- Breaking change - Calendar AJAX `wc_appointments_find_scheduled_day_slots` no longer returns legacy keys (`partially_scheduled_days`, `remaining_scheduled_days`, `fully_scheduled_days`, `unavailable_days`, `padding_days`, `restricted_days`) or invokes that filter at the AJAX layer. Read `day_data` / `meta`, filter the JSON response, or use `get_day_data()` in PHP where the filter still applies.
Fixed
- Admin panel create: availability no longer flashes loading twice or clears to blank before first-available prefill; date-only calendar prefill waits for day slots before checking; browse does not re-open after the user closes it during the same unavailable episode.
- Admin panel: datetime loading no longer sticks when day-slot AJAX fails or returns empty, when a superseded availability request aborts, or while bootstrap waits only on an in-flight slots request (not missing cached data).
- Slots / day & month products: timed global RRULE blocks (e.g. weekly Tue 4–6pm) block the whole civil day again — date-level checks use RRULE occurrence expansion, not midnight-only overlap, so higher-priority global rules override product day rules (`RuleMinutesRruleCivilDayTest`).
- Admin panel browse: slot chips show remaining capacity (e.g. **1 available**) on multi-capacity products when only one place is left, not only when two or more remain.
- Slots: dynamic availability (indexing off) paints full half-open unappointable ranges on the minute mask — priority blocks no longer leave appointable boundary minutes (e.g. 10:00–11:00 block showing 10:59 slots); matches indexed mask painting.
- Slots / calendars: minute/hour bookings that start at site midnight (including long durations such as 12 hours) block capacity again — timed overlap day-keys use Fake UTC site civil buckets for both occupancy indexing and slot lookup instead of mixing wp_date() site midnight with the civil UTC-midnight fast path.
- Slots / calendars: multi-day day-unit occupancy for admin-created (site-local midnight) bookings — civil overlap keys use the site calendar date (Fake UTC) so every booked day blocks correctly (e.g. June 4–5 for a 2-day booking starting June 4); `wc_appointments_get_posted_data()` stores matching Fake UTC start/end for new day/month appointments.
- Slots / calendars: multi-day day-unit products block every booked civil day again — occupancy uses live appointments; overlap day-keys use timezone-agnostic civil dates (gmdate on stored tokens), matching slot tokens, not wp_date() site-local midnight.
- Admin panel **create**: first available date/time via one AJAX call (`WC_Appointment_Slots_Provider::get_first_available_slot`, same as storefront), including when the first slot is outside the current month; calendar day click/drag date-only prefill auto-picks the first time or opens browse once with `noSlotsOnDateSelect` when that day has no slots; browse auto-opens only on unavailable validation (not during silent auto-pick) and stays dismissible (no immediate re-open after close until date or product changes); live preview placeholder while slots load; panel expands when the product form is ready.
- Slots / calendars: minute/hour day availability (storefront `day_data`, admin mini-calendar, time-slot lists) counts only bookable slot starts via shared `WC_Appointment_Slots_Collection::slots_for_day_summary()` on the same `get_collection()` engine — fully booked days no longer stay selectable when only sub-duration engine segments remain.
- Slots / time picker: **Availability check → The starting slot only** no longer lists grid starts in appointable fragments shorter than the booking duration (phantom evening slots such as 19:00 on 12h/12h products when the index leaves a tail after a gap); full-day ranges still allow starts when duration exceeds the window (e.g. 25h in 24h).
- Checkout validation (`WC_Appointment_Cart_Manager::validate_appointment_order`): re-validates cart slots via `WC_Appointments_Controller::get_indexed_total_available_for_range()` (same `slot_matches_requested_range` tolerance as add-to-cart) instead of strict `start_ts` equality; **all staff together** uses staff id `0` fan-out like `is_appointable()`; in-cart holds are excluded from overlap during the check.
- **Staff all together:** cart booking no longer collapses multi-staff `_staff_ids` to a single `_staff_id` during `WC_Appointment_Booking_Handler::create_appointment()` availability merge.
- Admin panel: **WooCommerce Deposits** on create — `calculate_costs` / `create_appointment` honour deposit choice and tax display (incl./excl. catalog); deposit footer quote and order item meta (`_deposit_full_amount`, etc.) match storefront; pay-deposit hides footer price edit (product deposit rules); pay-in-full edits full service price via `raw_full_price`; mistaken deposit-only overrides corrected on save.
- Storefront auto-select with `woocommerce_appointments_include_sold_out`: slots AJAX picks the first bookable time from the availability collection (marks it `selected` in HTML); the time picker applies that server selection instead of the first listed slot when sold-out rows are shown.
- Auto-select slot highlighting: match engine `start_ts` directly and compare unifi
v5.3.7Unavailable
April 30, 20262.8 MBScanned
Improved
- Admin calendar: on expired nonce/session (REST 401/403), show an expiry banner with reload link instead of failing silently after long idle periods.
- Admin appointment panel: create/edit success step uses a light success-tinted circular badge for the checkmark (aligned with other success affordances), not a plain large icon.
- Update translations (+2 strings, −11 strings removed).
Other
- Dev - Removed deprecated per-product `customer_timezones`; timezone support is site-wide. `has_timezones()` now reads `wc_appointments_timezone_support_enabled` (default `true`). Removed `get_customer_timezones` / `set_customer_timezones`, related data store mapping, admin and REST fields, and WCML/Polylang sync for that setting.
Fixed
- Admin timezone display: `appointments_time_zone` cookie no longer applies in admin/REST. Only staff (providers) may use profile timezone in admin; admins and shop managers always see site timezone. Remote staff can still set profile timezone.
- Admin create flow, existing order: order number no longer appears twice in Select2 and the selected chip—both prefixed `#` + internal ID onto labels that already included the display order number.
- Legacy calendar staff view: staffed appointments no longer also list under Unassigned—`$is_creator` matched `creator_id === 0` because `(int) 'unassigned' === 0`; creator shortcut now requires non-zero `creator_id`.
- Manual appointment on existing order (admin AJAX): line meta used public `appointment_id`, so emails/item meta showed an extra row. New rows use `_appointment_id` only (checkout parity); formatted order item meta strips legacy public `appointment_id` for display.
- Admin browse day slots: discounted `price.display` (` ` / ` `) was React-escaped; `.wca-slot-price` now renders with `dangerouslySetInnerHTML` (same as panel footer).
- Recurring `time:range`: same-day windows ending at **`00:00`** were misclassified as overnight (**string** order: **`00:00` < `08:00`**, **`24:00` < `08:00`**). `WC_Product_Appointment_Rule_Manager` / `WC_Appointment_Slots_Rule_Minutes` now use minute-based wrap helpers (`is_wrap_midnight_hhmm_pair` / `is_wrap_midnight_time_pair`); `evaluate_rule_for_timestamp` shares the same bounds. Availability cache indexing aligns with slot rules for **`00:00`** end-of-day, maps **`24:00` → 1440**, and sets same-day **`eff_to_min = 1440`** when needed. **`time:range` overnight** at midnight merges evening (**from–24:00**) with morning (**00:00–to**) when the prior day is in range (**`RuleMinutesMidnightWrapTest`**).
- Unified slots + `woocommerce_appointments_slots_display_pricing`: `WC_Appointment_Slots_Pricing::compute_slot_price_raw()` returns the full filtered slot price when the filter runs (not only when below default). `WC_Appointment_Slots_Collection::serialize_slots()` writes `price_data.display` / `price_formatted` HTML; the lazy formatter had read a non-array `price` from `jsonSerialize()`, so strikethrough/sale never reached unified JSON.
- Availability rules: changing `range_type` (e.g. Date range with time → Days) clears unused `from_date` / `to_date` via `apply_json_rule_to_availability()` (`time:range`, `custom:daterange`, `store_availability` still keep dates), so weekly/monthly rules are not skewed by stale columns.
- Indexed availability cache: indexer ignores stray `from_date` / `to_date` on **`days`**, **`months`**, **`weeks`** (same as recurring `time` / `time:N`), so corrupt rows no longer shrink the cache window until re-saved.
- Admin appointment save (`WC_Appointment_Meta_Box_Data::meta_box_save`): failed start/end validation now sets `self::$saved_meta_box` before return (matches success path), avoiding duplicate admin errors or extra `save_post` work in the same request.
- Day-unit products when add-ons (or `appointment_form_posted_total_duration`) extend past base duration: `WC_Appointments_Controller::get_exact_available_capacity_for_slot()` no longer uses the multi-day “start row only” shortcut (it skipped later days) or `get_extended_range_available_capacity()` for that span (UTC civil-day mismatch caused false unavailability). Added `get_addon_extended_day_span_capacity()` (same UTC keys as `get_multi_day_available_capacity()`, min capacity across span days). Minute/hour add-on extension unchanged.
v5.3.6.1Unavailable
April 27, 20262.8 MBScanned
Improved
- What's New admin screen (`html-wc-appointments-whats-new.php`): **admin appointment panel** behavior is now described and illustrated clearly—by default the panel **anchors beside the selected calendar appointment** (prefers right, then left, centers only when neither side fits), with backdrop and roadmap/changelog wording aligned. This replaces misleading “side panel” / generic sidebar copy and SVGs that did not show the panel next to the trigger event.
Fixed
- Admin appointment save (`WC_Appointment_Meta_Box_Data::meta_box_save`): blank or invalid start/end date (or missing time when not all-day) no longer calls `mktime()` with non-integer parts (PHP 8+ TypeError / fatal). Invalid input shows a WooCommerce admin error and the appointment object is not updated on that request.
v5.3.5Unavailable
April 24, 20262.7 MBScanned
Improved
- Unified Slots Standardization (part 1): `WC_Appointment_Slot` embeds projected business/display TZ metadata, wall times, formatted labels, `status`/`bookable`, and selection helpers so storefronts render slots without extra timezone math.
- Slots provider/engine accept optional `display_timezone`; `WC_Appointment_Slot::project_display_fields()` centralizes projection; `resolve_display_timezone()` pins day/month and non-timezone products to business TZ and validates IANA zones for timed products.
- `WC_Appointment_Slots_Collection::get_day_summaries_by_display()` groups by `display_day`; calendar AJAX includes `display_day_summaries` while legacy `scheduled_days` remains for compatibility.
- `WC_Appointment_Day_Summary` JSON adds `display_day`, `display_date_formatted`, `selectable`, and first/last slot hints sourced from projected slots.
Fixed
- Bundled Product Add-Ons integration JS: strip esbuild's top `"use strict"` in `build-js-addons`; initialize `window.WC_PAO`; declare quickview `product_price`; broaden `PaoValidation` minify post-replace for renamed locals.
- Slots engine: time-slot start step again uses the product **interval** (base) when it differs from **duration** (e.g. 90 min service every 2 hours). 5.3.4.1’s `min(duration, interval)` grid for all minute/hour products had caused starts every 90m instead of every 120m in that case; duration only defines block length, not the cadence between start times.
Why archive
Sometimes the latest release isn't the one you need. The archive lets you pin BookingWP - #1 WordPress Appointment Booking Plugin to a known-good version, or roll back while a bug is investigated. Every release stays scanned and reachable.