Plugin Directory

Changeset 3744939


Ignore:
Timestamp:
10/13/2026 09:38:19 PM (10 days ago)
Author:
alnoraclinic
Message:

Stable tag: 1.3.7

Location:
alnora-clinic
Files:
14 edited
25 copied

Legend:

Unmodified
Added
Removed
  • alnora-clinic/tags/1.3.7/admin/class-admin.php

    r3703472 r3744939  
    9696                add_submenu_page( self::SLUG, __( 'Shortcodes', 'alnora-clinic' ),      __( 'Shortcodes', 'alnora-clinic' ),        $cap, self::SLUG . '-settings-shortcodes', array( $this, 'render_settings' ) );
    9797                add_submenu_page( self::SLUG, __( 'Booking rules', 'alnora-clinic' ),   __( 'Booking rules', 'alnora-clinic' ),     $cap, self::SLUG . '-settings-booking',    array( $this, 'render_settings' ) );
     98                add_submenu_page( self::SLUG, __( 'Form', 'alnora-clinic' ),            __( 'Form', 'alnora-clinic' ),              $cap, self::SLUG . '-settings-form',       array( $this, 'render_settings' ) );
    9899                add_submenu_page( self::SLUG, __( 'Email & reminders', 'alnora-clinic' ), __( 'Email & reminders', 'alnora-clinic' ), $cap, self::SLUG . '-settings-email', array( $this, 'render_settings' ) );
    99100                add_submenu_page( self::SLUG, __( 'GDPR', 'alnora-clinic' ),            __( 'GDPR', 'alnora-clinic' ),              $cap, self::SLUG . '-settings-gdpr',       array( $this, 'render_settings' ) );
    … …  
    112113                $css = '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-shortcodes"]),'
    113114                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-booking"]),'
     115                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-form"]),'
    114116                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-email"]),'
    115117                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-gdpr"]){display:none !important;}';
    … …  
    614616                        self::SLUG . '-settings-shortcodes' => 'shortcodes',
    615617                        self::SLUG . '-settings-booking'    => 'booking',
     618                        self::SLUG . '-settings-form'       => 'form',
    616619                        self::SLUG . '-settings-email'      => 'email',
    617620                        self::SLUG . '-settings-gdpr'       => 'gdpr',
    … …  
    834837                }
    835838
     839                // Core booking-form fields (Settings → Form). Same keys as Pro.
     840                foreach ( array(
     841                        'form_field_name_label', 'form_field_email_label', 'form_field_phone_label', 'form_field_reason_label',
     842                        'form_field_name_placeholder', 'form_field_email_placeholder', 'form_field_phone_placeholder', 'form_field_reason_placeholder',
     843                ) as $key ) {
     844                        if ( array_key_exists( $key, $post ) ) {
     845                                $out[ $key ] = sanitize_text_field( (string) $post[ $key ] );
     846                        }
     847                }
     848                foreach ( array( 'form_field_phone_enabled', 'form_field_phone_required', 'form_field_reason_enabled', 'form_field_reason_required' ) as $key ) {
     849                        if ( array_key_exists( $key, $post ) ) {
     850                                $out[ $key ] = (int) (bool) $post[ $key ];
     851                        }
     852                }
     853
    836854                if ( array_key_exists( 'delete_data_on_uninstall', $post ) ) {
    837855                        $out['delete_data_on_uninstall'] = (int) (bool) $post['delete_data_on_uninstall'];
  • alnora-clinic/tags/1.3.7/admin/css/alnc-admin.css

    r3668931 r3744939  
    702702        }
    703703}
     704
     705/* ── Settings → Form: core field rows (same look as Pro, without the Rules column) ── */
     706.alnc-form-fields-table {
     707        display: flex;
     708        flex-direction: column;
     709        border: 1px solid var(--alnc-border);
     710        border-radius: var(--alnc-radius-md);
     711        overflow: hidden;
     712}
     713.alnc-form-fields-table__head,
     714.alnc-form-fields-table__row {
     715        display: grid;
     716        grid-template-columns: 100px 1fr 1fr 90px 90px;
     717        gap: var(--alnc-space-4);
     718        align-items: center;
     719        padding: var(--alnc-space-3) var(--alnc-space-4);
     720}
     721.alnc-form-fields-table__head {
     722        background: var(--alnc-teal-mist);
     723        font-size: var(--alnc-text-xs);
     724        font-weight: var(--alnc-weight-semibold);
     725        color: var(--alnc-text-muted);
     726        text-transform: uppercase;
     727        letter-spacing: 0.04em;
     728}
     729.alnc-form-fields-table__row { border-top: 1px solid var(--alnc-border-subtle); }
     730.alnc-form-fields-table__row:first-of-type { border-top: none; }
     731.alnc-form-fields-table__toggle { display: flex; align-items: center; }
     732@media (max-width: 700px) {
     733        .alnc-form-fields-table__head { display: none; }
     734        .alnc-form-fields-table__row { grid-template-columns: 1fr 1fr; }
     735        .alnc-form-fields-table__row .alnc-badge { grid-column: 1 / -1; }
     736}
  • alnora-clinic/tags/1.3.7/admin/partials/settings.php

    r3707811 r3744939  
    66 * @var array  $settings
    77 * @var string $booking_url
    8  * @var string $alnc_tab      Active tab: general|shortcodes|booking|email|gdpr
     8 * @var string $alnc_tab      Active tab: general|shortcodes|booking|form|email|gdpr
    99 */
    1010
    … …  
    1919        'shortcodes' => 'alnora-clinic-settings-shortcodes',
    2020        'booking'    => 'alnora-clinic-settings-booking',
     21        'form'       => 'alnora-clinic-settings-form',
    2122        'email'      => 'alnora-clinic-settings-email',
    2223        'gdpr'       => 'alnora-clinic-settings-gdpr',
    … …  
    3637                        'shortcodes' => __( 'Shortcodes', 'alnora-clinic' ),
    3738                        'booking'    => __( 'Booking rules', 'alnora-clinic' ),
     39                'form'       => __( 'Form', 'alnora-clinic' ),
    3840                        'email'      => __( 'Email & reminders', 'alnora-clinic' ),
    3941                        'gdpr'       => __( 'GDPR', 'alnora-clinic' ),
    … …  
    167169                </div>
    168170
     171                <?php /* ── FORM ─────────────────────────────────────────────────── */ ?>
     172                <?php elseif ( 'form' === $alnc_tab ) : ?>
     173
     174                <div class="alnc-card">
     175                        <h2 class="alnc-h2"><?php esc_html_e( 'Core fields', 'alnora-clinic' ); ?></h2>
     176                        <p style="color:var(--alnc-text-muted);margin-bottom:var(--alnc-space-5)"><?php esc_html_e( 'Customize labels, visibility and required status for each field on the booking form.', 'alnora-clinic' ); ?></p>
     177
     178                        <?php
     179                        /* The same rows and setting keys as Pro's Form tab, so everything set here carries
     180                         * over on upgrade. Name and email are always on and always required: a booking
     181                         * without them cannot become a patient record. */
     182                        $alnc_core_fields = array(
     183                                array( 'key' => 'name',   'label_key' => 'form_field_name_label',   'default' => 'Full name', 'always' => true ),
     184                                array( 'key' => 'email',  'label_key' => 'form_field_email_label',  'default' => 'Email', 'always' => true ),
     185                                array( 'key' => 'phone',  'label_key' => 'form_field_phone_label',  'default' => 'Phone', 'always' => false ),
     186                                array( 'key' => 'reason', 'label_key' => 'form_field_reason_label', 'default' => 'Reason for visit (optional)', 'always' => false ),
     187                        );
     188                        ?>
     189
     190                        <div class="alnc-form-fields-table">
     191                                <div class="alnc-form-fields-table__head">
     192                                        <span><?php esc_html_e( 'Field', 'alnora-clinic' ); ?></span>
     193                                        <span><?php esc_html_e( 'Label', 'alnora-clinic' ); ?></span>
     194                                        <span><?php esc_html_e( 'Placeholder', 'alnora-clinic' ); ?></span>
     195                                        <span><?php esc_html_e( 'Required', 'alnora-clinic' ); ?></span>
     196                                        <span><?php esc_html_e( 'Enabled', 'alnora-clinic' ); ?></span>
     197                                </div>
     198
     199                                <?php foreach ( $alnc_core_fields as $alnc_cf ) : ?>
     200                                        <?php
     201                                        $alnc_ph_key  = 'form_field_' . $alnc_cf['key'] . '_placeholder';
     202                                        $alnc_req_key = 'form_field_' . $alnc_cf['key'] . '_required';
     203                                        $alnc_en_key  = 'form_field_' . $alnc_cf['key'] . '_enabled';
     204                                        ?>
     205                                <div class="alnc-form-fields-table__row">
     206                                        <span class="alnc-badge alnc-badge-navy"><?php echo esc_html( $alnc_cf['key'] ); ?></span>
     207                                        <div class="alnc-field" style="margin:0">
     208                                                <input class="alnc-input" type="text"
     209                                                        name="<?php echo esc_attr( $alnc_cf['label_key'] ); ?>"
     210                                                        value="<?php echo esc_attr( (string) ( $settings[ $alnc_cf['label_key'] ] ?? $alnc_cf['default'] ) ); ?>"
     211                                                        placeholder="<?php echo esc_attr( $alnc_cf['default'] ); ?>"
     212                                                        aria-label="<?php echo esc_attr( sprintf( /* translators: %s: field key, e.g. phone. */ __( 'Label for %s', 'alnora-clinic' ), $alnc_cf['key'] ) ); ?>">
     213                                        </div>
     214                                        <div class="alnc-field" style="margin:0">
     215                                                <input class="alnc-input" type="text"
     216                                                        name="<?php echo esc_attr( $alnc_ph_key ); ?>"
     217                                                        value="<?php echo esc_attr( (string) ( $settings[ $alnc_ph_key ] ?? '' ) ); ?>"
     218                                                        placeholder="<?php esc_attr_e( 'Shown inside the empty field', 'alnora-clinic' ); ?>"
     219                                                        aria-label="<?php echo esc_attr( sprintf( /* translators: %s: field key, e.g. phone. */ __( 'Placeholder for %s', 'alnora-clinic' ), $alnc_cf['key'] ) ); ?>">
     220                                        </div>
     221                                        <div class="alnc-form-fields-table__toggle">
     222                                                <?php if ( $alnc_cf['always'] ) : ?>
     223                                                        <span class="alnc-badge alnc-badge-teal"><?php esc_html_e( 'Always', 'alnora-clinic' ); ?></span>
     224                                                <?php else : ?>
     225                                                        <input type="hidden" name="<?php echo esc_attr( $alnc_req_key ); ?>" value="0">
     226                                                        <label class="alnc-switch">
     227                                                                <input type="checkbox" name="<?php echo esc_attr( $alnc_req_key ); ?>" value="1" <?php checked( ! empty( $settings[ $alnc_req_key ] ) ); ?>>
     228                                                                <span></span>
     229                                                        </label>
     230                                                <?php endif; ?>
     231                                        </div>
     232                                        <div class="alnc-form-fields-table__toggle">
     233                                                <?php if ( $alnc_cf['always'] ) : ?>
     234                                                        <span class="alnc-badge alnc-badge-teal"><?php esc_html_e( 'Always', 'alnora-clinic' ); ?></span>
     235                                                <?php else : ?>
     236                                                        <input type="hidden" name="<?php echo esc_attr( $alnc_en_key ); ?>" value="0">
     237                                                        <label class="alnc-switch">
     238                                                                <input type="checkbox" name="<?php echo esc_attr( $alnc_en_key ); ?>" value="1" <?php checked( ! empty( $settings[ $alnc_en_key ] ) ); ?>>
     239                                                                <span></span>
     240                                                        </label>
     241                                                <?php endif; ?>
     242                                        </div>
     243                                </div>
     244                                <?php endforeach; ?>
     245                        </div>
     246
     247                        <p class="alnc-field-hint" style="margin-top:var(--alnc-space-4)"><?php esc_html_e( 'In the Pro version you can add your own questions, show a field only for certain doctors or services, split the name into name and surname,', 'alnora-clinic' ); ?> <a href="https://alnora-booking.com/docs/#booking-form-fields" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'and much more', 'alnora-clinic' ); ?></a>.</p>
     248                </div>
     249
    169250                <?php /* ── EMAIL & REMINDERS ──────────────────────────────────── */ ?>
    170251                <?php elseif ( 'email' === $alnc_tab ) : ?>
  • alnora-clinic/tags/1.3.7/alnora-clinic.php

    r3742445 r3744939  
    66 * Plugin URI:        https://alnora-booking.com
    77 * Description:       Appointment booking for private medical clinics — doctors, services, schedules, patient records and automated email notifications.
    8  * Version:           1.3.6
     8 * Version:           1.3.7
    99 * Requires at least: 6.0
    1010 * Requires PHP:      7.4
    … …  
    6262|--------------------------------------------------------------------------
    6363*/
    64 if ( ! defined( 'ALNC_VERSION' ) )  { define( 'ALNC_VERSION',  '1.3.6' ); }
     64if ( ! defined( 'ALNC_VERSION' ) )  { define( 'ALNC_VERSION',  '1.3.7' ); }
    6565if ( ! defined( 'ALNC_DB_VERSION' ) ) { define( 'ALNC_DB_VERSION', '1.7.2' ); }
    6666if ( ! defined( 'ALNC_FILE' ) )    { define( 'ALNC_FILE',    __FILE__ ); }
  • alnora-clinic/tags/1.3.7/api/class-rest-routes.php

    r3604289 r3744939  
    359359                }
    360360
    361                 $result = Appointment_Manager::create_booking( (array) $params );
     361                // Settings → Form: a field switched off is never stored, and a required one must
     362                // be filled. Checked here as well as in the form, so the rule holds for any client.
     363                $params = (array) $params;
     364                foreach ( array( 'phone' => 'patient_phone', 'reason' => 'reason' ) as $field => $param ) {
     365                        $conf = \AlnoraClinic\Frontend\Frontend::core_fields()[ $field ];
     366                        if ( ! $conf['enabled'] ) {
     367                                unset( $params[ $param ] );
     368                        } elseif ( $conf['required'] && '' === trim( (string) ( $params[ $param ] ?? '' ) ) ) {
     369                                // In the patient language, like the rest of the form.
     370                                $strings = \AlnoraClinic\Translations::get( (string) \AlnoraClinic\Settings::get( 'language', 'en_US' ) );
     371                                return new \WP_Error( 'alnc_missing_field', (string) ( $strings['required'] ?? __( 'Please fill in all required fields.', 'alnora-clinic' ) ), array( 'status' => 400 ) );
     372                        }
     373                }
     374
     375                $result = Appointment_Manager::create_booking( $params );
    362376
    363377                if ( is_wp_error( $result ) ) {
  • alnora-clinic/tags/1.3.7/frontend/class-frontend.php

    r3687687 r3744939  
    105105
    106106        /**
     107         * The core booking fields as set under Settings → Form.
     108         *
     109         * A label still at its English default is sent empty, so the form shows its own
     110         * translation in the patient language; only a label the clinic changed is sent as
     111         * typed. Name and email are always shown and always required.
     112         *
     113         * @return array<string,array<string,mixed>>
     114         */
     115        public static function core_fields(): array {
     116                $defaults = array(
     117                        'name'   => 'Full name',
     118                        'email'  => 'Email',
     119                        'phone'  => 'Phone',
     120                        'reason' => 'Reason for visit (optional)',
     121                );
     122                $fields = array();
     123                foreach ( $defaults as $key => $default ) {
     124                        $label = trim( (string) Settings::get( 'form_field_' . $key . '_label', $default ) );
     125                        $fixed = in_array( $key, array( 'name', 'email' ), true );
     126
     127                        $fields[ $key ] = array(
     128                                'label'       => $label === $default ? '' : $label,
     129                                'placeholder' => (string) Settings::get( 'form_field_' . $key . '_placeholder', '' ),
     130                                'enabled'     => $fixed || (bool) Settings::get( 'form_field_' . $key . '_enabled', 1 ),
     131                                'required'    => $fixed || (bool) Settings::get( 'form_field_' . $key . '_required', 0 ),
     132                        );
     133                }
     134                return $fields;
     135        }
     136
     137        /**
    107138         * Shortcode handler.
    108139         *
    … …  
    156187                        'timeFormat' => (string) get_option( 'time_format', 'g:i a' ),
    157188                        'i18n'       => \AlnoraClinic\Translations::get( $lang_code ),
     189                        'fields'     => self::core_fields(),
    158190                );
    159191
  • alnora-clinic/tags/1.3.7/frontend/js/alnc-booking.js

    r3687687 r3744939  
    483483                ] );
    484484
     485                // Core fields as set under Settings → Form. An empty label means "still the
     486                // default", so the patient-language translation is shown instead.
     487                var F = CFG.fields || {};
     488                function conf( key ) {
     489                        var c = F[ key ] || {};
     490                        var fixed = ( 'name' === key || 'email' === key );
     491                        var label = c.label || T[ key ] || key;
     492                        // The default reason label ends in "(optional)" in every language; drop it
     493                        // once the clinic makes the field required.
     494                        if ( ! c.label && c.required ) { label = label.replace( /\s*\([^)]*\)\s*$/, '' ); }
     495                        return {
     496                                label: label + ( ( fixed || c.required ) ? ' *' : '' ),
     497                                placeholder: c.placeholder || '',
     498                                enabled: fixed || c.enabled !== false,
     499                                required: fixed || !! c.required
     500                        };
     501                }
     502                var cName = conf( 'name' ), cEmail = conf( 'email' ), cPhone = conf( 'phone' ), cReason = conf( 'reason' );
     503
    485504                var form = el( 'form', { class: 'alnc-form', autocomplete: 'on' } );
    486                 form.appendChild( field( 'alnc-f-name', T.name + ' *', 'text', 'name', true ) );
    487                 form.appendChild( el( 'div', { class: 'alnc-form__row' }, [
    488                         field( 'alnc-f-email', T.email + ' *', 'email', 'email', true ),
    489                         field( 'alnc-f-phone', T.phone, 'tel', 'phone', false )
    490                 ] ) );
    491                 form.appendChild( textareaField( 'alnc-f-reason', T.reason, 'reason' ) );
     505                form.appendChild( field( 'alnc-f-name', cName.label, 'text', 'name', true, cName.placeholder ) );
     506                var emailField = field( 'alnc-f-email', cEmail.label, 'email', 'email', true, cEmail.placeholder );
     507                if ( cPhone.enabled ) {
     508                        // Email and phone share a two-column row.
     509                        form.appendChild( el( 'div', { class: 'alnc-form__row' }, [
     510                                emailField,
     511                                field( 'alnc-f-phone', cPhone.label, 'tel', 'tel', cPhone.required, cPhone.placeholder )
     512                        ] ) );
     513                } else {
     514                        // No phone: email takes the full width, like the name above it.
     515                        form.appendChild( emailField );
     516                }
     517                if ( cReason.enabled ) {
     518                        form.appendChild( textareaField( 'alnc-f-reason', cReason.label, 'reason', cReason.required, cReason.placeholder ) );
     519                }
    492520
    493521                // Recurring (Pro): optional repeat controls.
    … …  
    527555                        var name = form.querySelector( '#alnc-f-name' ).value.trim();
    528556                        var email = form.querySelector( '#alnc-f-email' ).value.trim();
    529                         var phone = form.querySelector( '#alnc-f-phone' ).value.trim();
    530                         var reason = form.querySelector( '#alnc-f-reason' ).value.trim();
     557                        var phoneEl = form.querySelector( '#alnc-f-phone' );
     558                        var reasonEl = form.querySelector( '#alnc-f-reason' );
     559                        var phone = phoneEl ? phoneEl.value.trim() : '';
     560                        var reason = reasonEl ? reasonEl.value.trim() : '';
    531561                        var consented = form.querySelector( '#' + consentId ).checked;
    532562
    533563                        if ( ! name || ! email ) { return showErr( T.required ); }
     564                        if ( ( phoneEl && cPhone.required && ! phone ) || ( reasonEl && cReason.required && ! reason ) ) { return showErr( T.required ); }
    534565                        if ( ! consented ) { return showErr( T.consentReq ); }
    535566
    … …  
    696727
    697728        /* ----- field factories ----- */
    698         function field( id, label, type, name, required ) {
     729        function field( id, label, type, name, required, placeholder ) {
    699730                return el( 'div', { class: 'alnc-field' }, [
    700731                        el( 'label', { class: 'alnc-field-label', for: id, text: label } ),
    701                         el( 'input', { class: 'alnc-input', id: id, name: name, type: type, required: required ? 'required' : null, autocomplete: name } )
     732                        el( 'input', { class: 'alnc-input', id: id, name: name, type: type, required: required ? 'required' : null, placeholder: placeholder || null, autocomplete: name } )
    702733                ] );
    703734        }
    704         function textareaField( id, label, name ) {
     735        function textareaField( id, label, name, required, placeholder ) {
    705736                return el( 'div', { class: 'alnc-field' }, [
    706737                        el( 'label', { class: 'alnc-field-label', for: id, text: label } ),
    707                         el( 'textarea', { class: 'alnc-textarea', id: id, name: name, rows: '3' } )
     738                        el( 'textarea', { class: 'alnc-textarea', id: id, name: name, rows: '3', required: required ? 'required' : null, placeholder: placeholder || null } )
    708739                ] );
    709740        }
  • alnora-clinic/tags/1.3.7/includes/class-appointment-manager.php

    r3585842 r3744939  
    385385
    386386        /**
    387          * Generate a unique reference number, e.g. alnc-2026-0042.
     387         * Generate a unique reference number, e.g. 26-0042.
     388         *
     389         * Two-digit year, the same as Pro, so a clinic moving between the two keeps one
     390         * numbering style. References issued earlier as 2026-0042 stay as they are: they
     391         * cannot collide with the new 26- series, which is counted on its own.
    388392         *
    389393         * @return string
    … …  
    392396                global $wpdb;
    393397                $table = self::table();
    394                 $year  = current_time( 'Y' );
     398                $year  = (string) current_time( 'y' );
    395399
    396400                // Next sequence = highest existing sequence for this year + 1. Using MAX (not
    … …  
    399403                // failing the insert ("Could not save the appointment").
    400404                $like = $wpdb->esc_like( $year . '-' ) . '%';
    401                 $max  = (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX( CAST( SUBSTRING_INDEX( ref_number, '-', -1 ) AS UNSIGNED ) ) FROM {$table} WHERE ref_number LIKE %s", $like ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     405                $max  = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COALESCE( MAX( CAST( SUBSTRING_INDEX( ref_number, '-', -1 ) AS UNSIGNED ) ), 0 ) FROM {$table} WHERE ref_number LIKE %s", $like ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    402406                $seq  = $max + 1;
    403407
  • alnora-clinic/tags/1.3.7/includes/class-settings.php

    r3657434 r3744939  
    4444                        // a reinstall. Turn on only if you want everything erased on uninstall.
    4545                        'delete_data_on_uninstall' => 0,
     46                        // Core booking-form fields (Settings → Form). Same keys and defaults as Pro,
     47                        // so a clinic's labels and switches carry over unchanged on upgrade.
     48                        'form_field_name_label'         => 'Full name',
     49                        'form_field_email_label'        => 'Email',
     50                        'form_field_phone_label'        => 'Phone',
     51                        'form_field_phone_enabled'      => 1,
     52                        'form_field_phone_required'     => 0,
     53                        'form_field_reason_label'       => 'Reason for visit (optional)',
     54                        'form_field_reason_enabled'     => 1,
     55                        'form_field_reason_required'    => 0,
     56                        'form_field_name_placeholder'   => '',
     57                        'form_field_email_placeholder'  => '',
     58                        'form_field_phone_placeholder'  => '+1 212 555 0147',
     59                        'form_field_reason_placeholder' => '',
    4660                );
    4761        }
  • alnora-clinic/tags/1.3.7/readme.txt

    r3742445 r3744939  
    55Tested up to: 7.1.2
    66Requires PHP: 7.4
    7 Stable tag: 1.3.6
     7Stable tag: 1.3.7
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1919No generic salon plugin bent into shape. No spreadsheets. No five add-ons duct-taped together. Just one focused product that understands doctors, services, patients and privacy.
    2020
    21 👉 [Live demo](https://alnora-booking.com/demo/) · [See Pro pricing](https://alnora-booking.com/pricing/) · [Documentation](https://alnora-booking.com/docs/)
     21👉 [Live demo](https://alnora-booking.com/demo/) · [See Pro pricing](https://alnora-booking.com/pricing/) · [Documentation](https://alnora-booking.com/docs/) · [YouTube](https://www.youtube.com/@alnora-clinic)
    2222
    2323**Why private clinics choose Alnora**
    … …  
    150150== Changelog ==
    151151
     152= 1.3.7 =
     153* New: **Settings → Form.** Change the label and placeholder of each field on the booking form: name, email, phone and reason for visit. Phone and reason each have their own **Required** and **Enabled** switches, so you can ask for a phone number on every booking or leave the reason box off the form entirely. Name and email are always shown and always required. A label you leave as it is still shows in the patient's language. When phone is switched off, the email field takes the full width.
     154* New: a required field is marked with an asterisk and checked when the booking is sent, as well as in the form. A field you switch off is never saved, even if something else sends it.
     155* Improvement: **shorter appointment references.** New bookings get references such as 26-0042 instead of 2026-0042, the same style as Pro, so a clinic moving between the two keeps one numbering style. References already issued stay as they are.
     156
    152157= 1.3.6 =
    153158* Updated Freemius SDK to the latest version.
    … …  
    257262== Upgrade Notice ==
    258263
     264= 1.3.7 =
     265New Settings → Form tab to relabel the booking form's fields and make phone or reason required or hidden. New appointment references use the shorter 26-0042 style. Existing references are unchanged. No database changes.
     266
    259267= 1.3.6 =
    260268Updated Freemius SDK to the latest version. No database or configuration changes.
  • alnora-clinic/trunk/admin/class-admin.php

    r3703472 r3744939  
    9696                add_submenu_page( self::SLUG, __( 'Shortcodes', 'alnora-clinic' ),      __( 'Shortcodes', 'alnora-clinic' ),        $cap, self::SLUG . '-settings-shortcodes', array( $this, 'render_settings' ) );
    9797                add_submenu_page( self::SLUG, __( 'Booking rules', 'alnora-clinic' ),   __( 'Booking rules', 'alnora-clinic' ),     $cap, self::SLUG . '-settings-booking',    array( $this, 'render_settings' ) );
     98                add_submenu_page( self::SLUG, __( 'Form', 'alnora-clinic' ),            __( 'Form', 'alnora-clinic' ),              $cap, self::SLUG . '-settings-form',       array( $this, 'render_settings' ) );
    9899                add_submenu_page( self::SLUG, __( 'Email & reminders', 'alnora-clinic' ), __( 'Email &amp; reminders', 'alnora-clinic' ), $cap, self::SLUG . '-settings-email', array( $this, 'render_settings' ) );
    99100                add_submenu_page( self::SLUG, __( 'GDPR', 'alnora-clinic' ),            __( 'GDPR', 'alnora-clinic' ),              $cap, self::SLUG . '-settings-gdpr',       array( $this, 'render_settings' ) );
    … …  
    112113                $css = '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-shortcodes"]),'
    113114                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-booking"]),'
     115                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-form"]),'
    114116                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-email"]),'
    115117                        . '#adminmenu li:has(> a[href$="page=alnora-clinic-settings-gdpr"]){display:none !important;}';
    … …  
    614616                        self::SLUG . '-settings-shortcodes' => 'shortcodes',
    615617                        self::SLUG . '-settings-booking'    => 'booking',
     618                        self::SLUG . '-settings-form'       => 'form',
    616619                        self::SLUG . '-settings-email'      => 'email',
    617620                        self::SLUG . '-settings-gdpr'       => 'gdpr',
    … …  
    834837                }
    835838
     839                // Core booking-form fields (Settings → Form). Same keys as Pro.
     840                foreach ( array(
     841                        'form_field_name_label', 'form_field_email_label', 'form_field_phone_label', 'form_field_reason_label',
     842                        'form_field_name_placeholder', 'form_field_email_placeholder', 'form_field_phone_placeholder', 'form_field_reason_placeholder',
     843                ) as $key ) {
     844                        if ( array_key_exists( $key, $post ) ) {
     845                                $out[ $key ] = sanitize_text_field( (string) $post[ $key ] );
     846                        }
     847                }
     848                foreach ( array( 'form_field_phone_enabled', 'form_field_phone_required', 'form_field_reason_enabled', 'form_field_reason_required' ) as $key ) {
     849                        if ( array_key_exists( $key, $post ) ) {
     850                                $out[ $key ] = (int) (bool) $post[ $key ];
     851                        }
     852                }
     853
    836854                if ( array_key_exists( 'delete_data_on_uninstall', $post ) ) {
    837855                        $out['delete_data_on_uninstall'] = (int) (bool) $post['delete_data_on_uninstall'];
  • alnora-clinic/trunk/admin/css/alnc-admin.css

    r3668931 r3744939  
    702702        }
    703703}
     704
     705/* ── Settings → Form: core field rows (same look as Pro, without the Rules column) ── */
     706.alnc-form-fields-table {
     707        display: flex;
     708        flex-direction: column;
     709        border: 1px solid var(--alnc-border);
     710        border-radius: var(--alnc-radius-md);
     711        overflow: hidden;
     712}
     713.alnc-form-fields-table__head,
     714.alnc-form-fields-table__row {
     715        display: grid;
     716        grid-template-columns: 100px 1fr 1fr 90px 90px;
     717        gap: var(--alnc-space-4);
     718        align-items: center;
     719        padding: var(--alnc-space-3) var(--alnc-space-4);
     720}
     721.alnc-form-fields-table__head {
     722        background: var(--alnc-teal-mist);
     723        font-size: var(--alnc-text-xs);
     724        font-weight: var(--alnc-weight-semibold);
     725        color: var(--alnc-text-muted);
     726        text-transform: uppercase;
     727        letter-spacing: 0.04em;
     728}
     729.alnc-form-fields-table__row { border-top: 1px solid var(--alnc-border-subtle); }
     730.alnc-form-fields-table__row:first-of-type { border-top: none; }
     731.alnc-form-fields-table__toggle { display: flex; align-items: center; }
     732@media (max-width: 700px) {
     733        .alnc-form-fields-table__head { display: none; }
     734        .alnc-form-fields-table__row { grid-template-columns: 1fr 1fr; }
     735        .alnc-form-fields-table__row .alnc-badge { grid-column: 1 / -1; }
     736}
  • alnora-clinic/trunk/admin/partials/settings.php

    r3707811 r3744939  
    66 * @var array  $settings
    77 * @var string $booking_url
    8  * @var string $alnc_tab      Active tab: general|shortcodes|booking|email|gdpr
     8 * @var string $alnc_tab      Active tab: general|shortcodes|booking|form|email|gdpr
    99 */
    1010
    … …  
    1919        'shortcodes' => 'alnora-clinic-settings-shortcodes',
    2020        'booking'    => 'alnora-clinic-settings-booking',
     21        'form'       => 'alnora-clinic-settings-form',
    2122        'email'      => 'alnora-clinic-settings-email',
    2223        'gdpr'       => 'alnora-clinic-settings-gdpr',
    … …  
    3637                        'shortcodes' => __( 'Shortcodes', 'alnora-clinic' ),
    3738                        'booking'    => __( 'Booking rules', 'alnora-clinic' ),
     39                'form'       => __( 'Form', 'alnora-clinic' ),
    3840                        'email'      => __( 'Email & reminders', 'alnora-clinic' ),
    3941                        'gdpr'       => __( 'GDPR', 'alnora-clinic' ),
    … …  
    167169                </div>
    168170
     171                <?php /* ── FORM ─────────────────────────────────────────────────── */ ?>
     172                <?php elseif ( 'form' === $alnc_tab ) : ?>
     173
     174                <div class="alnc-card">
     175                        <h2 class="alnc-h2"><?php esc_html_e( 'Core fields', 'alnora-clinic' ); ?></h2>
     176                        <p style="color:var(--alnc-text-muted);margin-bottom:var(--alnc-space-5)"><?php esc_html_e( 'Customize labels, visibility and required status for each field on the booking form.', 'alnora-clinic' ); ?></p>
     177
     178                        <?php
     179                        /* The same rows and setting keys as Pro's Form tab, so everything set here carries
     180                         * over on upgrade. Name and email are always on and always required: a booking
     181                         * without them cannot become a patient record. */
     182                        $alnc_core_fields = array(
     183                                array( 'key' => 'name',   'label_key' => 'form_field_name_label',   'default' => 'Full name', 'always' => true ),
     184                                array( 'key' => 'email',  'label_key' => 'form_field_email_label',  'default' => 'Email', 'always' => true ),
     185                                array( 'key' => 'phone',  'label_key' => 'form_field_phone_label',  'default' => 'Phone', 'always' => false ),
     186                                array( 'key' => 'reason', 'label_key' => 'form_field_reason_label', 'default' => 'Reason for visit (optional)', 'always' => false ),
     187                        );
     188                        ?>
     189
     190                        <div class="alnc-form-fields-table">
     191                                <div class="alnc-form-fields-table__head">
     192                                        <span><?php esc_html_e( 'Field', 'alnora-clinic' ); ?></span>
     193                                        <span><?php esc_html_e( 'Label', 'alnora-clinic' ); ?></span>
     194                                        <span><?php esc_html_e( 'Placeholder', 'alnora-clinic' ); ?></span>
     195                                        <span><?php esc_html_e( 'Required', 'alnora-clinic' ); ?></span>
     196                                        <span><?php esc_html_e( 'Enabled', 'alnora-clinic' ); ?></span>
     197                                </div>
     198
     199                                <?php foreach ( $alnc_core_fields as $alnc_cf ) : ?>
     200                                        <?php
     201                                        $alnc_ph_key  = 'form_field_' . $alnc_cf['key'] . '_placeholder';
     202                                        $alnc_req_key = 'form_field_' . $alnc_cf['key'] . '_required';
     203                                        $alnc_en_key  = 'form_field_' . $alnc_cf['key'] . '_enabled';
     204                                        ?>
     205                                <div class="alnc-form-fields-table__row">
     206                                        <span class="alnc-badge alnc-badge-navy"><?php echo esc_html( $alnc_cf['key'] ); ?></span>
     207                                        <div class="alnc-field" style="margin:0">
     208                                                <input class="alnc-input" type="text"
     209                                                        name="<?php echo esc_attr( $alnc_cf['label_key'] ); ?>"
     210                                                        value="<?php echo esc_attr( (string) ( $settings[ $alnc_cf['label_key'] ] ?? $alnc_cf['default'] ) ); ?>"
     211                                                        placeholder="<?php echo esc_attr( $alnc_cf['default'] ); ?>"
     212                                                        aria-label="<?php echo esc_attr( sprintf( /* translators: %s: field key, e.g. phone. */ __( 'Label for %s', 'alnora-clinic' ), $alnc_cf['key'] ) ); ?>">
     213                                        </div>
     214                                        <div class="alnc-field" style="margin:0">
     215                                                <input class="alnc-input" type="text"
     216                                                        name="<?php echo esc_attr( $alnc_ph_key ); ?>"
     217                                                        value="<?php echo esc_attr( (string) ( $settings[ $alnc_ph_key ] ?? '' ) ); ?>"
     218                                                        placeholder="<?php esc_attr_e( 'Shown inside the empty field', 'alnora-clinic' ); ?>"
     219                                                        aria-label="<?php echo esc_attr( sprintf( /* translators: %s: field key, e.g. phone. */ __( 'Placeholder for %s', 'alnora-clinic' ), $alnc_cf['key'] ) ); ?>">
     220                                        </div>
     221                                        <div class="alnc-form-fields-table__toggle">
     222                                                <?php if ( $alnc_cf['always'] ) : ?>
     223                                                        <span class="alnc-badge alnc-badge-teal"><?php esc_html_e( 'Always', 'alnora-clinic' ); ?></span>
     224                                                <?php else : ?>
     225                                                        <input type="hidden" name="<?php echo esc_attr( $alnc_req_key ); ?>" value="0">
     226                                                        <label class="alnc-switch">
     227                                                                <input type="checkbox" name="<?php echo esc_attr( $alnc_req_key ); ?>" value="1" <?php checked( ! empty( $settings[ $alnc_req_key ] ) ); ?>>
     228                                                                <span></span>
     229                                                        </label>
     230                                                <?php endif; ?>
     231                                        </div>
     232                                        <div class="alnc-form-fields-table__toggle">
     233                                                <?php if ( $alnc_cf['always'] ) : ?>
     234                                                        <span class="alnc-badge alnc-badge-teal"><?php esc_html_e( 'Always', 'alnora-clinic' ); ?></span>
     235                                                <?php else : ?>
     236                                                        <input type="hidden" name="<?php echo esc_attr( $alnc_en_key ); ?>" value="0">
     237                                                        <label class="alnc-switch">
     238                                                                <input type="checkbox" name="<?php echo esc_attr( $alnc_en_key ); ?>" value="1" <?php checked( ! empty( $settings[ $alnc_en_key ] ) ); ?>>
     239                                                                <span></span>
     240                                                        </label>
     241                                                <?php endif; ?>
     242                                        </div>
     243                                </div>
     244                                <?php endforeach; ?>
     245                        </div>
     246
     247                        <p class="alnc-field-hint" style="margin-top:var(--alnc-space-4)"><?php esc_html_e( 'In the Pro version you can add your own questions, show a field only for certain doctors or services, split the name into name and surname,', 'alnora-clinic' ); ?> <a href="https://alnora-booking.com/docs/#booking-form-fields" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'and much more', 'alnora-clinic' ); ?></a>.</p>
     248                </div>
     249
    169250                <?php /* ── EMAIL & REMINDERS ──────────────────────────────────── */ ?>
    170251                <?php elseif ( 'email' === $alnc_tab ) : ?>
  • alnora-clinic/trunk/alnora-clinic.php

    r3742445 r3744939  
    66 * Plugin URI:        https://alnora-booking.com
    77 * Description:       Appointment booking for private medical clinics — doctors, services, schedules, patient records and automated email notifications.
    8  * Version:           1.3.6
     8 * Version:           1.3.7
    99 * Requires at least: 6.0
    1010 * Requires PHP:      7.4
    … …  
    6262|--------------------------------------------------------------------------
    6363*/
    64 if ( ! defined( 'ALNC_VERSION' ) )  { define( 'ALNC_VERSION',  '1.3.6' ); }
     64if ( ! defined( 'ALNC_VERSION' ) )  { define( 'ALNC_VERSION',  '1.3.7' ); }
    6565if ( ! defined( 'ALNC_DB_VERSION' ) ) { define( 'ALNC_DB_VERSION', '1.7.2' ); }
    6666if ( ! defined( 'ALNC_FILE' ) )    { define( 'ALNC_FILE',    __FILE__ ); }
  • alnora-clinic/trunk/api/class-rest-routes.php

    r3604289 r3744939  
    359359                }
    360360
    361                 $result = Appointment_Manager::create_booking( (array) $params );
     361                // Settings → Form: a field switched off is never stored, and a required one must
     362                // be filled. Checked here as well as in the form, so the rule holds for any client.
     363                $params = (array) $params;
     364                foreach ( array( 'phone' => 'patient_phone', 'reason' => 'reason' ) as $field => $param ) {
     365                        $conf = \AlnoraClinic\Frontend\Frontend::core_fields()[ $field ];
     366                        if ( ! $conf['enabled'] ) {
     367                                unset( $params[ $param ] );
     368                        } elseif ( $conf['required'] && '' === trim( (string) ( $params[ $param ] ?? '' ) ) ) {
     369                                // In the patient language, like the rest of the form.
     370                                $strings = \AlnoraClinic\Translations::get( (string) \AlnoraClinic\Settings::get( 'language', 'en_US' ) );
     371                                return new \WP_Error( 'alnc_missing_field', (string) ( $strings['required'] ?? __( 'Please fill in all required fields.', 'alnora-clinic' ) ), array( 'status' => 400 ) );
     372                        }
     373                }
     374
     375                $result = Appointment_Manager::create_booking( $params );
    362376
    363377                if ( is_wp_error( $result ) ) {
  • alnora-clinic/trunk/frontend/class-frontend.php

    r3687687 r3744939  
    105105
    106106        /**
     107         * The core booking fields as set under Settings → Form.
     108         *
     109         * A label still at its English default is sent empty, so the form shows its own
     110         * translation in the patient language; only a label the clinic changed is sent as
     111         * typed. Name and email are always shown and always required.
     112         *
     113         * @return array<string,array<string,mixed>>
     114         */
     115        public static function core_fields(): array {
     116                $defaults = array(
     117                        'name'   => 'Full name',
     118                        'email'  => 'Email',
     119                        'phone'  => 'Phone',
     120                        'reason' => 'Reason for visit (optional)',
     121                );
     122                $fields = array();
     123                foreach ( $defaults as $key => $default ) {
     124                        $label = trim( (string) Settings::get( 'form_field_' . $key . '_label', $default ) );
     125                        $fixed = in_array( $key, array( 'name', 'email' ), true );
     126
     127                        $fields[ $key ] = array(
     128                                'label'       => $label === $default ? '' : $label,
     129                                'placeholder' => (string) Settings::get( 'form_field_' . $key . '_placeholder', '' ),
     130                                'enabled'     => $fixed || (bool) Settings::get( 'form_field_' . $key . '_enabled', 1 ),
     131                                'required'    => $fixed || (bool) Settings::get( 'form_field_' . $key . '_required', 0 ),
     132                        );
     133                }
     134                return $fields;
     135        }
     136
     137        /**
    107138         * Shortcode handler.
    108139         *
    … …  
    156187                        'timeFormat' => (string) get_option( 'time_format', 'g:i a' ),
    157188                        'i18n'       => \AlnoraClinic\Translations::get( $lang_code ),
     189                        'fields'     => self::core_fields(),
    158190                );
    159191
  • alnora-clinic/trunk/frontend/js/alnc-booking.js

    r3687687 r3744939  
    483483                ] );
    484484
     485                // Core fields as set under Settings → Form. An empty label means "still the
     486                // default", so the patient-language translation is shown instead.
     487                var F = CFG.fields || {};
     488                function conf( key ) {
     489                        var c = F[ key ] || {};
     490                        var fixed = ( 'name' === key || 'email' === key );
     491                        var label = c.label || T[ key ] || key;
     492                        // The default reason label ends in "(optional)" in every language; drop it
     493                        // once the clinic makes the field required.
     494                        if ( ! c.label && c.required ) { label = label.replace( /\s*\([^)]*\)\s*$/, '' ); }
     495                        return {
     496                                label: label + ( ( fixed || c.required ) ? ' *' : '' ),
     497                                placeholder: c.placeholder || '',
     498                                enabled: fixed || c.enabled !== false,
     499                                required: fixed || !! c.required
     500                        };
     501                }
     502                var cName = conf( 'name' ), cEmail = conf( 'email' ), cPhone = conf( 'phone' ), cReason = conf( 'reason' );
     503
    485504                var form = el( 'form', { class: 'alnc-form', autocomplete: 'on' } );
    486                 form.appendChild( field( 'alnc-f-name', T.name + ' *', 'text', 'name', true ) );
    487                 form.appendChild( el( 'div', { class: 'alnc-form__row' }, [
    488                         field( 'alnc-f-email', T.email + ' *', 'email', 'email', true ),
    489                         field( 'alnc-f-phone', T.phone, 'tel', 'phone', false )
    490                 ] ) );
    491                 form.appendChild( textareaField( 'alnc-f-reason', T.reason, 'reason' ) );
     505                form.appendChild( field( 'alnc-f-name', cName.label, 'text', 'name', true, cName.placeholder ) );
     506                var emailField = field( 'alnc-f-email', cEmail.label, 'email', 'email', true, cEmail.placeholder );
     507                if ( cPhone.enabled ) {
     508                        // Email and phone share a two-column row.
     509                        form.appendChild( el( 'div', { class: 'alnc-form__row' }, [
     510                                emailField,
     511                                field( 'alnc-f-phone', cPhone.label, 'tel', 'tel', cPhone.required, cPhone.placeholder )
     512                        ] ) );
     513                } else {
     514                        // No phone: email takes the full width, like the name above it.
     515                        form.appendChild( emailField );
     516                }
     517                if ( cReason.enabled ) {
     518                        form.appendChild( textareaField( 'alnc-f-reason', cReason.label, 'reason', cReason.required, cReason.placeholder ) );
     519                }
    492520
    493521                // Recurring (Pro): optional repeat controls.
    … …  
    527555                        var name = form.querySelector( '#alnc-f-name' ).value.trim();
    528556                        var email = form.querySelector( '#alnc-f-email' ).value.trim();
    529                         var phone = form.querySelector( '#alnc-f-phone' ).value.trim();
    530                         var reason = form.querySelector( '#alnc-f-reason' ).value.trim();
     557                        var phoneEl = form.querySelector( '#alnc-f-phone' );
     558                        var reasonEl = form.querySelector( '#alnc-f-reason' );
     559                        var phone = phoneEl ? phoneEl.value.trim() : '';
     560                        var reason = reasonEl ? reasonEl.value.trim() : '';
    531561                        var consented = form.querySelector( '#' + consentId ).checked;
    532562
    533563                        if ( ! name || ! email ) { return showErr( T.required ); }
     564                        if ( ( phoneEl && cPhone.required && ! phone ) || ( reasonEl && cReason.required && ! reason ) ) { return showErr( T.required ); }
    534565                        if ( ! consented ) { return showErr( T.consentReq ); }
    535566
    … …  
    696727
    697728        /* ----- field factories ----- */
    698         function field( id, label, type, name, required ) {
     729        function field( id, label, type, name, required, placeholder ) {
    699730                return el( 'div', { class: 'alnc-field' }, [
    700731                        el( 'label', { class: 'alnc-field-label', for: id, text: label } ),
    701                         el( 'input', { class: 'alnc-input', id: id, name: name, type: type, required: required ? 'required' : null, autocomplete: name } )
     732                        el( 'input', { class: 'alnc-input', id: id, name: name, type: type, required: required ? 'required' : null, placeholder: placeholder || null, autocomplete: name } )
    702733                ] );
    703734        }
    704         function textareaField( id, label, name ) {
     735        function textareaField( id, label, name, required, placeholder ) {
    705736                return el( 'div', { class: 'alnc-field' }, [
    706737                        el( 'label', { class: 'alnc-field-label', for: id, text: label } ),
    707                         el( 'textarea', { class: 'alnc-textarea', id: id, name: name, rows: '3' } )
     738                        el( 'textarea', { class: 'alnc-textarea', id: id, name: name, rows: '3', required: required ? 'required' : null, placeholder: placeholder || null } )
    708739                ] );
    709740        }
  • alnora-clinic/trunk/includes/class-appointment-manager.php

    r3585842 r3744939  
    385385
    386386        /**
    387          * Generate a unique reference number, e.g. alnc-2026-0042.
     387         * Generate a unique reference number, e.g. 26-0042.
     388         *
     389         * Two-digit year, the same as Pro, so a clinic moving between the two keeps one
     390         * numbering style. References issued earlier as 2026-0042 stay as they are: they
     391         * cannot collide with the new 26- series, which is counted on its own.
    388392         *
    389393         * @return string
    … …  
    392396                global $wpdb;
    393397                $table = self::table();
    394                 $year  = current_time( 'Y' );
     398                $year  = (string) current_time( 'y' );
    395399
    396400                // Next sequence = highest existing sequence for this year + 1. Using MAX (not
    … …  
    399403                // failing the insert ("Could not save the appointment").
    400404                $like = $wpdb->esc_like( $year . '-' ) . '%';
    401                 $max  = (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX( CAST( SUBSTRING_INDEX( ref_number, '-', -1 ) AS UNSIGNED ) ) FROM {$table} WHERE ref_number LIKE %s", $like ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     405                $max  = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COALESCE( MAX( CAST( SUBSTRING_INDEX( ref_number, '-', -1 ) AS UNSIGNED ) ), 0 ) FROM {$table} WHERE ref_number LIKE %s", $like ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    402406                $seq  = $max + 1;
    403407
  • alnora-clinic/trunk/includes/class-settings.php

    r3657434 r3744939  
    4444                        // a reinstall. Turn on only if you want everything erased on uninstall.
    4545                        'delete_data_on_uninstall' => 0,
     46                        // Core booking-form fields (Settings → Form). Same keys and defaults as Pro,
     47                        // so a clinic's labels and switches carry over unchanged on upgrade.
     48                        'form_field_name_label'         => 'Full name',
     49                        'form_field_email_label'        => 'Email',
     50                        'form_field_phone_label'        => 'Phone',
     51                        'form_field_phone_enabled'      => 1,
     52                        'form_field_phone_required'     => 0,
     53                        'form_field_reason_label'       => 'Reason for visit (optional)',
     54                        'form_field_reason_enabled'     => 1,
     55                        'form_field_reason_required'    => 0,
     56                        'form_field_name_placeholder'   => '',
     57                        'form_field_email_placeholder'  => '',
     58                        'form_field_phone_placeholder'  => '+1 212 555 0147',
     59                        'form_field_reason_placeholder' => '',
    4660                );
    4761        }
  • alnora-clinic/trunk/readme.txt

    r3742445 r3744939  
    55Tested up to: 7.1.2
    66Requires PHP: 7.4
    7 Stable tag: 1.3.6
     7Stable tag: 1.3.7
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1919No generic salon plugin bent into shape. No spreadsheets. No five add-ons duct-taped together. Just one focused product that understands doctors, services, patients and privacy.
    2020
    21 👉 [Live demo](https://alnora-booking.com/demo/) · [See Pro pricing](https://alnora-booking.com/pricing/) · [Documentation](https://alnora-booking.com/docs/)
     21👉 [Live demo](https://alnora-booking.com/demo/) · [See Pro pricing](https://alnora-booking.com/pricing/) · [Documentation](https://alnora-booking.com/docs/) · [YouTube](https://www.youtube.com/@alnora-clinic)
    2222
    2323**Why private clinics choose Alnora**
    … …  
    150150== Changelog ==
    151151
     152= 1.3.7 =
     153* New: **Settings → Form.** Change the label and placeholder of each field on the booking form: name, email, phone and reason for visit. Phone and reason each have their own **Required** and **Enabled** switches, so you can ask for a phone number on every booking or leave the reason box off the form entirely. Name and email are always shown and always required. A label you leave as it is still shows in the patient's language. When phone is switched off, the email field takes the full width.
     154* New: a required field is marked with an asterisk and checked when the booking is sent, as well as in the form. A field you switch off is never saved, even if something else sends it.
     155* Improvement: **shorter appointment references.** New bookings get references such as 26-0042 instead of 2026-0042, the same style as Pro, so a clinic moving between the two keeps one numbering style. References already issued stay as they are.
     156
    152157= 1.3.6 =
    153158* Updated Freemius SDK to the latest version.
    … …  
    257262== Upgrade Notice ==
    258263
     264= 1.3.7 =
     265New Settings → Form tab to relabel the booking form's fields and make phone or reason required or hidden. New appointment references use the shorter 26-0042 style. Existing references are unchanged. No database changes.
     266
    259267= 1.3.6 =
    260268Updated Freemius SDK to the latest version. No database or configuration changes.
Note: See TracChangeset for help on using the changeset viewer.