{"id":330419,"date":"2026-06-24T23:23:50","date_gmt":"2026-06-24T23:23:50","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/effortless-modular-booking-platform\/"},"modified":"2026-06-25T05:47:34","modified_gmt":"2026-06-25T05:47:34","slug":"effortless-modular-booking-platform","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/effortless-modular-booking-platform\/","author":23148025,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.1.28","stable_tag":"1.1.28","tested":"7.0","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"EffortLess Modular Booking Platform","header_author":"domclic","header_description":"A self-hosted, modular booking platform for WordPress.","assets_banners_color":"9e9e9e","last_updated":"2026-06-25 05:47:34","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":51,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","changelog"],"tags":{"1.1.27":{"tag":"1.1.27","author":"domclic","date":"2026-06-24 23:23:16"},"1.1.28":{"tag":"1.1.28","author":"domclic","date":"2026-06-25 05:47:34"}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.jpg":{"filename":"icon-128x128.jpg","revision":3585490,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.jpg":{"filename":"icon-256x256.jpg","revision":3585490,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.jpg":{"filename":"banner-1544x500.jpg","revision":3585490,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.jpg":{"filename":"banner-772x250.jpg","revision":3585490,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.1.27","1.1.28"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[262246],"plugin_tags":[8132,269,416,268,5349],"plugin_category":[40,45],"plugin_contributors":[241557],"plugin_business_model":[],"class_list":["post-330419","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-appointments","plugin_tags-booking","plugin_tags-calendar","plugin_tags-scheduling","plugin_tags-stripe","plugin_category-calendar-and-events","plugin_category-ecommerce","plugin_contributors-domclic","plugin_committers-domclic"],"banners":{"banner":"https:\/\/ps.w.org\/effortless-modular-booking-platform\/assets\/banner-772x250.jpg?rev=3585490","banner_2x":"https:\/\/ps.w.org\/effortless-modular-booking-platform\/assets\/banner-1544x500.jpg?rev=3585490","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/effortless-modular-booking-platform\/assets\/icon-128x128.jpg?rev=3585490","icon_2x":"https:\/\/ps.w.org\/effortless-modular-booking-platform\/assets\/icon-256x256.jpg?rev=3585490","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>Effortless Modular Booking Platform lets any service business accept appointments on their WordPress site. Features include service management, flexible availability scheduling, Stripe and pay-on-arrival payments, email confirmations with .ics calendar invites, and a lightweight shortcode-based booking form.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin folder to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate via Plugins menu.<\/li>\n<li>Go to Booking &gt; Settings to configure.<\/li>\n<\/ol>\n\n<!--section=changelog-->\n<h4>1.1.28<\/h4>\n\n<ul>\n<li>Add: donate notice displayed on all Booking admin screens (Services, Appointments, Agents, Settings, Network Settings).<\/li>\n<\/ul>\n\n<h4>1.1.27<\/h4>\n\n<ul>\n<li>Fix: replace all inline  and  tags in admin views with wp_enqueue_style \/ wp_add_inline_style and wp_add_inline_script via admin_enqueue_scripts.<\/li>\n<li>Fix: add explicit current_user_can( 'manage_options' ) checks in admin view files for defense-in-depth.<\/li>\n<li>Fix: agents tab system was toggling .is-active class instead of .active; now consistent with admin.css.<\/li>\n<\/ul>\n\n<h4>1.1.26<\/h4>\n\n<ul>\n<li>Fix: remove PROJECT.md from plugin root (flagged as unexpected file by WordPress Plugin Check).<\/li>\n<\/ul>\n\n<h4>1.1.25<\/h4>\n\n<ul>\n<li>Remove: Stripe payment integration removed (no vendor\/ dependency required).<\/li>\n<li>Remove: Payments tab from Settings page.<\/li>\n<li>Remove: Stripe option from service payment type; only Free and Pay on arrival remain.<\/li>\n<\/ul>\n\n<h4>1.1.24<\/h4>\n\n<ul>\n<li>Fix: replaced all interpolated table names in DB queries with $wpdb-&gt;prepare() %i placeholders (WP 6.2+).<\/li>\n<li>Fix: removed all phpcs:disable\/enable blocks; replaced with minimal per-line phpcs:ignore where unavoidable.<\/li>\n<li>Bump: Requires at least 6.2 (for %i placeholder support).<\/li>\n<\/ul>\n\n<h4>1.1.23<\/h4>\n\n<ul>\n<li>Fix: removed load_plugin_textdomain() call (auto-loaded by WordPress.org since WP 4.6).<\/li>\n<li>Fix: removed suppress_filters from WP_Query\/get_posts calls.<\/li>\n<li>Fix: added phpcs suppression for table-name DB params (internally trusted values).<\/li>\n<li>Fix: added phpcs suppression for global variable naming in admin view templates.<\/li>\n<\/ul>\n\n<h4>1.1.22<\/h4>\n\n<ul>\n<li>Fix: updated \"Tested up to\" to WordPress 6.8; removed PROJECT.md from distributed zip.<\/li>\n<\/ul>\n\n<h4>1.1.21<\/h4>\n\n<ul>\n<li>Fix: removed duplicate Plugin URI header (was identical to Author URI); only Author URI is kept.<\/li>\n<\/ul>\n\n<h4>1.1.20<\/h4>\n\n<ul>\n<li>UX: agent edit page reorganised into four tabs \u2014 Timezone, Weekly Hours, Time Off, Services \u2014 using WordPress native nav-tabs. Active tab is remembered via URL hash. Time Off form fields use a flex row layout with labelled columns.<\/li>\n<\/ul>\n\n<h4>1.1.19<\/h4>\n\n<ul>\n<li>Feature: time off now supports date ranges. Select a \"From\" and \"To\" date to block an entire period in one step. Each day in the range is stored individually so existing slot logic is unchanged. Time inputs are hidden automatically when a range is selected (full-day only for ranges). Applies to both Agents and the global Availability screen.<\/li>\n<\/ul>\n\n<h4>1.1.18<\/h4>\n\n<ul>\n<li>i18n: add \"at\" \u2192 \"\u00e0\/a las\/um\/\u306b\/\uc5d0\/\u65bc\" translations to all six bundled locales for the booking confirmation message.<\/li>\n<\/ul>\n\n<h4>1.1.17<\/h4>\n\n<ul>\n<li>Fix: booking confirmation message now shows the date in the site language (e.g. \"13 juin 2026 \u00e0 15:30\" on a French site) instead of the raw ISO date and hardcoded English \"at\".<\/li>\n<\/ul>\n\n<h4>1.1.16<\/h4>\n\n<ul>\n<li>Fix: all email types (customer, cancellation, agent, admin) now use the site language for date and time formatting. Previously customer and cancellation emails used the visitor's browser locale, and agent emails used the agent's WordPress user locale, causing dates like \"June 12, 2026\" on French sites.<\/li>\n<\/ul>\n\n<h4>1.1.15<\/h4>\n\n<ul>\n<li>Fix: admin notification emails now use the site locale for date and time formatting (e.g. \"12 juin 2025\" on a French site). Previously the locale was not switched, so month names always appeared in the request locale (often English).<\/li>\n<\/ul>\n\n<h4>1.1.14<\/h4>\n\n<ul>\n<li>Fix: use post_name__in to also detect trashed templates (WordPress renames post_name to {slug}__trashed on trash), preventing indefinite recreation.<\/li>\n<\/ul>\n\n<h4>1.1.13<\/h4>\n\n<ul>\n<li>Fix: email templates no longer recreated after deletion \u2014 existence check now includes trash status.<\/li>\n<li>Fix: removed template auto-creation from new-site activation to prevent English templates appearing on non-English subsites; admin_init handles creation with the correct locale on first admin visit.<\/li>\n<\/ul>\n\n<h4>1.1.12<\/h4>\n\n<ul>\n<li>Email templates are now stored as a Custom Post Type (Booking \u2192 Email Templates). Translators can edit templates per site in the standard WordPress editor. In multisite global mode, each booking uses the templates from the site where the booking was made. The Settings \u2192 Emails tab has been removed.<\/li>\n<\/ul>\n\n<h4>1.1.11<\/h4>\n\n<ul>\n<li>Emails to customers are now sent in the customer's browser locale; emails to agents are sent in the agent's WordPress user locale. Locale-aware date and time formatting (month names, weekday names) applies to all recipient types.<\/li>\n<li>A new customer_locale field is stored at booking time (captured from navigator.language in the booking form).<\/li>\n<\/ul>\n\n<h4>1.1.10<\/h4>\n\n<ul>\n<li>Availability menu item removed \u2014 availability is now managed exclusively per-agent in Booking \u2192 Agents.<\/li>\n<li>Services edit\/add forms split into Details and Booking tabs for clearer organisation.<\/li>\n<\/ul>\n\n<h4>1.1.9<\/h4>\n\n<ul>\n<li>Slot Buffer, Booking Window, and Cancellation Cutoff are now per-service settings (configured in Services &gt; Edit &gt; Details).<\/li>\n<li>Availability (weekly schedule, blocked dates) is now managed per-agent only.<\/li>\n<\/ul>\n\n<h4>1.1.8<\/h4>\n\n<ul>\n<li>Fix: appointment times are now stored as UTC in two new database columns\n(start_utc, end_utc). A third column (agent_timezone_at_booking) snapshots\nthe agent's timezone at booking time, so emails remain correct even if the\nagent later changes their timezone (e.g. when travelling). Existing\nappointments are migrated automatically on the next admin load. The .ics\ncalendar invite also uses the UTC values directly.<\/li>\n<\/ul>\n\n<h4>1.1.7<\/h4>\n\n<ul>\n<li>Fix: confirmation, cancellation, and agent emails now display times in the correct timezone. Agent emails show the agent's own timezone; customer emails show the customer's browser timezone (stored at booking time). A new {timezone} placeholder is available in all email templates.<\/li>\n<\/ul>\n\n<h4>1.1.6<\/h4>\n\n<ul>\n<li>Slot times display in 24h format. Visitor timezone shown once as a small label below all slot buttons.<\/li>\n<\/ul>\n\n<h4>1.1.5<\/h4>\n\n<ul>\n<li>Slot buttons now display the visitor's timezone as a small tag inside each button, replacing the standalone note below the grid.<\/li>\n<\/ul>\n\n<h4>1.1.4<\/h4>\n\n<ul>\n<li>Fix: submitting the Timezone form no longer wipes all agent availability windows. The timezone form now posts to a dedicated <code>elmbp_save_agent_timezone<\/code> handler instead of sharing the weekly-hours handler, which was clearing windows when <code>avail_active<\/code> was absent from the request.<\/li>\n<\/ul>\n\n<h4>1.1.3<\/h4>\n\n<ul>\n<li>Each agent now has a configurable timezone (IANA identifier).<\/li>\n<li>Booking form displays available slots converted to the visitor's local timezone.<\/li>\n<li>Slots that have already passed (in the visitor's timezone) are hidden.<\/li>\n<\/ul>\n\n<h4>1.1.2<\/h4>\n\n<ul>\n<li>Fix: email {date} and {time} placeholders now show the correct local time on sites not in UTC \u2014 previously strtotime() parsed appointment times as UTC, shifting every email by the site's UTC offset. Added {raw_date} (YYYY-MM-DD) and {raw_time} (HH:MM) as stable, machine-readable alternatives.<\/li>\n<li>Fix: WordPress locales that include a script-variant suffix (e.g. sr_RS@latin) are now sanitised before being passed to the browser, preventing the JS Intl API from discarding the locale and falling back silently to the visitor's language.<\/li>\n<li>Fix: the falsy-zero guard on the timestamp check now uses strict false !== comparison.<\/li>\n<li>Improvement: the weekday-header Intl.DateTimeFormat object is now created once per calendar instead of once per cell.<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Fix: the booking calendar now formats dates in the site language (month name, weekday headers, and selected-date label) instead of the visitor's browser locale. Confirmation\/admin\/agent emails now show {date} and {time} in the site's configured date\/time format and language.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>New: Agents \/ team scheduling. Assign multiple WordPress users as agents to a service (Booking \u2192 Agents); each agent has their own weekly hours, time off, and assigned services. A slot is offered when any assigned agent is free, and the system auto-assigns the least-loaded free agent at booking. The assigned agent is emailed (new Agent Notification template; {agent_name} placeholder). Services with no agents keep the single-schedule behaviour.<\/li>\n<li>New: per-service availability. Each service can have its own weekly schedule and blocked dates (Services \u2192 edit \u2192 Availability tab); the Booking \u2192 Availability page is now the default used when a service defines none. Precedence: agents \u2192 per-service schedule \u2192 global default.<\/li>\n<li>Appointments screen shows the assigned agent and allows reassigning to another of the service's agents.<\/li>\n<li>All new admin\/email strings translated into the six bundled locales (fr, es, ko, ja, de, zh_TW).<\/li>\n<\/ul>\n\n<h4>1.0.11<\/h4>\n\n<ul>\n<li>Fix: enabling Global mode now creates the shared (network) tables immediately, instead of waiting for the next admin page load \u2014 so services and availability saved right after switching land in the correct store.<\/li>\n<li>New: admin notices on the Booking screens show the active data mode (on multisite) and warn when no availability is configured (which is why a booking calendar shows all dates greyed out).<\/li>\n<\/ul>\n\n<h4>1.0.10<\/h4>\n\n<ul>\n<li>Change: on multisite, the Global\/Per-site mode toggle and all global settings now live in Network Admin \u2192 Settings \u2192 Booking. The per-site Booking \u2192 Settings page only shows settings in Per-site mode (or on single-site); in Global mode it points to the network settings.<\/li>\n<li>Note: in Global mode, services and availability are shared network-wide. After enabling Global mode you must (re)configure Availability while in Global mode \u2014 existing per-site data is not copied into the shared store, so an empty shared schedule shows no bookable dates.<\/li>\n<\/ul>\n\n<h4>1.0.9<\/h4>\n\n<ul>\n<li>New (multisite): \"Booking data mode\" setting (Booking \u2192 Settings \u2192 General, super admins only) to run bookings Per site or Global. In Global mode all sites share the same services, availability, appointments and settings. Stored as a network option; single-site installs are unaffected.<\/li>\n<\/ul>\n\n<h4>1.0.8<\/h4>\n\n<ul>\n<li>Change: the booking form now opens on the calendar with the available dates to pick (positioned on, and highlighting, the first available date) instead of jumping straight into that day's time slots. Time slots load when a date is clicked.<\/li>\n<\/ul>\n\n<h4>1.0.7<\/h4>\n\n<ul>\n<li>New: cancel an appointment directly from the Appointments admin screen \u2014 a \"Cancel\" row action (and the Change Status dropdown) now sets it to cancelled and emails the customer the cancellation notice. Translated into all bundled languages.<\/li>\n<\/ul>\n\n<h4>1.0.6<\/h4>\n\n<ul>\n<li>New: translations for French (fr_FR), Spanish (es_ES), Korean (ko_KR), Japanese (ja), German (de_DE), and Taiwan Mandarin (zh_TW), including the customer\/admin emails. Front-end strings are translated server-side, so the booking form follows the site\/user language.<\/li>\n<li>Added load_plugin_textdomain() so the bundled translations in \/languages load automatically.<\/li>\n<\/ul>\n\n<h4>1.0.5<\/h4>\n\n<ul>\n<li>Fix (critical): booking submission crashed with a fatal error \u2014 wp_tempnam() (used for the .ics attachment) is not available during front-end REST requests. The calendar invite is now written with front-end-safe functions.<\/li>\n<li>Hardened multisite new-site activation against a similar admin-only function being unavailable.<\/li>\n<li>New: the booking form now opens on the next available date and shows that day's time slots immediately, even when it falls in a later month.<\/li>\n<li>New: popup mode \u2014 [effortless_booking service=\"1\" popup=\"yes\" button_text=\"Book now\"] renders a button that opens the booking form in a modal. Inline remains the default.<\/li>\n<\/ul>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Change: the \"Quick Start\" welcome notice now appears only on the Booking admin pages instead of across all of wp-admin.<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>Fix: booking form showed a permanent loading spinner over the calendar. The loading overlay's <code>hidden<\/code> attribute was being overridden by its own <code>display: flex<\/code> rule, so it never hid. The <code>hidden<\/code> attribute is now authoritative inside the widget.<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Fix: services (and other data) silently failing to save when a site's database tables were missing \u2014 common on multisite sub-sites. Tables are now self-healed on every admin load via a schema-version check.<\/li>\n<li>Fix: the Services screen now reports the real database error instead of always showing \"Service created.\" on failure.<\/li>\n<li>Fix: dashboard widget styles and copy button now load correctly on the WordPress dashboard.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Multisite network activation support \u2014 tables created for all sites on network activation and for new sites when added.<\/li>\n<li>Dashboard widget listing all active services with one-click copy-to-clipboard shortcodes.<\/li>\n<li>Welcome \/ quick-start admin notice shown after activation with shortcode usage instructions.<\/li>\n<li>Copy button added to each shortcode in the Services admin table.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<\/ul>","raw_excerpt":"A self-hosted, modular booking platform for WordPress.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/330419","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=330419"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/domclic"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=330419"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=330419"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=330419"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=330419"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=330419"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=330419"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}