--- a/booking/core/timeline/v2/wpbc-class-timeline_v2.php
+++ b/booking/core/timeline/v2/wpbc-class-timeline_v2.php
@@ -258,7 +258,38 @@
if ( ! defined( 'WPBC_TIMELINE_AJAX' ) ) { define( 'WPBC_TIMELINE_AJAX', true ); } // FixIn: 8.4.7.13.
- $this->is_frontend = (bool) $attr['is_frontend'];;
+ // FixIn: 10.14.14.2.
+ $is_frontend = (bool) $attr['is_frontend'];
+
+ // Not front-end (or possibly changed parameter by attacker to see the bookings).
+ if ( ! $is_frontend ) {
+
+ // If this is not front-end -- admin side, then allow do this only for logged in users, with minimum user role, defined in the settings.
+ // Get minimum user role to access the Timeline in admin panel (Booking Listing and Timeline Overview (Calendar Overview).
+ $curr_user_role = get_bk_option( 'booking_user_role_booking' );
+
+ // Get current user.
+ $current_user = wpbc_get_current_user();
+ $user_role_map = array(
+ 'administrator' => 10,
+ 'editor' => 7,
+ 'author' => 2,
+ 'contributor' => 1,
+ 'subscriber' => 0,
+ );
+
+ $level = 0;
+ if ( isset( $user_role_map[ $curr_user_role ] ) ) {
+ $level = $user_role_map[ $curr_user_role ];
+ }
+
+ if ( empty( $current_user ) || empty( $current_user->user_level ) || ( $current_user->user_level < $level ) ) {
+ // Security Fix: Enforce frontend mode for non-admins.
+ $attr['is_frontend'] = 1;
+ }
+ }
+
+ $this->is_frontend = (bool) $attr['is_frontend'];
//Ovverride some parameters
//if ( isset( $attr['resource_id'] ) ) { $attr['type'] = $attr['resource_id']; }
--- a/booking/includes/_capacity/captcha_simple_text.php
+++ b/booking/includes/_capacity/captcha_simple_text.php
@@ -17,30 +17,32 @@
*/
function wpbc_captcha__in_ajx__check( $request_params, $is_from_admin_panel , $original_ajx_search_params ) {
- if (
- ( 'On' === get_bk_option( 'booking_is_use_captcha' ) )
- && ( ! $is_from_admin_panel )
- && ( ( isset( $original_ajx_search_params['captcha_user_input'] ) ) && ( isset( $original_ajx_search_params['captcha_chalange'] ) ) )
+ if (
+ ( ( 'On' === get_bk_option( 'booking_is_use_captcha' ) ) || ( WPBC_NEW_FORM_BUILDER ) ) &&
+ ( ! $is_from_admin_panel ) &&
+ ( ( isset( $original_ajx_search_params['captcha_user_input'] ) ) && ( isset( $original_ajx_search_params['captcha_chalange'] ) ) )
) {
if ( ! wpbc_captcha__simple__is_ansfer_correct( $request_params['captcha_user_input'], $request_params['captcha_chalange'] ) ) {
$captcha_arr = wpbc_captcha__simple__generate_new();
- $ajx_data_arr = array();
- $ajx_data_arr['status'] = 'error';
- $ajx_data_arr['status_error'] = 'captcha_simple_wrong';
- $ajx_data_arr['captcha__simple'] = $captcha_arr;
+ $ajx_data_arr = array();
+ $ajx_data_arr['status'] = 'error';
+ $ajx_data_arr['status_error'] = 'captcha_simple_wrong';
+ $ajx_data_arr['captcha__simple'] = $captcha_arr;
$ajx_data_arr['ajx_after_action_message'] = __( 'The code you entered is incorrect', 'booking' );
$ajx_data_arr['ajx_after_action_message_status'] = 'warning';
- wp_send_json( array(
- 'ajx_data' => $ajx_data_arr,
- 'ajx_search_params' => $original_ajx_search_params,
- 'ajx_cleaned_params' => $request_params,
- 'resource_id' => $request_params['resource_id']
- ) );
- // After this page will die;
+ wp_send_json(
+ array(
+ 'ajx_data' => $ajx_data_arr,
+ 'ajx_search_params' => $original_ajx_search_params,
+ 'ajx_cleaned_params' => $request_params,
+ 'resource_id' => $request_params['resource_id'],
+ )
+ );
+ // After this page will die;.
}
}
}
@@ -186,7 +188,7 @@
*/
function wpbc_booking_form_is_captcha_allowed_here() {
- if ( 'On' !== get_bk_option( 'booking_is_use_captcha' ) ) {
+ if ( ( 'On' !== get_bk_option( 'booking_is_use_captcha' ) ) && ( ! WPBC_NEW_FORM_BUILDER ) ) {
return false;
}
--- a/booking/includes/_functions/calendar_scripts.php
+++ b/booking/includes/_functions/calendar_scripts.php
@@ -10,6 +10,7 @@
* @email info@wpbookingcalendar.com
*
* @modified 2025-07-19
+ * @file: ../includes/_functions/calendar_scripts.php
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -69,31 +70,29 @@
border-radius: 30px;
animation: calendar_loader_bar_progress 3s infinite linear;
}
- @keyframes calendar_loader_bar_progress {
- to { width: 100%; }
- }
+ @keyframes calendar_loader_bar_progress { to { width: 100%; } }
@media (prefers-reduced-motion: reduce) {
- .calendar_loader_frame__progress_line {
- animation: none;
- width: 50%;
- }
+ .calendar_loader_frame__progress_line { animation: none; width: 50%; }
}
";
- // Elementor-safe path: collect CSS into assets manager (no <style> inside content).
+ // Elementor-safe path: attach CSS to an enqueued WPBC stylesheet.
if ( class_exists( 'WPBC_FE_Assets' ) ) {
- // If wp_head already fired, printing in head is too late => push to footer. // did_action( 'wp_head' ) ? 'footer' : 'head'; // .
- $position = 'head';
+ // Ensure the target style is enqueued (safe-check anyway).
+ if ( function_exists( 'wp_enqueue_style' ) ) {
+ wp_enqueue_style( 'wpbc-ui-both', wpbc_plugin_url( '/css/wpbc_ui_both.css' ), array(), WP_BK_VERSION_NUM ); // FixIn: 10.0.0.25.
+ }
+ // FixIn: 10.14.14.1.
+ $added = WPBC_FE_Assets::add_inline_css_to_wp_style( 'wpbc-ui-both', $css, 'wpbc:calendar-loader-css' );
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo WPBC_FE_Assets::add_inline_css( $css, 'wpbc_calendar_loader_inline_css', $position );
+ if ( $added ) {
+ return; // Do NOT print <style> inside content.
+ }
}
- // Legacy fallback (may be stripped by some builders; kept for backward compatibility).
- ?>
- <style id="wpbc_calendar_loader_inline_css"><?php echo $css; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></style>
- <?php
+ // Legacy fallback (only when WP inline style cannot be attached).
+ echo "n" . '<style id="wpbc_calendar_loader_inline_css">' . "n" . $css . "n" . '</style>' . "n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
--- a/booking/includes/fontend/class-fe-inline-css-js.php
+++ b/booking/includes/fontend/class-fe-inline-css-js.php
@@ -8,11 +8,6 @@
* IMPORTANT:
* This is NOT “Elementor-safe” by design. It outputs inline tags at the call site.
*
- * Usage (prints immediately where called):
- *
- * WPBC_FE_Assets::add_inline_css( $css, 'wpbc_calendar_loader_inline_css' );
- * WPBC_FE_Assets::add_jq_ready_js( $js_body, 'wpbc:calendar-loader:init' );
- *
* @file: includes/fontend/class-fe-inline-css-js.php
* @package Booking Calendar
* @since 11.0.x
@@ -33,6 +28,10 @@
/** @var array */
protected static $printed_js = array();
+ // ------------------------------------------------------------------------------------------------
+ // == CSS ==
+ // ------------------------------------------------------------------------------------------------
+
/**
* Print inline CSS immediately (deduped by key).
*
@@ -58,8 +57,53 @@
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return "n" . '<style id="' . esc_attr( $id ) . '">' . "n" . $css . "n" . '</style>' . "n";
+
+ // wp_add_inline_style( $id, $css ); ???
+ }
+
+ /**
+ * Add inline CSS via WP style handle (Elementor-safe).
+ * Avoids printing <style> tags inside content.
+ *
+ * @param string $handle Style handle (e.g. 'wpbc-ui-both').
+ * @param string $css CSS text.
+ * @param string $key Dedupe key (optional).
+ *
+ * @return bool True if added / already added, false if not possible.
+ */
+ public static function add_inline_css_to_wp_style( $handle, $css, $key = '' ) {
+ // FixIn: 10.14.14.1.
+ $handle = is_scalar( $handle ) ? trim( (string) $handle ) : '';
+ $css = is_scalar( $css ) ? trim( (string) $css ) : '';
+
+ if ( '' === $handle || '' === $css ) {
+ return false;
+ }
+
+ if ( ! function_exists( 'wp_add_inline_style' ) ) {
+ return false;
+ }
+
+ // Handle must exist in WP styles system.
+ if ( ! wp_style_is( $handle, 'enqueued' ) && ! wp_style_is( $handle, 'done' ) && ! wp_style_is( $handle, 'registered' ) ) {
+ return false;
+ }
+
+ $key = self::normalize_key( $key, $css, 'css' );
+ if ( isset( self::$printed_css[ $key ] ) ) {
+ return true;
+ }
+ self::$printed_css[ $key ] = true;
+
+ wp_add_inline_style( $handle, $css );
+
+ return true;
}
+ // ------------------------------------------------------------------------------------------------
+ // == JS ==
+ // ------------------------------------------------------------------------------------------------
+
/**
* Print inline JS immediately (deduped by key).
*
@@ -89,7 +133,7 @@
}
/**
- * Print JS wrapped into wpbc_jq_ready_start()/wpbc_jq_ready_end(), immediately.
+ * Deprecated (use this add_jq_ready_js_to_wp_script instead): Print JS wrapped into wpbc_jq_ready_start()/wpbc_jq_ready_end(), immediately.
*
* @param string $js_body
* @param string $key
--- a/booking/includes/fontend/class-fe-render-form-body.php
+++ b/booking/includes/fontend/class-fe-render-form-body.php
@@ -10,7 +10,7 @@
*
* @package Booking Calendar
* @since 11.0.x
- * @file ../includes/fontend/class-fe-form-body-rendering.php
+ * @file ../includes/fontend/class-fe-render-form-body.php
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -99,6 +99,10 @@
$form_html = apply_filters( 'wpbc_booking_form_content__after_load', $form_html, $resource_id, $custom_booking_form );
}
+
+ // 1. Body HTML, before you inject calendar/captcha/extra calendars. Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
+ $form_html = apply_filters( 'wpbc_booking_form__body_html__before_postprocess', $form_html, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
+
$form_html = WPBC_FE_Form_Postprocessor::apply( $form_html, $calendar_html, array(
'resource_id' => $resource_id,
'custom_booking_form' => $custom_booking_form,
@@ -110,14 +114,24 @@
'nl' => $nl,
) );
+ // 2. Composed booking form HTML (calendar already inserted). Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
+ $form_html = apply_filters( 'wpbc_booking_form__html__before_wrapper', $form_html, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
+
$wrapped = WPBC_FE_Form_Wrapper::wrap( $form_html, $resource_id );
+ // 3. Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
+ $wrapped = apply_filters( 'wpbc_booking_form__wrapped_html__before_inline_scripts', $wrapped, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
+
$wrapped .= WPBC_FE_Inline_Scripts::collect( $resource_id, $selected_dates_without_calendar );
+ // 4. Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
+ $wrapped = apply_filters( 'wpbc_booking_form__wrapped_html__after_inline_scripts', $wrapped, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
+
return $wrapped;
}
}
+
/**
* Calendar markup builder for booking form composition.
*
@@ -237,8 +251,60 @@
$bfb_loader_args['form_id'] = (int) $custom_params['bfb_form_id'];
}
- // Get shortcodes source from BFB loader.
- $bfb_source = wpbc_bfb_get_booking_form_source( $bfb_loader_args );
+ $bfb_settings = array();
+ $bfb_source = trim( '' );
+ $settings_json = trim( '' );
+
+ if ( function_exists( 'wpbc_bfb_get_booking_form_pair' ) ) {
+
+ $bfb_pair = wpbc_bfb_get_booking_form_pair( $bfb_loader_args );
+
+ if ( is_array( $bfb_pair ) ) {
+ $bfb_source = isset( $bfb_pair['form'] ) ? (string) $bfb_pair['form'] : '';
+ $settings_json = isset( $bfb_pair['settings_json'] ) ? (string) $bfb_pair['settings_json'] : '';
+ }
+
+ } else {
+
+ // Fallback (old behavior).
+ $bfb_source = wpbc_bfb_get_booking_form_source( $bfb_loader_args );
+ }
+
+ if ( '' !== $settings_json ) {
+ $decoded = json_decode( $settings_json, true );
+ if ( is_array( $decoded ) ) {
+ $bfb_settings = $decoded;
+ }
+ }
+
+ // Allow loader to return either string OR array with settings.
+ if ( is_array( $bfb_source ) ) {
+
+ // Common possible keys (support multiple formats, future-proof).
+ if ( ! empty( $bfb_source['settings'] ) && is_array( $bfb_source['settings'] ) ) {
+ $bfb_settings = $bfb_source['settings'];
+ } elseif ( ! empty( $bfb_source['settings_json'] ) && is_string( $bfb_source['settings_json'] ) ) {
+ $decoded = json_decode( $bfb_source['settings_json'], true );
+ if ( is_array( $decoded ) ) {
+ $bfb_settings = $decoded;
+ }
+ }
+
+ // Source itself.
+ if ( isset( $bfb_source['advanced_form'] ) ) {
+ $bfb_source = (string) $bfb_source['advanced_form'];
+ } elseif ( isset( $bfb_source['source'] ) ) {
+ $bfb_source = (string) $bfb_source['source'];
+ } else {
+ $bfb_source = '';
+ }
+ }
+
+ // Optional fallback hook if you keep loader returning string for now.
+ if ( empty( $bfb_settings ) ) {
+ $bfb_settings = apply_filters( 'wpbc_bfb_form_settings_for_render', array(), $bfb_loader_args, $resource_id, $custom_booking_form, $custom_params );
+ }
+
if ( '' !== trim( (string) $bfb_source ) ) {
@@ -272,6 +338,7 @@
return array(
'body_html' => $bfb_form_html,
'apply_after_load_filter' => ! empty( $resolved['apply_after_load_filter'] ),
+ 'bfb_settings' => $bfb_settings,
);
}
}
@@ -289,6 +356,7 @@
return array(
'body_html' => $legacy_instance->wpdev_bk_personal->get_booking_form( $resource_id, $custom_booking_form, $custom_params ),
'apply_after_load_filter' => false,
+ 'bfb_settings' => array(),
);
}
@@ -298,6 +366,7 @@
return array(
'body_html' => wpbc_simple_form__get_booking_form__as_html( $resource_id, $custom_booking_form, $custom_params ),
'apply_after_load_filter' => true,
+ 'bfb_settings' => array(),
);
}
@@ -457,7 +526,7 @@
* @param int $resource_id
* @param string $selected_dates_without_calendar
*
- * @return void
+ * @return string Inline scripts HTML.
*/
public static function collect( $resource_id, $selected_dates_without_calendar ) {
@@ -560,3 +629,126 @@
return $js_body;
}
}
+
+class WPBC_FE_Form_Style_Injector {
+
+ /**
+ * Inject CSS variables into the FIRST <div ... class="... wpbc_bfb_form ..."> tag.
+ *
+ * @param string $html
+ * @param array $css_vars Map: '--var-name' => 'value'
+ *
+ * @return string
+ */
+ public static function inject_css_vars_into_bfb_root( $html, $css_vars ) {
+
+ $html = (string) $html;
+ $css_vars = is_array( $css_vars ) ? $css_vars : array();
+
+ if ( empty( $css_vars ) ) {
+ return $html;
+ }
+ if ( false === strpos( $html, 'wpbc_bfb_form' ) ) {
+ return $html;
+ }
+
+ $style_append = self::build_css_vars_style_fragment( $css_vars );
+ if ( '' === $style_append ) {
+ return $html;
+ }
+
+ $pattern = '/<divb[^>]*bclasss*=s*(["'])(?:(?!1).)*bwpbc_bfb_formb(?:(?!1).)*1[^>]*>/i';
+
+ return preg_replace_callback(
+ $pattern,
+ function( $m ) use ( $style_append ) {
+
+ $tag = $m[0];
+
+ // If style already exists: append.
+ if ( preg_match( '/bstyles*=s*(["'])(.*?)1/i', $tag, $sm ) ) {
+
+ $quote = $sm[1];
+ $existing = (string) $sm[2];
+
+ $merged = trim( $existing );
+ if ( ( '' !== $merged ) && ( ';' !== substr( $merged, -1 ) ) ) {
+ $merged .= ';';
+ }
+ $merged .= $style_append;
+
+ $tag = preg_replace(
+ '/bstyles*=s*(["'])(.*?)1/i',
+ 'style=' . $quote . esc_attr( $merged ) . $quote,
+ $tag,
+ 1
+ );
+
+ return $tag;
+ }
+
+ // No style attr: add it.
+ $tag = rtrim( $tag, '>' ) . ' style="' . esc_attr( $style_append ) . '">';
+
+ return $tag;
+ },
+ $html,
+ 1
+ );
+ }
+
+ /**
+ * Build CSS vars fragment: "--a:1;--b:2;"
+ *
+ * @param array $css_vars
+ *
+ * @return string
+ */
+ private static function build_css_vars_style_fragment( $css_vars ) {
+
+ $out = '';
+
+ foreach ( $css_vars as $name => $value ) {
+
+ $name = self::sanitize_css_var_name( $name );
+ $value = self::sanitize_css_var_value( $value );
+
+ if ( '' === $name || '' === $value ) {
+ continue;
+ }
+
+ $out .= $name . ':' . $value . ';';
+ }
+
+ return $out;
+ }
+
+ private static function sanitize_css_var_name( $name ) {
+
+ $name = is_scalar( $name ) ? trim( (string) $name ) : '';
+
+ // Keep it strict: only CSS custom properties, preferably your namespace.
+ if ( ! preg_match( '/^--[a-z0-9-_]+$/i', $name ) ) {
+ return '';
+ }
+ // Optional: enforce prefix to avoid abusing other vars.
+ if ( 0 !== strpos( $name, '--wpbc-' ) && 0 !== strpos( $name, '--wpbc_bfb-' ) ) {
+ return '';
+ }
+
+ return $name;
+ }
+
+ private static function sanitize_css_var_value( $value ) {
+
+ $value = is_scalar( $value ) ? trim( (string) $value ) : '';
+ if ( '' === $value ) {
+ return '';
+ }
+
+ // Remove dangerous characters for inline style attribute.
+ $value = str_replace( array( '"', "'", '<', '>', "n", "r" ), '', $value );
+
+ return $value;
+ }
+}
--- a/booking/includes/fontend/hooks/class-fe-bfb-settings-hooks.php
+++ b/booking/includes/fontend/hooks/class-fe-bfb-settings-hooks.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * BFB Settings -> Front-End Booking Form Post-Processing Hooks
+ *
+ * This file registers hook callbacks that apply **BFB form settings** (decoded JSON settings array) to the final booking form HTML that is rendered on the front-end.
+ *
+ * These hooks are executed inside `WPBC_FE_Form_Body_Renderer::render()` in this order:
+ *
+ * 1) `wpbc_booking_form__body_html__before_postprocess` --> raw **form body HTML only** (no injected calendar/captcha/extra calendars yet).
+ *
+ * 2) `wpbc_booking_form__html__before_wrapper` --> composed **form HTML** where calendar/captcha/extra calendars already injected,
+ *
+ * 3) `wpbc_booking_form__wrapped_html__before_inline_scripts --> full **wrapped HTML** including the wrapper `<div class="wpbc_container wpbc_form ...">` and the `<form>` markup
+ *
+ * 4) `wpbc_booking_form__wrapped_html__after_inline_scripts` --> the final HTML after inline scripts are appended.
+ *
+ * Notes:
+ * - `$bfb_settings` is expected to be an array like:
+ * [
+ * 'options' => [ 'booking_form_theme' => 'wpbc_theme_dark_1', ... ],
+ * 'css_vars' => [ '--wpbc-bfb-form-max-width' => '430px', ... ]
+ * ]
+ *
+ * @package Booking Calendar
+ * @since 11.0.x
+ * @file ../includes/fontend/hooks/class-fe-bfb-settings-hooks.php
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Inject per-form CSS variables into the BFB form root wrapper. On Step 1.
+ *
+ * Purpose:
+ * - If the form body contains the BFB root element (class `wpbc_bfb_form`),
+ * inject CSS Custom Properties (variables) into its `style` attribute.
+ *
+ * This hook must run **before** legacy post-processing inserts calendars/captcha, because it targets the BFB body root and should not depend on wrapper existence.
+ *
+ * Expected `$bfb_settings` format: - `$bfb_settings['css_vars']` is an associative array: `--var-name` => `value`.
+ *
+ * @since 11.0.x
+ *
+ * @param string $form_html Raw booking form body HTML (no calendar/captcha injected yet).
+ * @param array $bfb_settings Decoded BFB settings array (may be empty).
+ * @param int $resource_id Booking resource ID.
+ * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
+ *
+ * @return string Filtered body HTML.
+ */
+function wpbc_booking_form__body_html__before_postprocess__apply_css_vars( $form_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
+
+ $form_html = (string) $form_html;
+ $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
+
+ // Nothing to apply.
+ if ( empty( $bfb_settings['css_vars'] ) || ! is_array( $bfb_settings['css_vars'] ) ) {
+ return $form_html;
+ }
+
+ // Injector not available.
+ if ( ! class_exists( 'WPBC_FE_Form_Style_Injector' ) ) {
+ return $form_html;
+ }
+
+ // Inject only if a BFB root exists inside the body.
+ return WPBC_FE_Form_Style_Injector::inject_css_vars_into_bfb_root( $form_html, $bfb_settings['css_vars'] );
+}
+add_filter( 'wpbc_booking_form__body_html__before_postprocess', 'wpbc_booking_form__body_html__before_postprocess__apply_css_vars', 10, 4 );
+
+
+/**
+ * Apply a "theme" CSS class to the main booking form wrapper container. On Step 3.
+ *
+ * Purpose:
+ * - Add `$bfb_settings['options']['booking_form_theme']` as an extra class to the FIRST wrapper container:
+ * `<div class="... wpbc_container wpbc_form ...">`
+ *
+ * Why here:
+ * - The wrapper container does not exist until `WPBC_FE_Form_Wrapper::wrap()` runs, therefore this must be executed on the wrapped HTML stage.
+ *
+ * Potential limitations:
+ * - This implementation supports a **single** class value. If you plan to support multiple
+ * classes in one setting (space-separated), you should split and sanitize each class.
+ *
+ * @since 11.0.x
+ *
+ * @param string $wrapped_html Fully wrapped booking form HTML (before inline scripts appended).
+ * @param array $bfb_settings Decoded BFB settings array (may be empty).
+ * @param int $resource_id Booking resource ID.
+ * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
+ *
+ * @return string Filtered wrapped HTML.
+ */
+function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
+
+ $wrapped_html = (string) $wrapped_html;
+ $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
+
+ if ( empty( $bfb_settings['options']['booking_form_theme'] ) ) {
+ return $wrapped_html;
+ }
+
+ // Get theme class(es).
+ $raw_theme = trim( (string) $bfb_settings['options']['booking_form_theme'] );
+ $theme_class_arr = array(); // token => true .
+ $parts = preg_split( '/s+/', $raw_theme, - 1, PREG_SPLIT_NO_EMPTY );
+ foreach ( $parts as $part ) {
+ $tok = sanitize_html_class( (string) $part );
+ if ( '' === $tok ) { continue; }
+ $theme_class_arr[ $tok ] = true;
+ }
+ $theme_class_arr = array_keys( $theme_class_arr );
+ if ( empty( $theme_class_arr ) ) {
+ return $wrapped_html;
+ }
+
+
+ /**
+ * Find the first wrapper container:
+ * <div class="... wpbc_container wpbc_form ...">
+ *
+ * BUG CHECK:
+ * - The pattern is "tempered" to find the class attribute containing both tokens.
+ * - It does not require order (wpbc_container can appear before/after wpbc_form).
+ * - It matches the opening <div ...> tag only.
+ */
+ $pattern = '/<divb[^>]*bclasss*=s*(["'])(?:(?!1).)*bwpbc_containerb(?:(?!1).)*bwpbc_formb(?:(?!1).)*1[^>]*>/i';
+
+ return preg_replace_callback( $pattern, function ( $m ) use ( $theme_class_arr ) {
+
+ $tag = $m[0];
+
+ if ( preg_match( '/bclasss*=s*(["'])(.*?)1/i', $tag, $cm ) ) {
+
+ $quote = $cm[1];
+ $classes = (string) $cm[2];
+
+ // Optional but recommended: remove any existing wpbc_theme_* to avoid conflicts.
+ $classes = preg_replace( '/bwpbc_theme_[A-Za-z0-9_-]+b/', '', $classes );
+ $classes = trim( preg_replace( '/s+/', ' ', $classes ) );
+
+ // Add missing theme tokens.
+ foreach ( $theme_class_arr as $tok ) {
+ if ( false === strpos( ' ' . $classes . ' ', ' ' . $tok . ' ' ) ) {
+ $classes = trim( $classes . ' ' . $tok );
+ }
+ }
+
+ $tag = preg_replace( '/bclasss*=s*(["'])(.*?)1/i', 'class=' . $quote . esc_attr( $classes ) . $quote, $tag, 1 );
+ }
+
+ return $tag;
+ }, $wrapped_html, 1 );
+
+}
+add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class', 10, 4 );
--- a/booking/includes/save-load-option/save-load-option.php
+++ b/booking/includes/save-load-option/save-load-option.php
@@ -35,7 +35,7 @@
* @package Booking Calendar
* @author wpdevelop
* @since 11.0.0
- * @version 1.0.0
+ * @version 1.0.1
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -44,39 +44,11 @@
class wpbc_option_saver_loader {
- /**
- * Save AJAX slug.
- *
- * @var string
- */
private static $ajax_action_save = 'wpbc_ajax_option_save';
-
- /**
- * Load AJAX slug.
- *
- * @var string
- */
private static $ajax_action_load = 'wpbc_ajax_option_load';
+ private static $option_prefix = '';
+ private static $asset_version = '1.0.1';
- /**
- * Option key prefix (keep empty for plain keys).
- *
- * @var string
- */
- private static $option_prefix = '';
-
- /**
- * Asset version.
- *
- * @var string
- */
- private static $asset_version = '1.0.0';
-
- /**
- * Bootstrap.
- *
- * @return void
- */
public static function init() {
add_action( 'init', array( __CLASS__, 'register_ajax_handlers' ) );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
@@ -148,8 +120,12 @@
}
$data_name = isset( $_POST['data_name'] ) ? sanitize_key( wp_unslash( $_POST['data_name'] ) ) : '';
- /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing */
+ /* phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing */
$data_raw = isset( $_POST['data_value'] ) ? wp_unslash( $_POST['data_value'] ) : '';
+ // Optional: split JSON object into multiple options.
+ $data_mode = isset( $_POST['data_mode'] ) ? sanitize_key( wp_unslash( $_POST['data_mode'] ) ) : '';
+ $data_fields = isset( $_POST['data_fields'] ) ? sanitize_text_field( wp_unslash( $_POST['data_fields'] ) ) : '';
+
$nonce_name = isset( $_POST['nonce_action'] ) ? sanitize_key( wp_unslash( $_POST['nonce_action'] ) ) : '';
$nonce_value = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
@@ -163,9 +139,75 @@
$value_to_store = self::normalize_incoming_value( $data_raw );
+ // Split mode: JSON object => multiple options saved separately.
+ if ( 'split' === $data_mode && is_array( $value_to_store ) ) {
+
+ $allowed_keys = array();
+ if ( '' !== trim( $data_fields ) ) {
+ $parts = explode( ',', (string) $data_fields );
+ foreach ( $parts as $p ) {
+ $k = sanitize_key( trim( (string) $p ) );
+ if ( '' !== $k ) {
+ $allowed_keys[ $k ] = true;
+ }
+ }
+ }
+
+ $saved = array();
+
+ foreach ( $value_to_store as $k => $v ) {
+
+ if ( ! is_scalar( $k ) ) {
+ continue;
+ }
+
+ $opt_key = sanitize_key( (string) $k );
+ if ( '' === $opt_key ) {
+ continue;
+ }
+
+ // If allowlist provided, only save those keys.
+ if ( ! empty( $allowed_keys ) && ! isset( $allowed_keys[ $opt_key ] ) ) {
+ continue;
+ }
+
+ // Values: allow scalar or arrays (already sanitized by normalize_incoming_value()).
+ $opt_val = $v;
+ if ( is_scalar( $opt_val ) ) {
+ $opt_val = sanitize_text_field( (string) $opt_val );
+ } elseif ( is_array( $opt_val ) ) {
+ $opt_val = self::sanitize_mixed_value( $opt_val );
+ } else {
+ $opt_val = '';
+ }
+
+ self::update_option( self::$option_prefix . $opt_key, $opt_val );
+ $saved[ $opt_key ] = $opt_val;
+ }
+
+ if ( empty( $saved ) ) {
+ wp_send_json_error( array( 'message' => __( 'Nothing to save.', 'booking' ) ) );
+ }
+
+ wp_send_json_success(
+ array(
+ 'message' => __( 'Settings saved.', 'booking' ),
+ 'value' => $saved,
+ 'mode' => 'split',
+ )
+ );
+ }
+
+ // Default: store as a single option (scalar/array).
self::update_option( self::$option_prefix . $data_name, $value_to_store );
- wp_send_json_success( array( 'message' => __( 'Settings saved.', 'booking' ) ) );
+ // Return stored value (useful for client callbacks / UI sync).
+ wp_send_json_success(
+ array(
+ 'message' => __( 'Settings saved.', 'booking' ),
+ 'value' => $value_to_store,
+ )
+ );
}
/**
@@ -183,7 +225,7 @@
wp_send_json_error( array( 'message' => __( 'You do not have permission to load settings.', 'booking' ) ) );
}
- /* phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing */
+ /* phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing */
$data_name = isset( $_GET['data_name'] ) ? sanitize_key( wp_unslash( $_GET['data_name'] ) ) : '';
if ( empty( $data_name ) ) {
wp_send_json_error( array( 'message' => __( 'Missing data name.', 'booking' ) ) );
@@ -202,6 +244,7 @@
* @return mixed
*/
private static function normalize_incoming_value( $data_raw ) {
+
if ( ! is_string( $data_raw ) || '' === $data_raw ) {
return '';
}
@@ -211,7 +254,7 @@
// JSON path.
if (
0 === strpos( $maybe_json, '{' ) || 0 === strpos( $maybe_json, '[' ) ||
- 'null' === $maybe_json || 'true' === strtolower( $maybe_json ) ||
+ 'null' === strtolower( $maybe_json ) || 'true' === strtolower( $maybe_json ) ||
'false' === strtolower( $maybe_json ) || is_numeric( $maybe_json )
) {
$decoded = json_decode( $maybe_json, true );
@@ -238,6 +281,7 @@
* @return mixed
*/
private static function sanitize_mixed_value( $value ) {
+
if ( is_array( $value ) ) {
$out = array();
foreach ( $value as $k => $v ) {
@@ -261,6 +305,7 @@
* @return array
*/
private static function sanitize_kv_array_preserve_brackets( $parsed_data ) {
+
$sanitized_data = array();
if ( empty( $parsed_data ) || ! is_array( $parsed_data ) ) {
--- a/booking/includes/ui_settings/parts/ui__nav_top.php
+++ b/booking/includes/ui_settings/parts/ui__nav_top.php
@@ -77,17 +77,17 @@
function wpbc_ui__top_nav__dropdown__wpbc() {
$svg_size = '22px';
- $svg_icon_style = '';// 'margin:0 5px 0 0;';//'background-position: 0 0;background-size: ' . $svg_size . ' ' . $svg_size . ';width: ' . $svg_size . ';height: ' . $svg_size . ';';
+ $svg_icon_style = 'margin:5px 5px 0 0;';//'background-position: 0 0;background-size: ' . $svg_size . ' ' . $svg_size . ';width: ' . $svg_size . ';height: ' . $svg_size . ';';
$svg_icon = wpbc_get_svg_logo_for_background( '#555', '#e5e5e5', '1.0' );
$el_arr = array(
// 'title' => 'Booking Calendar',
// 'font_icon' => 'wpbc-bi-calendar2-range',
- 'title_html' => '<span class="nav-tab-text" style="margin: -11px 0 0 5px;font-size: 16px;padding: 0;"><span style="position: absolute;font-size: 7px;margin-top: 13px;margin-left: 1px;">WP</span>Booking Calendar</span>',
+ 'title_html' => '<span class="nav-tab-text" style="margin: -3px 0 0 5px;font-size: 16px;padding: 0;"><span style="position: absolute;font-size: 7px;margin-top: 13px;margin-left: 1px;">WP</span>Booking Calendar</span>',
'svg_icon' => $svg_icon,
'svg_icon_style' => $svg_icon_style,
- 'style' => 'display: flex;flex-flow:row nowrap;align-items: center;justify-content: flex-start;',
- 'container_style' => 'padding: 0 5px 0 10px;',
+ 'style' => 'display: flex;flex-flow:row nowrap;align-items: center;justify-content: flex-start;' . ' ',
+ 'container_style' => 'padding: 0 15px 0 10px;',
'position' => 'left',
'has_down_arrow' => true,
'items' => array(
--- a/booking/includes/wpbc-include.php
+++ b/booking/includes/wpbc-include.php
@@ -262,6 +262,7 @@
require_once WPBC_PLUGIN_DIR . '/includes/fontend/class-fe-render.php';
require_once WPBC_PLUGIN_DIR . '/includes/fontend/class-fe-shortcodes.php';
require_once WPBC_PLUGIN_DIR . '/includes/fontend/class-fe-form-source-resolver.php';
+ require_once WPBC_PLUGIN_DIR . '/includes/fontend/hooks/class-fe-bfb-settings-hooks.php'; // Apply BFB settings to rendered booking form HTML.
require_once WPBC_PLUGIN_DIR . '/includes/fontend/class-fe-render-form-body.php';
}
require_once WPBC_PLUGIN_DIR . '/core/lib/wpdev-booking-class.php'; // C L A S S B o o k i n g.
--- a/booking/wpdev-booking.php
+++ b/booking/wpdev-booking.php
@@ -7,7 +7,7 @@
Author URI: https://wpbookingcalendar.com/
Text Domain: booking
Domain Path: /languages/
-Version: 10.14.13
+Version: 10.14.14
License: GPLv2 or later
*/
@@ -34,7 +34,7 @@
if ( ! defined( 'WP_BK_VERSION_NUM' ) ) {
- define( 'WP_BK_VERSION_NUM', '10.14.13' );
+ define( 'WP_BK_VERSION_NUM', '10.14.14' );
}
if ( ! defined( 'WP_BK_MINOR_UPDATE' ) ) {
define( 'WP_BK_MINOR_UPDATE', true );
@@ -105,7 +105,7 @@
if ( ! defined( 'WPBC_NEW_FORM_BUILDER' ) ) {
- define( 'WPBC_NEW_FORM_BUILDER', ! true );
+ define( 'WPBC_NEW_FORM_BUILDER', ! true );
}
// ---------------------------------------------------------------------------------------------------------------------