array( 'default' => __( 'Please, select booking date(s) at Calendar.', 'booking' ), 'title' => __( 'No booking dates selected', 'booking' ), 'description' => __( 'Shown when the booking form is submitted without selected calendar dates.', 'booking' ), 'group' => 'date_selection', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_range_selection_click_last_date' => array( 'default' => __( 'Start date selected. Now click the last day of your booking to complete the date range.', 'booking' ), 'title' => __( 'Complete date range', 'booking' ), 'description' => __( 'Informational note shown after the first click in two-click range selection mode.', 'booking' ), 'group' => 'date_selection', 'severity' => 'info', 'optional' => true, 'placeholders' => array(), ), 'message_range_selection_has_unavailable_dates' => array( 'default' => __( 'The selected date range includes unavailable or already booked dates, so the selection was cleared. Please choose a range that contains only available dates.', 'booking' ), 'title' => __( 'Unavailable date inside range', 'booking' ), 'description' => __( 'Shown when a selected range contains an unavailable date and must be cleared.', 'booking' ), 'group' => 'date_selection', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_dates_times_unavailable' => array( 'default' => __( 'These dates and times in this calendar are already booked or unavailable.', 'booking' ), 'title' => __( 'Dates or times unavailable', 'booking' ), 'description' => __( 'Shown when the requested dates or times are no longer available.', 'booking' ), 'group' => 'date_selection', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_choose_alternative_dates' => array( 'default' => __( 'Please choose alternative date(s), times, or adjust the number of slots booked.', 'booking' ), 'title' => __( 'Choose alternative dates', 'booking' ), 'description' => __( 'Follow-up instruction for an unavailable date, time, or capacity selection.', 'booking' ), 'group' => 'date_selection', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_cannot_save_in_one_resource' => array( 'default' => __( 'It is not possible to store this sequence of the dates into the one same resource.', 'booking' ), 'title' => __( 'Cannot use one booking resource', 'booking' ), 'description' => __( 'Shown when the selected sequence cannot be stored in one booking resource.', 'booking' ), 'group' => 'date_selection', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_required' => array( 'default' => __( 'This field is required', 'booking' ), 'title' => __( 'Required field', 'booking' ), 'description' => __( 'Shown for an empty required text, select, or other standard field.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_required_for_check_box' => array( 'default' => __( 'This checkbox must be checked', 'booking' ), 'title' => __( 'Required checkbox', 'booking' ), 'description' => __( 'Shown when a required checkbox is not checked.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_required_for_radio_box' => array( 'default' => __( 'At least one option must be selected', 'booking' ), 'title' => __( 'Required option', 'booking' ), 'description' => __( 'Shown when no option is selected in a required radio group.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_email' => array( 'default' => __( 'Incorrect email address', 'booking' ), 'title' => __( 'Invalid email address', 'booking' ), 'description' => __( 'Shown when an email field does not contain a valid email address.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_same_email' => array( 'default' => __( 'Your emails do not match', 'booking' ), 'title' => __( 'Email addresses do not match', 'booking' ), 'description' => __( 'Shown when email confirmation does not match the original email field.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_valid_date' => array( /* translators: Keep the {date_example} placeholder unchanged. */ 'default' => __( 'This field must be a valid date like {date_example}', 'booking' ), 'title' => __( 'Invalid date format', 'booking' ), 'description' => __( 'Shown for a field using the validate_as_date CSS class.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array( '{date_example}' ), ), 'message_check_digits_only' => array( 'default' => __( 'This field must contain only digits', 'booking' ), 'title' => __( 'Digits only', 'booking' ), 'description' => __( 'Shown for a field using the validate_as_digit CSS class.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_check_digits_count' => array( /* translators: Keep the {digits} placeholder unchanged. */ 'default' => __( 'This field must contain {digits} digits', 'booking' ), 'title' => __( 'Required number of digits', 'booking' ), 'description' => __( 'Shown for validate_digit_N fields. Keep the {digits} placeholder.', 'booking' ), 'group' => 'form_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array( '{digits}' ), ), 'message_error_start_time' => array( 'default' => __( 'Start Time is invalid. The date or time may be booked, or already in the past! Please choose another date or time.', 'booking' ), 'title' => __( 'Invalid start time', 'booking' ), 'description' => __( 'Shown when a selected start time cannot be booked.', 'booking' ), 'group' => 'time_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_error_end_time' => array( 'default' => __( 'End Time is invalid. The date or time may be booked, or already in the past. The End Time may also be earlier that the start time, if only 1 day was selected! Please choose another date or time.', 'booking' ), 'title' => __( 'Invalid end time', 'booking' ), 'description' => __( 'Shown when a selected end time cannot be booked.', 'booking' ), 'group' => 'time_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_error_range_time' => array( 'default' => __( 'The time(s) may be booked, or already in the past!', 'booking' ), 'title' => __( 'Invalid time range', 'booking' ), 'description' => __( 'Shown when a range-time selection is unavailable or in the past.', 'booking' ), 'group' => 'time_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_error_duration_time' => array( 'default' => __( 'The time(s) may be booked, or already in the past!', 'booking' ), 'title' => __( 'Invalid duration', 'booking' ), 'description' => __( 'Shown when a duration-time selection is unavailable or in the past.', 'booking' ), 'group' => 'time_validation', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_booking_saving' => array( 'default' => __( 'Saving', 'booking' ), 'title' => __( 'Saving booking', 'booking' ), 'description' => __( 'Short progress text shown while the booking is being submitted.', 'booking' ), 'group' => 'submission', 'severity' => 'info', 'optional' => false, 'placeholders' => array(), ), 'message_duplicate_booking_resource_on_page' => array( /* translators: Keep the {resource_id} placeholder unchanged. */ 'default' => __( 'More than one calendar or booking form for Booking Resource ID {resource_id} is present on this page. A hidden copy still conflicts with the visible form and can prevent booking submission. The site administrator must keep only one calendar or form for this Booking Resource. After the page is corrected, reload it and try again.', 'booking' ), 'title' => __( 'Duplicate Booking Resource on page', 'booking' ), 'description' => __( 'Shown when more than one calendar or booking form uses the same Booking Resource ID and one copy cannot initialize.', 'booking' ), 'group' => 'submission', 'severity' => 'warning', 'optional' => false, 'placeholders' => array( '{resource_id}' ), ), 'message_booking_form_context_required' => array( /* translators: Keep the {resource_id} placeholder unchanged. */ 'default' => __( 'This booking form is not connected to a valid calendar for Booking Resource ID {resource_id}. This can happen when the same Booking Resource is used more than once on the page, including in hidden content, or when the page cache is outdated. Reload the page and try again. If the problem continues, ask the site administrator to remove duplicate forms for this Booking Resource and clear the page cache.', 'booking' ), 'title' => __( 'Booking form is not connected to its calendar', 'booking' ), 'description' => __( 'Shown when a public booking submission is missing its server-signed Booking Form context.', 'booking' ), 'group' => 'submission', 'severity' => 'warning', 'optional' => false, 'placeholders' => array( '{resource_id}' ), ), 'message_booking_form_context_expired' => array( /* translators: Keep the {resource_id} placeholder unchanged. */ 'default' => __( 'This booking form was generated by an older or cached version of the page for Booking Resource ID {resource_id}. Reload the page and try again. If the problem continues, ask the site administrator to clear the page cache and remove any duplicate calendar or form for this Booking Resource.', 'booking' ), 'title' => __( 'Booking form context is outdated', 'booking' ), 'description' => __( 'Shown when a signed Booking Form context uses an older unsupported contract version.', 'booking' ), 'group' => 'submission', 'severity' => 'warning', 'optional' => false, 'placeholders' => array( '{resource_id}' ), ), 'message_unexpected_server_response' => array( 'default' => __( 'The server returned an unexpected response. Please reload the page and try again. If the problem continues, contact the website administrator.', 'booking' ), 'title' => __( 'Unexpected server response', 'booking' ), 'description' => __( 'Shown when calendar loading or booking submission does not return valid Booking Calendar data.', 'booking' ), 'group' => 'submission', 'severity' => 'error', 'optional' => false, 'placeholders' => array(), ), 'message_http_conflict_cache_hint' => array( 'default' => __( 'A cached or optimized version of this page may be outdated. Clear the browser cache and reload the page. Website administrators should also purge page, server, and CDN caches and regenerate minified or optimized files.', 'booking' ), 'title' => __( 'HTTP conflict cache troubleshooting', 'booking' ), 'description' => __( 'Shown after an HTTP 409 calendar-loading failure when stale cached or optimized content may be involved.', 'booking' ), 'group' => 'submission', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), 'message_resource_selector_summary_label' => array( 'default' => __( 'Booking Resource', 'booking' ), 'title' => __( 'Selected Booking Resource label', 'booking' ), 'description' => __( 'Shown above the selected Booking Resource title on the booking form step.', 'booking' ), 'group' => 'booking_summaries', 'severity' => 'info', 'optional' => false, 'placeholders' => array(), ), 'message_resource_selector_start_over' => array( 'default' => __( 'Start over', 'booking' ), 'title' => __( 'Booking Resource Start over button', 'booking' ), 'description' => __( 'Button text that returns a visitor to Booking Resource selection.', 'booking' ), 'group' => 'booking_summaries', 'severity' => 'info', 'optional' => false, 'placeholders' => array(), ), 'message_appointment_summary_label' => array( 'default' => __( 'Your appointment', 'booking' ), 'title' => __( 'Appointment summary label', 'booking' ), 'description' => __( 'Shown above the selected Service title on the Appointment booking form step.', 'booking' ), 'group' => 'booking_summaries', 'severity' => 'info', 'optional' => false, 'placeholders' => array(), ), 'message_appointment_start_over' => array( 'default' => __( 'Start over', 'booking' ), 'title' => __( 'Appointment Start over button', 'booking' ), 'description' => __( 'Button text that returns a visitor to Service selection.', 'booking' ), 'group' => 'booking_summaries', 'severity' => 'info', 'optional' => false, 'placeholders' => array(), ), 'message_captcha_incorrect' => array( 'default' => __( 'The code you entered is incorrect', 'booking' ), 'title' => __( 'Incorrect CAPTCHA', 'booking' ), 'description' => __( 'Shown after an incorrect CAPTCHA response.', 'booking' ), 'group' => 'captcha', 'severity' => 'warning', 'optional' => false, 'placeholders' => array(), ), ); /** * Filters the visitor-facing message registry. * * @param array $registry Message definitions keyed by stable message ID. */ return apply_filters( 'wpbc_frontend_message_registry', $registry ); } /** * Get normalized saved message settings. * * @return array */ function wpbc_frontend_messages__get_settings() { $settings = get_bk_option( 'booking_frontend_messages' ); if ( ! is_array( $settings ) ) { $settings = array(); } // Accept an early flat override array as a compatibility fallback. if ( ! isset( $settings['messages'] ) ) { $flat = array_intersect_key( $settings, wpbc_frontend_messages__get_registry() ); $settings = array( 'version' => 1, 'messages' => $flat, 'enabled' => array() ); } return array( 'version' => 1, 'messages' => ( isset( $settings['messages'] ) && is_array( $settings['messages'] ) ) ? $settings['messages'] : array(), 'enabled' => ( isset( $settings['enabled'] ) && is_array( $settings['enabled'] ) ) ? $settings['enabled'] : array(), ); } /** * Resolve a message for the active Booking Calendar locale. * * @param string $message_key Stable registry key. * @param array $replacements Optional literal placeholder replacements. * @param int $resource_id Optional resource ID reserved for future scoped overrides. * @return string */ function wpbc_frontend_messages__get( $message_key, $replacements = array(), $resource_id = 0 ) { $registry = wpbc_frontend_messages__get_registry(); if ( ! isset( $registry[ $message_key ] ) ) { return ''; } $settings = wpbc_frontend_messages__get_settings(); if ( isset( $settings['messages'][ $message_key ] ) && is_string( $settings['messages'][ $message_key ] ) && '' !== trim( $settings['messages'][ $message_key ] ) ) { $override = trim( $settings['messages'][ $message_key ] ); // Translation-only overrides inherit the live gettext default instead of freezing the administrator's locale. if ( 0 === strpos( $override, '[lang=' ) ) { $override = $registry[ $message_key ]['default'] . "\n" . $override; } $value = wpbc_lang( $override ); } else { $value = $registry[ $message_key ]['default']; } if ( is_array( $replacements ) && ! empty( $replacements ) ) { $safe_replacements = array(); foreach ( $replacements as $placeholder => $replacement ) { $safe_replacements[ (string) $placeholder ] = sanitize_text_field( (string) $replacement ); } $value = strtr( $value, $safe_replacements ); } /** * Filters a resolved visitor-facing message before final plain-text sanitization. * * @param string $value Message text. * @param string $message_key Registry key. * @param int $resource_id Booking resource ID, or zero for global context. */ $value = apply_filters( 'wpbc_frontend_message_value', $value, $message_key, absint( $resource_id ) ); if ( ! is_scalar( $value ) ) { $value = $registry[ $message_key ]['default']; } return sanitize_textarea_field( (string) $value ); } /** * Check whether an optional message is enabled. * * @param string $message_key Registry key. * @return bool */ function wpbc_frontend_messages__is_enabled( $message_key ) { $registry = wpbc_frontend_messages__get_registry(); if ( ! isset( $registry[ $message_key ] ) ) { return false; } if ( empty( $registry[ $message_key ]['optional'] ) ) { return true; } $settings = wpbc_frontend_messages__get_settings(); return ! isset( $settings['enabled'][ $message_key ] ) || 'Off' !== $settings['enabled'][ $message_key ]; } /** * Split a validated [lang=LOCALE] message into editable sections. * * @param string $value Stored message value. * @return array */ function wpbc_frontend_messages__parse_language_sections( $value ) { $result = array( 'default' => '', 'translations' => array() ); $value = (string) $value; if ( '' === $value ) { return $result; } if ( ! preg_match_all( '/\[lang=([A-Za-z0-9_@.\-]+)\]/', $value, $matches, PREG_OFFSET_CAPTURE ) ) { $result['default'] = trim( $value ); return $result; } $result['default'] = trim( substr( $value, 0, $matches[0][0][1] ) ); $count = count( $matches[0] ); for ( $index = 0; $index < $count; $index++ ) { $locale = $matches[1][ $index ][0]; $start = $matches[0][ $index ][1] + strlen( $matches[0][ $index ][0] ); $end = ( $index + 1 < $count ) ? $matches[0][ $index + 1 ][1] : strlen( $value ); $result['translations'][ $locale ] = trim( substr( $value, $start, $end - $start ) ); } return $result; } /** * Validate and sanitize one raw multilingual override. * * @param string $message_key Registry key. * @param mixed $raw_value Posted value. * @return string|WP_Error */ function wpbc_frontend_messages__validate_value( $message_key, $raw_value ) { $registry = wpbc_frontend_messages__get_registry(); if ( ! isset( $registry[ $message_key ] ) || ! is_scalar( $raw_value ) ) { return new WP_Error( 'invalid_message', __( 'Invalid message value.', 'booking' ) ); } $value = str_replace( array( "\r\n", "\r" ), "\n", wp_unslash( (string) $raw_value ) ); $value = trim( $value ); if ( '' === $value ) { return ''; } if ( 4000 < strlen( $value ) ) { return new WP_Error( 'message_too_long', __( 'A form message cannot exceed 4000 characters.', 'booking' ) ); } if ( false !== strpos( $value, '<' ) || false !== strpos( $value, '>' ) ) { return new WP_Error( 'message_html_not_allowed', __( 'HTML is not allowed in form messages.', 'booking' ) ); } $sanitized = sanitize_textarea_field( $value ); $without_valid_tags = preg_replace( '/\[lang=([A-Za-z0-9_@.\-]+)\]/', '', $sanitized ); if ( false !== stripos( $without_valid_tags, '[lang' ) ) { return new WP_Error( 'invalid_language_tag', __( 'A language section contains an invalid [lang=LOCALE] tag.', 'booking' ) ); } $sections = wpbc_frontend_messages__parse_language_sections( $sanitized ); if ( preg_match_all( '/\[lang=([A-Za-z0-9_@.\-]+)\]/', $sanitized, $locale_matches ) ) { if ( count( $locale_matches[1] ) !== count( array_unique( $locale_matches[1] ) ) ) { return new WP_Error( 'duplicate_language', __( 'Each locale can be used only once in a message.', 'booking' ) ); } foreach ( $sections['translations'] as $translation ) { if ( '' === trim( $translation ) ) { return new WP_Error( 'empty_translation', __( 'Language sections cannot be empty.', 'booking' ) ); } } } $placeholder_default = '' !== $sections['default'] ? $sections['default'] : $registry[ $message_key ]['default']; $all_sections = array_merge( array( $placeholder_default ), array_values( $sections['translations'] ) ); foreach ( $registry[ $message_key ]['placeholders'] as $placeholder ) { foreach ( $all_sections as $section_text ) { if ( false === strpos( $section_text, $placeholder ) ) { return new WP_Error( 'missing_placeholder', sprintf( __( 'The message must contain the %s placeholder in every language.', 'booking' ), $placeholder ) ); } } } return $sanitized; }