Plugin Directory

Changeset 3612903


Ignore:
Timestamp:
07/18/2026 06:45:54 PM (3 months ago)
Author:
oscararroliga
Message:

Version 1.1.0 — extension API + fix services list and time-slot readability

Location:
easypeasy-booking/trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • easypeasy-booking/trunk/admin/class-epbk-admin.php

    r3607778 r3612903  
    113113        );
    114114
     115        // 'wp-hooks' gives add-ons a standard way to filter the admin UI
     116        // (wp.hooks.addFilter('epbk.bookings.columns', ...)).
    115117        wp_enqueue_script(
    116118            'ebi-admin',
    117119            EPBK_PLUGIN_URL . 'assets/js/admin.js',
    118             array(),
     120            array('wp-hooks'),
    119121            (string) filemtime(EPBK_PLUGIN_DIR . 'assets/js/admin.js'),
    120122            true
    … …  
    125127        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    126128
     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
    127142        // Pass config to JS.
    128         wp_localize_script('ebi-admin', 'ebiAdmin', array(
     143        $config = array(
     144            'paymentMethods'    => $payment_methods,
    129145            'restUrl'           => esc_url_raw(rest_url('epbk/v1/')),
    130146            'nonce'             => wp_create_nonce('wp_rest'),
    … …  
    147163                'noResults' => __('No results found.', 'easypeasy-booking'),
    148164            ),
    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));
    150176    }
    151177
  • easypeasy-booking/trunk/api/class-epbk-rest-availability.php

    r3607778 r3612903  
    9797        $slots = EPBK_Availability_Engine::get_available_slots($service_id, $date);
    9898
     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
    99113        return new \WP_REST_Response(array('slots' => $slots, 'date' => $date), 200);
    100114    }
    … …  
    117131
    118132        $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);
    119142
    120143        return new \WP_REST_Response(array('dates' => $dates, 'year' => $year, 'month' => $month), 200);
  • easypeasy-booking/trunk/api/class-epbk-rest-bookings.php

    r3607778 r3612903  
    137137        );
    138138
     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
    139149        return new \WP_REST_Response(array(
    140150            'bookings' => $bookings,
    … …  
    256266        $end_dt = new \DateTime($booking_date . ' ' . $booking_time);
    257267        $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        }
    258284
    259285        // Insert booking.
    … …  
    295321        );
    296322
     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
    297336        // Handle payment.
    298337        $settings = get_option('epbk_settings', array());
    … …  
    320359                'status' => 'confirmed',
    321360            ), 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;
    322386        }
    323387
  • easypeasy-booking/trunk/api/class-epbk-rest-settings.php

    r3607778 r3612903  
    173173        );
    174174
    175         return new \WP_REST_Response(array(
     175        $stats = array(
    176176            'total_bookings'   => $total_bookings,
    177177            'today_bookings'   => $today_bookings,
    … …  
    181181            'active_services'  => $active_services,
    182182            '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);
    184194    }
    185195}
  • easypeasy-booking/trunk/assets/css/frontend.css

    r3607778 r3612903  
    282282    border-radius: 10px;
    283283    background: #fff;
     284    color: #1e293b;
    284285    font-size: 14px;
    285286    font-weight: 500;
    … …  
    652653.ebi-services-list[data-columns="4"] {
    653654    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;
    654717}
    655718
  • easypeasy-booking/trunk/assets/js/admin.js

    r3607778 r3612903  
    840840                                        card.appendChild(el('h3', {}, '💳 Payment Method'));
    841841                                        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]);
    845847                                                if (form.payment_method === m) opt.selected = true;
    846848                                                paySelect.appendChild(opt);
  • easypeasy-booking/trunk/assets/js/booking-widget.js

    r3607778 r3612903  
    9090    }
    9191
     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
    92101    async function loadDates() {
    93102        if (!state.selectedService) return;
    … …  
    95104        render();
    96105        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()}`);
    98107            state.availableDates = data.dates || {};
    99108        } catch (e) {
    … …  
    111120        render();
    112121        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()}`);
    114123            let slots = data.slots || [];
    115124
    … …  
    359368        });
    360369        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        }
    361376    }
    362377
    … …  
    456471
    457472        try {
    458             const result = await api.post('bookings', {
     473            let payload = {
    459474                service_id: state.selectedService.id,
    460475                customer_name: name,
    … …  
    465480                notes: notes,
    466481                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);
    468491
    469492            if (result.checkout_url) {
    … …  
    507530    }
    508531})();
     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  
    44 * Plugin URI:  https://easypeasybooking.irish/
    55 * Description: A simple, commission-free booking and appointments system for service businesses. Works with or without WooCommerce.
    6  * Version:     1.0.0
     6 * Version:     1.1.0
    77 * Author:      EasyPeasy Booking
    88 * License:     GPL v2 or later
    … …  
    2222
    2323// Plugin constants.
    24 define('EPBK_VERSION', '1.0.0');
     24define('EPBK_VERSION', '1.1.0');
    2525define('EPBK_PLUGIN_FILE', __FILE__);
    2626define('EPBK_PLUGIN_DIR', plugin_dir_path(__FILE__));
    … …  
    138138                        $controller->register_routes();
    139139                }
     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');
    140147        });
    141148
    … …  
    178185        // Confirmation and cancellation page rendering.
    179186        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);
    180197}
    181198add_action('plugins_loaded', 'epbk_init');
  • easypeasy-booking/trunk/frontend/class-epbk-frontend.php

    r3607778 r3612903  
    5454        );
    5555
     56        // 'wp-hooks' lets add-ons extend the booking widget from their own
     57        // script (wp.hooks.addFilter('epbk.widget.steps', ...)).
    5658        wp_enqueue_script(
    5759            'ebi-booking-widget',
    5860            EPBK_PLUGIN_URL . 'assets/js/booking-widget.js',
    59             array(),
     61            array('wp-hooks'),
    6062            (string) filemtime(EPBK_PLUGIN_DIR . 'assets/js/booking-widget.js'),
    6163            true
    6264        );
    6365
     66        // Let add-ons enqueue their own frontend scripts that depend on the widget.
     67        do_action('epbk_enqueue_frontend_assets');
     68
    6469        $settings = get_option('epbk_settings', array());
    6570
    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(
    6781            'restUrl'       => esc_url_raw(rest_url('epbk/v1/')),
    6882            'nonce'         => wp_create_nonce('wp_rest'),
    … …  
    91105                'perNight' => __('per night', 'easypeasy-booking'),
    92106            ),
    93         ));
     107        ), $settings);
     108
     109        wp_localize_script('ebi-booking-widget', 'ebiFrontend', $frontend_config);
    94110
    95111        $this->assets_enqueued = true;
  • easypeasy-booking/trunk/readme.txt

    r3607778 r3612903  
    66Tested up to: 7.0
    77Requires PHP: 8.0
    8 Stable tag: 1.0.0
     8Stable tag: 1.1.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    3333* Optional logo and brand colour for the booking page and emails
    3434* Built to WordPress standards — sanitised input, escaped output, prepared queries, capability checks, and rate-limited public endpoints
     35
     36= Premium add-on =
     37
     38A 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/
    3539
    3640= Privacy =
    … …  
    8084== Changelog ==
    8185
     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
    8291= 1.0.0 =
    8392* Initial release: services, bookings, availability, cash and WooCommerce payments, booking widget, shortcodes, Gutenberg blocks, and email notifications.
    … …  
    8594== Upgrade Notice ==
    8695
     96= 1.1.0 =
     97Adds an extension API for add-ons and fixes the services list ([epbk_services] shortcode / Service List block) so it displays your services.
     98
    8799= 1.0.0 =
    88100Initial release.
  • easypeasy-booking/trunk/services/class-epbk-availability-engine.php

    r3607778 r3612903  
    202202        }
    203203
    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        );
    205227    }
    206228
  • easypeasy-booking/trunk/services/class-epbk-notification-service.php

    r3607778 r3612903  
    6262                $from    = $options['from'] ?? self::get_from_address();
    6363
     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
    6483                // Deliver via wp_mail().
    6584                return self::send_via_wp_mail( $to, $subject, $html, $from );
    … …  
    105124                        return null;
    106125                }
     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);
    107137
    108138                // Expose the raw values to the template as $escaped. Each template is
Note: See TracChangeset for help on using the changeset viewer.