Changeset 3612903
- Timestamp:
- 07/18/2026 06:45:54 PM (3 months ago)
- Location:
- easypeasy-booking/trunk
- Files:
-
- 12 edited
-
admin/class-epbk-admin.php (modified) (3 diffs)
-
api/class-epbk-rest-availability.php (modified) (2 diffs)
-
api/class-epbk-rest-bookings.php (modified) (4 diffs)
-
api/class-epbk-rest-settings.php (modified) (2 diffs)
-
assets/css/frontend.css (modified) (2 diffs)
-
assets/js/admin.js (modified) (1 diff)
-
assets/js/booking-widget.js (modified) (7 diffs)
-
easypeasy-booking.php (modified) (4 diffs)
-
frontend/class-epbk-frontend.php (modified) (2 diffs)
-
readme.txt (modified) (4 diffs)
-
services/class-epbk-availability-engine.php (modified) (1 diff)
-
services/class-epbk-notification-service.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
easypeasy-booking/trunk/admin/class-epbk-admin.php
r3607778 r3612903 113 113 ); 114 114 115 // 'wp-hooks' gives add-ons a standard way to filter the admin UI 116 // (wp.hooks.addFilter('epbk.bookings.columns', ...)). 115 117 wp_enqueue_script( 116 118 'ebi-admin', 117 119 EPBK_PLUGIN_URL . 'assets/js/admin.js', 118 array( ),120 array('wp-hooks'), 119 121 (string) filemtime(EPBK_PLUGIN_DIR . 'assets/js/admin.js'), 120 122 true … … 125 127 // phpcs:enable WordPress.Security.NonceVerification.Recommended 126 128 129 /** 130 * Filter the payment methods offered by the plugin. 131 * 132 * Add-ons can register their own gateway here; the key is stored in the 133 * `payment_method` setting and the value is the label shown in the UI. 134 * 135 * @param array $methods Map of method key => display label. 136 */ 137 $payment_methods = apply_filters('epbk_payment_methods', array( 138 'none' => __('Cash Payments', 'easypeasy-booking'), 139 'woocommerce' => __('WooCommerce', 'easypeasy-booking'), 140 )); 141 127 142 // Pass config to JS. 128 wp_localize_script('ebi-admin', 'ebiAdmin', array( 143 $config = array( 144 'paymentMethods' => $payment_methods, 129 145 'restUrl' => esc_url_raw(rest_url('epbk/v1/')), 130 146 'nonce' => wp_create_nonce('wp_rest'), … … 147 163 'noResults' => __('No results found.', 'easypeasy-booking'), 148 164 ), 149 )); 165 ); 166 167 /** 168 * Filter the configuration object passed to the admin app. 169 * 170 * Add-ons can inject their own settings here (for example a flag that 171 * turns on a Pro feature's UI) for the JavaScript admin to read. 172 * 173 * @param array $config The admin configuration. 174 */ 175 wp_localize_script('ebi-admin', 'ebiAdmin', apply_filters('epbk_admin_config', $config)); 150 176 } 151 177 -
easypeasy-booking/trunk/api/class-epbk-rest-availability.php
r3607778 r3612903 97 97 $slots = EPBK_Availability_Engine::get_available_slots($service_id, $date); 98 98 99 /** 100 * Filter the slots returned by the REST endpoint, with request context. 101 * 102 * Unlike the engine-level 'epbk_available_slots' filter, this one has the 103 * full request, so add-ons can react to extra parameters (for example a 104 * requested staff member) sent by the booking widget. 105 * 106 * @param array $slots The available slots. 107 * @param int $service_id Service ID. 108 * @param string $date Date (Y-m-d). 109 * @param WP_REST_Request $request The request. 110 */ 111 $slots = apply_filters('epbk_rest_available_slots', $slots, $service_id, $date, $request); 112 99 113 return new \WP_REST_Response(array('slots' => $slots, 'date' => $date), 200); 100 114 } … … 117 131 118 132 $dates = EPBK_Availability_Engine::get_available_dates($service_id, $year, $month); 133 134 /** 135 * Filter the available dates returned by the REST endpoint. 136 * 137 * @param array $dates Map of date => bool. 138 * @param int $service_id Service ID. 139 * @param WP_REST_Request $request The request. 140 */ 141 $dates = apply_filters('epbk_rest_available_dates', $dates, $service_id, $request); 119 142 120 143 return new \WP_REST_Response(array('dates' => $dates, 'year' => $year, 'month' => $month), 200); -
easypeasy-booking/trunk/api/class-epbk-rest-bookings.php
r3607778 r3612903 137 137 ); 138 138 139 /** 140 * Filter the bookings returned by the admin list endpoint. 141 * 142 * Add-ons can enrich each row with their own data (for example, the 143 * assigned staff member's name) before it reaches the admin UI. 144 * 145 * @param array $bookings The booking rows. 146 */ 147 $bookings = apply_filters('epbk_bookings_response', $bookings); 148 139 149 return new \WP_REST_Response(array( 140 150 'bookings' => $bookings, … … 256 266 $end_dt = new \DateTime($booking_date . ' ' . $booking_time); 257 267 $end_dt->modify("+{$duration} minutes"); 268 269 /** 270 * Filter to let add-ons reject a booking before it is created. 271 * 272 * Return a non-empty string (a customer-facing message) to block the 273 * booking; return an empty string to allow it. Used, for example, to 274 * check that a requested staff member is actually free. 275 * 276 * @param string $error Empty to allow; message to reject. 277 * @param WP_REST_Request $request The booking request. 278 * @param array $service The service row. 279 */ 280 $epbk_validation_error = apply_filters('epbk_validate_booking', '', $request, $service); 281 if (!empty($epbk_validation_error)) { 282 return new \WP_REST_Response(array('message' => $epbk_validation_error), 409); 283 } 258 284 259 285 // Insert booking. … … 295 321 ); 296 322 323 /** 324 * Fires immediately after a booking row is created. 325 * 326 * Add-ons can store their own related records here (for example a staff 327 * assignment) keyed by the new booking ID, without altering this table. 328 * 329 * @param int $booking_id The new booking ID. 330 * @param array $booking_data The booking data. 331 * @param array $service The service row. 332 * @param WP_REST_Request $request The original request. 333 */ 334 do_action('epbk_booking_created', $booking_id, $booking_data, $service, $request); 335 297 336 // Handle payment. 298 337 $settings = get_option('epbk_settings', array()); … … 320 359 'status' => 'confirmed', 321 360 ), 201); 361 } 362 363 /** 364 * Let an add-on take over the payment step for its own payment method. 365 * 366 * Return a WP_REST_Response to handle the payment (for example, to send 367 * the customer to a hosted checkout). Return null to fall through to the 368 * built-in WooCommerce / pay-at-venue handling. 369 * 370 * @param null|WP_REST_Response $response Null to fall through. 371 * @param string $payment_method The configured method. 372 * @param array $booking_data The booking data. 373 * @param array $service The service row. 374 * @param int $booking_id The new booking ID. 375 */ 376 $epbk_payment_response = apply_filters( 377 'epbk_process_payment', 378 null, 379 $payment_method, 380 $booking_data, 381 $service, 382 $booking_id 383 ); 384 if ($epbk_payment_response instanceof \WP_REST_Response) { 385 return $epbk_payment_response; 322 386 } 323 387 -
easypeasy-booking/trunk/api/class-epbk-rest-settings.php
r3607778 r3612903 173 173 ); 174 174 175 return new \WP_REST_Response(array(175 $stats = array( 176 176 'total_bookings' => $total_bookings, 177 177 'today_bookings' => $today_bookings, … … 181 181 'active_services' => $active_services, 182 182 'upcoming_bookings' => $upcoming_bookings, 183 ), 200); 183 ); 184 185 /** 186 * Filter the dashboard statistics. 187 * 188 * Add-ons can add their own figures here (for example voucher sales or 189 * revenue) so they appear alongside the built-in stats. 190 * 191 * @param array $stats The dashboard statistics. 192 */ 193 return new \WP_REST_Response(apply_filters('epbk_dashboard_stats', $stats), 200); 184 194 } 185 195 } -
easypeasy-booking/trunk/assets/css/frontend.css
r3607778 r3612903 282 282 border-radius: 10px; 283 283 background: #fff; 284 color: #1e293b; 284 285 font-size: 14px; 285 286 font-weight: 500; … … 652 653 .ebi-services-list[data-columns="4"] { 653 654 grid-template-columns: repeat(4, 1fr); 655 } 656 657 /* Service cards rendered by booking-widget.js into #ebi-services-list */ 658 .ebi-service-card { 659 display: flex; 660 flex-direction: column; 661 justify-content: space-between; 662 gap: 12px; 663 padding: 20px; 664 background: #fff; 665 border: 1px solid #e2e8f0; 666 border-radius: 12px; 667 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); 668 transition: box-shadow 0.2s, transform 0.2s; 669 } 670 671 .ebi-service-card:hover { 672 box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); 673 transform: translateY(-2px); 674 } 675 676 .ebi-service-card__name { 677 margin: 0 0 6px; 678 font-size: 17px; 679 font-weight: 600; 680 color: #1e293b; 681 } 682 683 .ebi-service-card__desc { 684 margin: 0; 685 font-size: 14px; 686 line-height: 1.6; 687 color: #64748b; 688 } 689 690 .ebi-service-card__meta { 691 display: flex; 692 align-items: baseline; 693 justify-content: space-between; 694 gap: 8px; 695 padding-top: 12px; 696 border-top: 1px solid #f1f5f9; 697 } 698 699 .ebi-service-card__price { 700 font-size: 18px; 701 font-weight: 700; 702 color: #2e7d32; 703 } 704 705 .ebi-service-card__duration { 706 font-size: 13px; 707 color: #94a3b8; 708 } 709 710 .ebi-services-empty { 711 grid-column: 1 / -1; 712 margin: 0; 713 padding: 24px; 714 text-align: center; 715 color: #64748b; 716 font-size: 14px; 654 717 } 655 718 -
easypeasy-booking/trunk/assets/js/admin.js
r3607778 r3612903 840 840 card.appendChild(el('h3', {}, '💳 Payment Method')); 841 841 var paySelect = el('select', { className: 'ebi-select', onChange: function (e) { form.payment_method = e.target.value; } }); 842 var payLabels = { woocommerce: 'WooCommerce', none: 'Cash Payments' }; 843 ['none', 'woocommerce'].forEach(function (m) { 844 var opt = el('option', { value: m }, payLabels[m] || m); 842 // Payment methods come from the server (config.paymentMethods) so add-ons 843 // can register a gateway via the 'epbk_payment_methods' PHP filter. 844 var payMethods = config.paymentMethods || { none: 'Cash Payments', woocommerce: 'WooCommerce' }; 845 Object.keys(payMethods).forEach(function (m) { 846 var opt = el('option', { value: m }, payMethods[m]); 845 847 if (form.payment_method === m) opt.selected = true; 846 848 paySelect.appendChild(opt); -
easypeasy-booking/trunk/assets/js/booking-widget.js
r3607778 r3612903 90 90 } 91 91 92 // Extra query string that add-ons can append to availability requests 93 // (e.g. a selected staff member). Returns '' unless a filter provides more. 94 function epbkExtraParams() { 95 if (window.wp && wp.hooks) { 96 return wp.hooks.applyFilters('epbk.widget.availabilityParams', '', state); 97 } 98 return ''; 99 } 100 92 101 async function loadDates() { 93 102 if (!state.selectedService) return; … … 95 104 render(); 96 105 try { 97 const data = await api.get(`availability/dates?service_id=${state.selectedService.id}&year=${state.currentYear}&month=${state.currentMonth + 1} `);106 const data = await api.get(`availability/dates?service_id=${state.selectedService.id}&year=${state.currentYear}&month=${state.currentMonth + 1}${epbkExtraParams()}`); 98 107 state.availableDates = data.dates || {}; 99 108 } catch (e) { … … 111 120 render(); 112 121 try { 113 const data = await api.get(`availability/slots?service_id=${state.selectedService.id}&date=${date} `);122 const data = await api.get(`availability/slots?service_id=${state.selectedService.id}&date=${date}${epbkExtraParams()}`); 114 123 let slots = data.slots || []; 115 124 … … 359 368 }); 360 369 document.getElementById('ebi-back-1')?.addEventListener('click', () => { state.step = 1; state.selectedService = null; render(); }); 370 371 // Extension point: add-ons can inject UI into the date step (for example 372 // a staff-member picker) and call reloadDates() when their choice changes. 373 if (window.wp && wp.hooks) { 374 wp.hooks.doAction('epbk.widget.dateStep', container, state, { reloadDates: loadDates }); 375 } 361 376 } 362 377 … … 456 471 457 472 try { 458 const result = await api.post('bookings',{473 let payload = { 459 474 service_id: state.selectedService.id, 460 475 customer_name: name, … … 465 480 notes: notes, 466 481 epbk_hp: hp, 467 }); 482 }; 483 484 // Extension point: add-ons can add fields to the booking payload 485 // (for example a chosen staff_id) before it is submitted. 486 if (window.wp && wp.hooks) { 487 payload = wp.hooks.applyFilters('epbk.widget.bookingPayload', payload, state); 488 } 489 490 const result = await api.post('bookings', payload); 468 491 469 492 if (result.checkout_url) { … … 507 530 } 508 531 })(); 532 533 /** 534 * EasyPeasy Booking — Services List 535 * 536 * Renders the [epbk_services] shortcode / ebi/service-list block. Fetches 537 * services from the REST API and populates #ebi-services-list. This runs as 538 * its own IIFE so it works on pages that show only the services list (the 539 * booking-widget IIFE above returns early when #ebi-booking-widget is absent). 540 */ 541 (function () { 542 'use strict'; 543 544 const list = document.getElementById('ebi-services-list'); 545 if (!list) return; 546 547 const cfg = window.ebiFrontend || {}; 548 const restUrl = cfg.restUrl; 549 const nonce = cfg.nonce; 550 const currency = cfg.currency || '€'; 551 const i18n = cfg.i18n || {}; 552 if (!restUrl) return; 553 554 const filterCategory = list.dataset.category || ''; 555 556 function escHtml(str) { 557 const div = document.createElement('div'); 558 div.textContent = str == null ? '' : String(str); 559 return div.innerHTML; 560 } 561 562 function priceLabel(price) { 563 const p = parseFloat(price); 564 if (isNaN(p) || p <= 0) return i18n.free || 'Free'; 565 return currency + p.toFixed(2); 566 } 567 568 function cardHtml(s) { 569 const desc = s.description 570 ? `<p class="ebi-service-card__desc">${escHtml(s.description)}</p>` 571 : ''; 572 const duration = s.duration 573 ? `<span class="ebi-service-card__duration">${escHtml(s.duration)} ${escHtml(i18n.minutes || 'min')}</span>` 574 : ''; 575 return ` 576 <div class="ebi-service-card"> 577 <div class="ebi-service-card__body"> 578 <h3 class="ebi-service-card__name">${escHtml(s.name)}</h3> 579 ${desc} 580 </div> 581 <div class="ebi-service-card__meta"> 582 <span class="ebi-service-card__price">${priceLabel(s.price)}</span> 583 ${duration} 584 </div> 585 </div>`; 586 } 587 588 async function load() { 589 try { 590 const res = await fetch(restUrl + 'services/grouped', { 591 cache: 'no-store', 592 headers: { 'X-WP-Nonce': nonce }, 593 }); 594 if (!res.ok) throw new Error('HTTP ' + res.status); 595 const data = await res.json(); 596 597 const categories = Array.isArray(data) ? data : (data.categories || []); 598 599 // Flatten active services, honouring an optional category filter. 600 const services = []; 601 categories.forEach(cat => { 602 if (filterCategory && cat.slug !== filterCategory) return; 603 (cat.services || []).forEach(s => { 604 if (!s.status || s.status === 'active') services.push(s); 605 }); 606 }); 607 608 if (services.length === 0) { 609 list.innerHTML = '<p class="ebi-services-empty">' 610 + escHtml(i18n.noServices || 'No services to display yet.') 611 + '</p>'; 612 return; 613 } 614 615 list.innerHTML = services.map(cardHtml).join(''); 616 } catch (e) { 617 list.innerHTML = '<p class="ebi-services-empty">' 618 + escHtml(i18n.error || 'Unable to load services.') 619 + '</p>'; 620 } 621 } 622 623 load(); 624 })(); -
easypeasy-booking/trunk/easypeasy-booking.php
r3607778 r3612903 4 4 * Plugin URI: https://easypeasybooking.irish/ 5 5 * Description: A simple, commission-free booking and appointments system for service businesses. Works with or without WooCommerce. 6 * Version: 1. 0.06 * Version: 1.1.0 7 7 * Author: EasyPeasy Booking 8 8 * License: GPL v2 or later … … 22 22 23 23 // Plugin constants. 24 define('EPBK_VERSION', '1. 0.0');24 define('EPBK_VERSION', '1.1.0'); 25 25 define('EPBK_PLUGIN_FILE', __FILE__); 26 26 define('EPBK_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 138 138 $controller->register_routes(); 139 139 } 140 141 /** 142 * Fires after the core REST routes are registered. 143 * 144 * Add-ons should register their own routes on the 'epbk/v1' namespace here. 145 */ 146 do_action('epbk_register_rest_routes'); 140 147 }); 141 148 … … 178 185 // Confirmation and cancellation page rendering. 179 186 add_action('template_redirect', 'epbk_handle_frontend_pages'); 187 188 /** 189 * Fires when EasyPeasy Booking has finished initializing. 190 * 191 * The public entry point for add-ons (such as EasyPeasy Booking Pro). At this 192 * point all core classes are loaded and hooks are available. 193 * 194 * @param string $version The plugin version. 195 */ 196 do_action('epbk_loaded', EPBK_VERSION); 180 197 } 181 198 add_action('plugins_loaded', 'epbk_init'); -
easypeasy-booking/trunk/frontend/class-epbk-frontend.php
r3607778 r3612903 54 54 ); 55 55 56 // 'wp-hooks' lets add-ons extend the booking widget from their own 57 // script (wp.hooks.addFilter('epbk.widget.steps', ...)). 56 58 wp_enqueue_script( 57 59 'ebi-booking-widget', 58 60 EPBK_PLUGIN_URL . 'assets/js/booking-widget.js', 59 array( ),61 array('wp-hooks'), 60 62 (string) filemtime(EPBK_PLUGIN_DIR . 'assets/js/booking-widget.js'), 61 63 true 62 64 ); 63 65 66 // Let add-ons enqueue their own frontend scripts that depend on the widget. 67 do_action('epbk_enqueue_frontend_assets'); 68 64 69 $settings = get_option('epbk_settings', array()); 65 70 66 wp_localize_script('ebi-booking-widget', 'ebiFrontend', array( 71 /** 72 * Filter the configuration passed to the front-end booking widget. 73 * 74 * Add-ons can add data here (for example, whether a staff picker should 75 * be shown) for the widget script to read. 76 * 77 * @param array $config The widget configuration. 78 * @param array $settings The plugin settings. 79 */ 80 $frontend_config = apply_filters('epbk_frontend_config', array( 67 81 'restUrl' => esc_url_raw(rest_url('epbk/v1/')), 68 82 'nonce' => wp_create_nonce('wp_rest'), … … 91 105 'perNight' => __('per night', 'easypeasy-booking'), 92 106 ), 93 )); 107 ), $settings); 108 109 wp_localize_script('ebi-booking-widget', 'ebiFrontend', $frontend_config); 94 110 95 111 $this->assets_enqueued = true; -
easypeasy-booking/trunk/readme.txt
r3607778 r3612903 6 6 Tested up to: 7.0 7 7 Requires PHP: 8.0 8 Stable tag: 1. 0.08 Stable tag: 1.1.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 33 33 * Optional logo and brand colour for the booking page and emails 34 34 * Built to WordPress standards — sanitised input, escaped output, prepared queries, capability checks, and rate-limited public endpoints 35 36 = Premium add-on = 37 38 A separate premium add-on, EasyPeasy Booking Pro, extends this plugin with staff management, gift vouchers, 24-hour booking reminders, managed card payments (Stripe), and branded email delivery. It is optional — everything above works fully without it. Details at https://easypeasybooking.irish/ 35 39 36 40 = Privacy = … … 80 84 == Changelog == 81 85 86 = 1.1.0 = 87 * Added an extension API (action and filter hooks) so add-ons can extend availability, bookings, payments, emails and the admin/booking-widget UI. 88 * Fixed: the [epbk_services] shortcode and Service List block now display your services (the list previously rendered empty). 89 * Fixed: booking widget time slots are now readable on themes that style button text white (e.g. GeneratePress). 90 82 91 = 1.0.0 = 83 92 * Initial release: services, bookings, availability, cash and WooCommerce payments, booking widget, shortcodes, Gutenberg blocks, and email notifications. … … 85 94 == Upgrade Notice == 86 95 96 = 1.1.0 = 97 Adds an extension API for add-ons and fixes the services list ([epbk_services] shortcode / Service List block) so it displays your services. 98 87 99 = 1.0.0 = 88 100 Initial release. -
easypeasy-booking/trunk/services/class-epbk-availability-engine.php
r3607778 r3612903 202 202 } 203 203 204 return $available_slots; 204 /** 205 * Filter the available time slots for a service on a date. 206 * 207 * Add-ons can use this to apply their own availability rules — for 208 * example, restricting slots to a particular staff member's roster, 209 * or recalculating capacity from a pool of resources. 210 * 211 * @param array $available_slots Slots, each: array{time, end_time, available}. 212 * @param int $service_id Service ID. 213 * @param string $date Date in Y-m-d format. 214 * @param array $context Extra context: service row, duration, buffer. 215 */ 216 return apply_filters( 217 'epbk_available_slots', 218 $available_slots, 219 $service_id, 220 $date, 221 array( 222 'service' => $service, 223 'duration' => $duration, 224 'buffer' => $buffer_time, 225 ) 226 ); 205 227 } 206 228 -
easypeasy-booking/trunk/services/class-epbk-notification-service.php
r3607778 r3612903 62 62 $from = $options['from'] ?? self::get_from_address(); 63 63 64 /** 65 * Let an add-on deliver this email instead of wp_mail(). 66 * 67 * Return true (delivered) or false (failed) to take over delivery — for 68 * example, to route the message through a branded transactional email 69 * service. Return null to fall through to the built-in wp_mail(). 70 * 71 * @param null|bool $handled Null to fall through to wp_mail(). 72 * @param string $to Recipient address. 73 * @param string $subject Subject line. 74 * @param string $html HTML body. 75 * @param string $from From header. 76 * @param string $type Template name (e.g. 'booking-confirmation'). 77 */ 78 $handled = apply_filters( 'epbk_send_email', null, $to, $subject, $html, $from, $type ); 79 if ( null !== $handled ) { 80 return (bool) $handled; 81 } 82 64 83 // Deliver via wp_mail(). 65 84 return self::send_via_wp_mail( $to, $subject, $html, $from ); … … 105 124 return null; 106 125 } 126 127 /** 128 * Filter the data made available to an email template. 129 * 130 * Add-ons can add their own placeholders here (for example, the name of 131 * the assigned staff member) for templates that render them. 132 * 133 * @param array $data The template data. 134 * @param string $type The template name. 135 */ 136 $data = apply_filters('epbk_email_template_data', $data, $type); 107 137 108 138 // Expose the raw values to the template as $escaped. Each template is
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)