PluginProbe
Booking Calendar / 11.8.4
Booking Calendar v11.8.4
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
← All changes | includes/_front_end/hooks/class-fe-bfb-settings-hooks.php +517 -243 11.011.8.4 View file →
@@ -1,243 +1,517 @@
1 -<?php
2 -/**
3 - * BFB Settings -> Front-End Booking Form Post-Processing Hooks
4 - *
5 - * 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.
6 - *
7 - * These hooks are executed inside `WPBC_FE_Form_Body_Renderer::render()` in this order:
8 - *
9 - * 1) `wpbc_booking_form__body_html__before_postprocess` --> raw **form body HTML only** (no injected calendar/captcha/extra calendars yet).
10 - *
11 - * 2) `wpbc_booking_form__html__before_wrapper` --> composed **form HTML** where calendar/captcha/extra calendars already injected,
12 - *
13 - * 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
14 - *
15 - * 4) `wpbc_booking_form__wrapped_html__after_inline_scripts` --> the final HTML after inline scripts are appended.
16 - *
17 - * Notes:
18 - * - `$bfb_settings` is expected to be an array like:
19 - * [
20 - * 'options' => [ 'booking_form_theme' => 'wpbc_theme_dark_1', ... ]
21 - * ]
22 - *
23 - * @package Booking Calendar
24 - * @since 11.0.x
25 - * @file ../includes/_front_end/hooks/class-fe-bfb-settings-hooks.php
26 - */
27 -
28 -if ( ! defined( 'ABSPATH' ) ) {
29 - exit;
30 -}
31 -
32 -/**
33 - * ['options']['booking_form_layout_width'] - > Inject per-form CSS variables: into the BFB form root wrapper. On Step 1.
34 - *
35 - * Purpose:
36 - * - If the form body contains the BFB root element (class `wpbc_bfb_form`),
37 - * inject CSS Custom Properties (variables) into its `style` attribute.
38 - *
39 - * 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.
40 - *
41 - * Expected `$bfb_settings` format: - `$bfb_settings['css_vars']` is an associative array: `--var-name` => `value`.
42 - *
43 - * @since 11.0.x
44 - *
45 - * @param string $form_html Raw booking form body HTML (no calendar/captcha injected yet).
46 - * @param array $bfb_settings Decoded BFB settings array (may be empty).
47 - * @param int $resource_id Booking resource ID.
48 - * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
49 - *
50 - * @return string Filtered body HTML.
51 - */
52 -function wpbc_booking_form__body_html__before_postprocess__apply_bfb_vars( $form_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
53 -
54 - $form_html = (string) $form_html;
55 - $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
56 -
57 - // If body has no BFB root, do nothing (fast path).
58 - if ( false === strpos( $form_html, 'wpbc_bfb_form' ) ) {
59 - return $form_html;
60 - }
61 -
62 - // Local helper (no global function redeclare risk).
63 - $sanitize_css_length = function( $v ) {
64 - $v = trim( (string) $v );
65 - if ( '' === $v ) {
66 - return '';
67 - }
68 - // Allow only: number + unit.
69 - return preg_match( '/^-?\d+(?:\.\d+)?(?:%|px|rem|em|vw|vh)$/', $v ) ? $v : '';
70 - };
71 -
72 - $width = '';
73 - if ( ! empty( $bfb_settings['options'] ) && is_array( $bfb_settings['options'] ) ) {
74 - $width = isset( $bfb_settings['options']['booking_form_layout_width'] )
75 - ? $sanitize_css_length( $bfb_settings['options']['booking_form_layout_width'] )
76 - : '';
77 - }
78 -
79 - // If no valid width -> do nothing (keeps HTML unchanged).
80 - if ( '' === $width ) {
81 - return $form_html;
82 - }
83 -
84 - $css_vars = ( ! empty( $bfb_settings['css_vars'] ) && is_array( $bfb_settings['css_vars'] ) )
85 - ? $bfb_settings['css_vars']
86 - : array();
87 -
88 - // Use a dedicated CSS var name.
89 - $css_vars['--wpbc-bfb-booking_form_layout_width'] = $width;
90 -
91 - return WPBC_FE_Form_Style_Injector::inject_css_vars_into_bfb_root( $form_html, $css_vars );
92 -}
93 -add_filter( 'wpbc_booking_form__body_html__before_postprocess', 'wpbc_booking_form__body_html__before_postprocess__apply_bfb_vars', 10, 4 );
94 -
95 -
96 -/**
97 - * ['options'][booking_form_theme] -> Apply a "theme" CSS class to the main booking form wrapper container. On Step 3.
98 - *
99 - * Purpose:
100 - * - Add `$bfb_settings['options']['booking_form_theme']` as an extra class to the FIRST wrapper container:
101 - * `<div class="... wpbc_container wpbc_form ...">`
102 - *
103 - * Why here:
104 - * - The wrapper container does not exist until `WPBC_FE_Form_Wrapper::wrap()` runs, therefore this must be executed on the wrapped HTML stage.
105 - *
106 - * Potential limitations:
107 - * - This implementation supports a **single** class value. If you plan to support multiple
108 - * classes in one setting (space-separated), you should split and sanitize each class.
109 - *
110 - * @since 11.0.x
111 - *
112 - * @param string $wrapped_html Fully wrapped booking form HTML (before inline scripts appended).
113 - * @param array $bfb_settings Decoded BFB settings array (may be empty).
114 - * @param int $resource_id Booking resource ID.
115 - * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
116 - *
117 - * @return string Filtered wrapped HTML.
118 - */
119 -function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
120 -
121 - $wrapped_html = (string) $wrapped_html;
122 - $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
123 -
124 - if ( empty( $bfb_settings['options']['booking_form_theme'] ) ) {
125 - return $wrapped_html;
126 - }
127 -
128 - // Get theme class(es).
129 - $raw_theme = trim( (string) $bfb_settings['options']['booking_form_theme'] );
130 - $theme_class_arr = array(); // token => true .
131 - $parts = preg_split( '/\s+/', $raw_theme, - 1, PREG_SPLIT_NO_EMPTY );
132 - foreach ( $parts as $part ) {
133 - $tok = sanitize_html_class( (string) $part );
134 - if ( '' === $tok ) { continue; }
135 - $theme_class_arr[ $tok ] = true;
136 - }
137 - $theme_class_arr = array_keys( $theme_class_arr );
138 - if ( empty( $theme_class_arr ) ) {
139 - return $wrapped_html;
140 - }
141 -
142 -
143 - /**
144 - * Find the first wrapper container:
145 - * <div class="... wpbc_container wpbc_form ...">
146 - *
147 - * BUG CHECK:
148 - * - The pattern is "tempered" to find the class attribute containing both tokens.
149 - * - It does not require order (wpbc_container can appear before/after wpbc_form).
150 - * - It matches the opening <div ...> tag only.
151 - */
152 - $pattern = '/<div\b[^>]*\bclass\s*=\s*(["\'])(?:(?!\1).)*\bwpbc_container\b(?:(?!\1).)*\bwpbc_form\b(?:(?!\1).)*\1[^>]*>/i';
153 -
154 - return preg_replace_callback( $pattern, function ( $m ) use ( $theme_class_arr ) {
155 -
156 - $tag = $m[0];
157 -
158 - if ( preg_match( '/\bclass\s*=\s*(["\'])(.*?)\1/i', $tag, $cm ) ) {
159 -
160 - $quote = $cm[1];
161 - $classes = (string) $cm[2];
162 -
163 - // Optional but recommended: remove any existing wpbc_theme_* to avoid conflicts.
164 - $classes = preg_replace( '/\bwpbc_theme_[A-Za-z0-9_-]+\b/', '', $classes );
165 - $classes = trim( preg_replace( '/\s+/', ' ', $classes ) );
166 -
167 - // Add missing theme tokens.
168 - foreach ( $theme_class_arr as $tok ) {
169 - if ( false === strpos( ' ' . $classes . ' ', ' ' . $tok . ' ' ) ) {
170 - $classes = trim( $classes . ' ' . $tok );
171 - }
172 - }
173 -
174 - $tag = preg_replace( '/\bclass\s*=\s*(["\'])(.*?)\1/i', 'class=' . $quote . esc_attr( $classes ) . $quote, $tag, 1 );
175 - }
176 -
177 - return $tag;
178 - }, $wrapped_html, 1 );
179 -
180 -}
181 -add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class', 10, 4 );
182 -
183 -
184 -/**
185 - * ['options']['booking_type_of_day_selections'] -> Set per-form day selection mode for calendar init.
186 - *
187 - * Uses legacy JS global: bk_days_selection_mode = 'single'|'multiple'
188 - *
189 - * @param string $wrapped_html
190 - * @param array $bfb_settings
191 - * @param int $resource_id
192 - * @param string $custom_booking_form_name
193 - *
194 - * @return string
195 - */
196 -function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_day_selection_mode( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
197 -
198 - $wrapped_html = (string) $wrapped_html;
199 - $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
200 -
201 - $mode = '';
202 - if ( ! empty( $bfb_settings['options'] ) && is_array( $bfb_settings['options'] ) ) {
203 - if ( isset( $bfb_settings['options']['booking_type_of_day_selections'] ) ) {
204 - $mode = (string) $bfb_settings['options']['booking_type_of_day_selections'];
205 - }
206 - }
207 -
208 - if ( ( 'single' !== $mode ) && ( 'multiple' !== $mode ) ) {
209 - return $wrapped_html;
210 - }
211 -
212 - $rid = (int) $resource_id;
213 - $fn = ( 'single' === $mode ) ? 'wpbc_cal_ready_days_select__single' : 'wpbc_cal_ready_days_select__multiple';
214 -
215 - $js_body = '(function(){';
216 - $js_body .= 'var rid=' . $rid . ';';
217 - $js_body .= 'var tries=0;';
218 - $js_body .= 'function run(){';
219 - $js_body .= ' if (typeof window.' . $fn . ' === "function") { window.' . $fn . '(rid); return; }';
220 - $js_body .= ' if (tries++ < 40) { setTimeout(run,50); }';
221 - $js_body .= '}';
222 - $js_body .= 'run();';
223 - $js_body .= '}());';
224 -
225 - $can_use_wp_inline = ( ! wp_doing_ajax() );
226 -
227 - // Elementor safe adding inline scripts after loaded of 'wpbc_all' script.
228 - if ( $can_use_wp_inline && class_exists( 'WPBC_FE_Assets' ) && function_exists( 'wp_script_is' ) ) {
229 -
230 - $assets_key = 'wpbc:day-select:' . $rid . ':' . md5( (string) $custom_booking_form_name . ':' . $mode );
231 -
232 - $added = WPBC_FE_Assets::add_jq_ready_js_to_wp_script( 'wpbc_all', $js_body, $assets_key );
233 -
234 - if ( $added ) {
235 - return $wrapped_html; // no <script> printed in content.
236 - }
237 - }
238 -
239 - // Fallback only if WP inline could not be attached.
240 - $wrapped_html .= "\n" . '<script type="text/javascript">' . "\n" . $js_body . "\n" . '</script>' . "\n";
241 - return $wrapped_html;
242 -}
243 -add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_day_selection_mode', 9, 4 );
1 +<?php
2 +/**
3 + * BFB Settings -> Front-End Booking Form Post-Processing Hooks
4 + *
5 + * 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.
6 + *
7 + * These hooks are executed inside `WPBC_FE_Form_Body_Renderer::render()` in this order:
8 + *
9 + * 1) `wpbc_booking_form__body_html__before_postprocess` --> raw **form body HTML only** (no injected calendar/captcha/extra calendars yet).
10 + *
11 + * 2) `wpbc_booking_form__html__before_wrapper` --> composed **form HTML** where calendar/captcha/extra calendars already injected,
12 + *
13 + * 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
14 + *
15 + * 4) `wpbc_booking_form__wrapped_html__after_inline_scripts` --> the final HTML after inline scripts are appended.
16 + *
17 + * Notes:
18 + * - `$bfb_settings` is expected to be an array like:
19 + * [
20 + * 'options' => [ 'booking_form_theme' => 'wpbc_theme_dark_1', ... ]
21 + * ]
22 + *
23 + * @package Booking Calendar
24 + * @since 11.0.x
25 + * @file ../includes/_front_end/hooks/class-fe-bfb-settings-hooks.php
26 + */
27 +
28 +if ( ! defined( 'ABSPATH' ) ) {
29 + exit;
30 +}
31 +
32 +/**
33 + * ['options']['booking_form_layout_width'] and appearance options -> inject per-form CSS variables into the BFB form root wrapper. On Step 1.
34 + *
35 + * Purpose:
36 + * - If the form body contains the BFB root element (class `wpbc_bfb_form`),
37 + * inject CSS Custom Properties (variables) into its `style` attribute.
38 + *
39 + * 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.
40 + *
41 + * Expected `$bfb_settings` format: - `$bfb_settings['css_vars']` is an associative array: `--var-name` => `value`.
42 + *
43 + * @since 11.0.x
44 + *
45 + * @param string $form_html Raw booking form body HTML (no calendar/captcha injected yet).
46 + * @param array $bfb_settings Decoded BFB settings array (may be empty).
47 + * @param int $resource_id Booking resource ID.
48 + * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
49 + *
50 + * @return string Filtered body HTML.
51 + */
52 +function wpbc_booking_form__body_html__before_postprocess__apply_bfb_vars( $form_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
53 +
54 + $form_html = (string) $form_html;
55 + $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
56 +
57 + // If body has no BFB root, do nothing (fast path).
58 + if ( false === strpos( $form_html, 'wpbc_bfb_form' ) ) {
59 + return $form_html;
60 + }
61 +
62 + // Local helper (no global function redeclare risk).
63 + $sanitize_css_length = function( $v ) {
64 + $v = trim( (string) $v );
65 + if ( '' === $v ) {
66 + return '';
67 + }
68 + // Allow only: number + unit.
69 + return preg_match( '/^\d+(?:\.\d+)?(?:%|px|rem|em|vw|vh)$/', $v ) ? $v : '';
70 + };
71 +
72 + $width = '';
73 + $appearance_style = '';
74 + $appearance_values = array();
75 + if ( ! empty( $bfb_settings['options'] ) && is_array( $bfb_settings['options'] ) ) {
76 + $options = $bfb_settings['options'];
77 +
78 + $width = isset( $options['booking_form_layout_width'] )
79 + ? $sanitize_css_length( $options['booking_form_layout_width'] )
80 + : '';
81 +
82 + $appearance_style = isset( $options['booking_form_container_style'] ) ? (string) $options['booking_form_container_style'] : '';
83 + $appearance_values = $options;
84 + }
85 +
86 + $css_vars = ( ! empty( $bfb_settings['css_vars'] ) && is_array( $bfb_settings['css_vars'] ) )
87 + ? $bfb_settings['css_vars']
88 + : array();
89 +
90 + // Use a dedicated CSS var name.
91 + if ( '' !== $width ) {
92 + $css_vars['--wpbc-bfb-booking_form_layout_width'] = $width;
93 + }
94 +
95 + $appearance_vars = wpbc_bfb_settings__get_form_style_css_vars();
96 + if ( ! empty( $appearance_vars ) ) {
97 + $css_vars = array_merge( $css_vars, $appearance_vars );
98 + }
99 +
100 + if ( empty( $css_vars ) ) {
101 + return $form_html;
102 + }
103 +
104 + $form_html = WPBC_FE_Form_Style_Injector::inject_css_vars_into_bfb_root( $form_html, $css_vars );
105 +
106 + if ( wpbc_bfb_settings__is_custom_form_style( wpbc_bfb_settings__get_current_form_style() ) ) {
107 + $form_html = WPBC_FE_Form_Style_Injector::add_class_to_first_tag_with_classes( $form_html, array( 'wpbc_bfb_form' ), 'wpbc_bfb_form_appearance_custom' );
108 + }
109 +
110 + return $form_html;
111 +}
112 +add_filter( 'wpbc_booking_form__body_html__before_postprocess', 'wpbc_booking_form__body_html__before_postprocess__apply_bfb_vars', 10, 4 );
113 +
114 +/**
115 + * Resolve form appearance settings into CSS variables.
116 + *
117 + * @param string $style bordered|none|soft|custom.
118 + * @param array $options Form settings options.
119 + *
120 + * @return array
121 + */
122 +function wpbc_bfb_settings__get_form_appearance_presets() {
123 +
124 + return array(
125 + 'bordered' => array(
126 + 'background' => '#ffffff',
127 + 'border_color' => '#cccccc',
128 + 'border_width' => '1px',
129 + 'radius' => '2px',
130 + 'padding' => '10px 30px',
131 + 'shadow' => 'rgba(0, 0, 0, 0.05) 0px 2px 6px 0px',
132 + ),
133 + 'none' => array(
134 + 'background' => 'transparent',
135 + 'border_color' => 'transparent',
136 + 'border_width' => '0px',
137 + 'radius' => '0px',
138 + 'padding' => '0px',
139 + 'shadow' => 'none',
140 + ),
141 + 'soft' => array(
142 + 'background' => '#f9f9fa',
143 + 'border_color' => '#fff',
144 + 'border_width' => '3px',
145 + 'radius' => '8px',
146 + 'padding' => '20px',
147 + 'shadow' => 'rgba(15, 23, 42, 0.06) 0px 4px 16px 0px',
148 + ),
149 + );
150 +}
151 +
152 +function wpbc_bfb_settings__is_dark_form_theme( $options ) {
153 +
154 + $options = is_array( $options ) ? $options : array();
155 +
156 + return ( isset( $options['booking_form_theme'] ) && 'wpbc_theme_dark_1' === (string) $options['booking_form_theme'] );
157 +}
158 +
159 +function wpbc_bfb_settings__get_form_appearance_preset_for_options( $style, $options ) {
160 +
161 + $presets = wpbc_bfb_settings__get_form_appearance_presets();
162 + $style = (string) $style;
163 +
164 + if ( ! wpbc_bfb_settings__is_dark_form_theme( $options ) ) {
165 + return isset( $presets[ $style ] ) ? $presets[ $style ] : $presets['bordered'];
166 + }
167 +
168 + if ( 'soft' === $style ) {
169 + return array(
170 + 'background' => '#1f2937',
171 + 'border_color' => '#334155',
172 + 'border_width' => '3px',
173 + 'radius' => '8px',
174 + 'padding' => '20px',
175 + 'shadow' => 'rgba(0, 0, 0, 0.24) 0px 4px 16px 0px',
176 + );
177 + }
178 +
179 + return isset( $presets[ $style ] ) ? $presets[ $style ] : $presets['bordered'];
180 +}
181 +
182 +function wpbc_bfb_settings__sanitize_form_appearance_values( $values ) {
183 +
184 + $values = is_array( $values ) ? $values : array();
185 +
186 + $sanitize_color = function ( $value, $fallback ) {
187 + $value = trim( (string) $value );
188 + if ( preg_match( '/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i', $value ) || 'transparent' === $value ) {
189 + return $value;
190 + }
191 + return $fallback;
192 + };
193 +
194 + $sanitize_length = function ( $value, $fallback ) {
195 + $value = trim( (string) $value );
196 + if ( preg_match( '/^\d+(?:\.\d+)?(?:px|rem|em|%)$/i', $value ) ) {
197 + return $value;
198 + }
199 + return $fallback;
200 + };
201 +
202 + $sanitize_spacing = function ( $value, $fallback ) {
203 + $value = preg_replace( '/\s+/', ' ', trim( (string) $value ) );
204 + if ( '' === $value ) {
205 + return $fallback;
206 + }
207 + $parts = explode( ' ', $value );
208 + if ( count( $parts ) < 1 || count( $parts ) > 4 ) {
209 + return $fallback;
210 + }
211 + foreach ( $parts as $part ) {
212 + if ( ! preg_match( '/^\d+(?:\.\d+)?(?:px|rem|em|%)$/i', $part ) ) {
213 + return $fallback;
214 + }
215 + }
216 + return implode( ' ', $parts );
217 + };
218 +
219 + return array(
220 + 'background' => $sanitize_color( isset( $values['background'] ) ? $values['background'] : '', '#ffffff' ),
221 + 'border_color' => $sanitize_color( isset( $values['border_color'] ) ? $values['border_color'] : '', '#cccccc' ),
222 + 'border_width' => $sanitize_length( isset( $values['border_width'] ) ? $values['border_width'] : '', '1px' ),
223 + 'radius' => $sanitize_length( isset( $values['radius'] ) ? $values['radius'] : '', '2px' ),
224 + 'padding' => $sanitize_spacing( isset( $values['padding'] ) ? $values['padding'] : '', '10px 30px' ),
225 + 'shadow' => isset( $values['shadow'] ) ? (string) $values['shadow'] : 'rgba(0, 0, 0, 0.05) 0px 2px 6px 0px',
226 + );
227 +}
228 +
229 +function wpbc_bfb_settings__sanitize_form_design_color_values( $values ) {
230 +
231 + $values = is_array( $values ) ? $values : array();
232 +
233 + $sanitize_color = function ( $value ) {
234 + $value = trim( (string) $value );
235 + if ( '' === $value ) {
236 + return '';
237 + }
238 + if ( preg_match( '/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i', $value ) ) {
239 + return $value;
240 + }
241 + return '';
242 + };
243 +
244 + return array(
245 + 'text_color' => $sanitize_color( isset( $values['booking_form_text_color'] ) ? $values['booking_form_text_color'] : '' ),
246 + 'field_background_color' => $sanitize_color( isset( $values['booking_form_field_background_color'] ) ? $values['booking_form_field_background_color'] : '' ),
247 + 'field_text_color' => $sanitize_color( isset( $values['booking_form_field_text_color'] ) ? $values['booking_form_field_text_color'] : '' ),
248 + 'field_border_color' => $sanitize_color( isset( $values['booking_form_field_border_color'] ) ? $values['booking_form_field_border_color'] : '' ),
249 + );
250 +}
251 +
252 +function wpbc_bfb_settings__is_custom_form_appearance( $style, $options ) {
253 +
254 + $style = (string) $style;
255 + $options = is_array( $options ) ? $options : array();
256 +
257 + if ( '' === $style && isset( $options['booking_form_container_style'] ) ) {
258 + $style = (string) $options['booking_form_container_style'];
259 + }
260 +
261 + return ( 'custom' === $style );
262 +}
263 +
264 +function wpbc_bfb_settings__get_global_form_appearance_options() {
265 +
266 + $preset = get_bk_option( 'booking_form_appearance_preset' );
267 + $preset = in_array( $preset, array( 'bordered', 'none', 'soft', 'custom' ), true ) ? $preset : 'bordered';
268 + if ( 'custom' === $preset ) {
269 + $preset = 'bordered';
270 + }
271 +
272 + return array(
273 + 'booking_form_theme' => get_bk_option( 'booking_form_theme' ),
274 + 'booking_form_container_style' => $preset,
275 + 'booking_form_background_color' => get_bk_option( 'booking_form_appearance_background_color' ),
276 + 'booking_form_border_color' => get_bk_option( 'booking_form_appearance_border_color' ),
277 + 'booking_form_border_width' => get_bk_option( 'booking_form_appearance_border_width' ),
278 + 'booking_form_border_radius' => get_bk_option( 'booking_form_appearance_border_radius' ),
279 + 'booking_form_padding' => get_bk_option( 'booking_form_appearance_padding' ),
280 + 'booking_form_text_color' => '',
281 + 'booking_form_field_background_color' => '',
282 + 'booking_form_field_text_color' => '',
283 + 'booking_form_field_border_color' => '',
284 + );
285 +}
286 +
287 +function wpbc_bfb_settings__get_form_appearance_css_vars( $style, $options ) {
288 +
289 + $style = (string) $style;
290 + $options = is_array( $options ) ? $options : array();
291 + $design_options = $options;
292 + $css_vars = array();
293 +
294 + $presets = wpbc_bfb_settings__get_form_appearance_presets();
295 +
296 + if ( '' === $style || 'inherit' === $style ) {
297 + $options = wpbc_bfb_settings__get_global_form_appearance_options();
298 + $style = isset( $options['booking_form_container_style'] ) ? (string) $options['booking_form_container_style'] : 'bordered';
299 + }
300 +
301 + if ( 'bordered' !== $style ) {
302 + if ( 'custom' === $style ) {
303 + $resolved = wpbc_bfb_settings__sanitize_form_appearance_values(
304 + array(
305 + 'background' => isset( $options['booking_form_background_color'] ) ? $options['booking_form_background_color'] : '',
306 + 'border_color' => isset( $options['booking_form_border_color'] ) ? $options['booking_form_border_color'] : '',
307 + 'border_width' => isset( $options['booking_form_border_width'] ) ? $options['booking_form_border_width'] : '',
308 + 'radius' => isset( $options['booking_form_border_radius'] ) ? $options['booking_form_border_radius'] : '',
309 + 'padding' => isset( $options['booking_form_padding'] ) ? $options['booking_form_padding'] : '',
310 + 'shadow' => 'rgba(0, 0, 0, 0.05) 0px 2px 6px 0px',
311 + )
312 + );
313 + } elseif ( isset( $presets[ $style ] ) ) {
314 + $resolved = wpbc_bfb_settings__get_form_appearance_preset_for_options( $style, $options );
315 + } else {
316 + $resolved = array();
317 + }
318 +
319 + if ( ! empty( $resolved ) ) {
320 + $css_vars['--wpbc-bfb-form-background'] = $resolved['background'];
321 + $css_vars['--wpbc-bfb-form-border-color'] = $resolved['border_color'];
322 + $css_vars['--wpbc-bfb-form-border-width'] = $resolved['border_width'];
323 + $css_vars['--wpbc-bfb-form-border-radius'] = $resolved['radius'];
324 + $css_vars['--wpbc-bfb-form-padding'] = $resolved['padding'];
325 + $css_vars['--wpbc-bfb-form-box-shadow'] = $resolved['shadow'];
326 + }
327 + }
328 +
329 + if ( ! wpbc_bfb_settings__is_custom_form_appearance( $style, $design_options ) && wpbc_bfb_settings__is_dark_form_theme( $options ) && 'none' === $style ) {
330 + $css_vars['--wpbc_form-label-color'] = '#1d2327';
331 + $css_vars['--wpbc_form-label-sublabel-color'] = '#1d2327';
332 + }
333 +
334 + if ( wpbc_bfb_settings__is_custom_form_appearance( $style, $design_options ) ) {
335 + $design_values = wpbc_bfb_settings__sanitize_form_design_color_values( $design_options );
336 +
337 + if ( '' !== $design_values['text_color'] ) {
338 + $css_vars['--wpbc_form-label-color'] = $design_values['text_color'];
339 + $css_vars['--wpbc_form-label-sublabel-color'] = $design_values['text_color'];
340 + }
341 + if ( '' !== $design_values['field_background_color'] ) {
342 + $css_vars['--wpbc_form-field-background-color'] = $design_values['field_background_color'];
343 + $css_vars['--wpbc_form-field-menu-color'] = $design_values['field_background_color'];
344 + }
345 + if ( '' !== $design_values['field_text_color'] ) {
346 + $css_vars['--wpbc_form-field-text-color'] = $design_values['field_text_color'];
347 + }
348 + if ( '' !== $design_values['field_border_color'] ) {
349 + $css_vars['--wpbc_form-field-border-color'] = $design_values['field_border_color'];
350 + $css_vars['--wpbc_form-field-border-color-spare'] = $design_values['field_border_color'];
351 + }
352 + }
353 +
354 + return $css_vars;
355 +}
356 +
357 +function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_custom_appearance_class( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
358 +
359 + $wrapped_html = (string) $wrapped_html;
360 + $style = wpbc_bfb_settings__get_current_form_style();
361 + $css_vars = wpbc_bfb_settings__get_form_style_css_vars( $style );
362 +
363 + if ( ! empty( $css_vars ) ) {
364 + $wrapped_html = WPBC_FE_Form_Style_Injector::inject_css_vars_into_form_wrapper( $wrapped_html, $css_vars );
365 + }
366 +
367 + if ( wpbc_bfb_settings__is_custom_form_style( $style ) ) {
368 + $wrapped_html = WPBC_FE_Form_Style_Injector::add_class_to_first_tag_with_classes( $wrapped_html, array( 'wpbc_container', 'wpbc_form' ), 'wpbc_bfb_form_appearance_custom' );
369 + }
370 +
371 + return $wrapped_html;
372 +}
373 +add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_custom_appearance_class', 12, 4 );
374 +
375 +
376 +/**
377 + * ['options'][booking_form_theme] -> Apply a "theme" CSS class to the main booking form wrapper container. On Step 3.
378 + *
379 + * Purpose:
380 + * - Add `$bfb_settings['options']['booking_form_theme']` as an extra class to the FIRST wrapper container:
381 + * `<div class="... wpbc_container wpbc_form ...">`
382 + *
383 + * Why here:
384 + * - The wrapper container does not exist until `WPBC_FE_Form_Wrapper::wrap()` runs, therefore this must be executed on the wrapped HTML stage.
385 + *
386 + * Potential limitations:
387 + * - This implementation supports a **single** class value. If you plan to support multiple
388 + * classes in one setting (space-separated), you should split and sanitize each class.
389 + *
390 + * @since 11.0.x
391 + *
392 + * @param string $wrapped_html Fully wrapped booking form HTML (before inline scripts appended).
393 + * @param array $bfb_settings Decoded BFB settings array (may be empty).
394 + * @param int $resource_id Booking resource ID.
395 + * @param string $custom_booking_form_name Booking form slug/name (legacy: "standard").
396 + *
397 + * @return string Filtered wrapped HTML.
398 + */
399 +function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
400 +
401 + $wrapped_html = (string) $wrapped_html;
402 + $preset = wpbc_bfb_settings__get_form_style_preset( wpbc_bfb_settings__get_current_form_style() );
403 + $raw_theme = isset( $preset['theme_class'] ) ? trim( (string) $preset['theme_class'] ) : '';
404 + $theme_class_arr = array(); // token => true .
405 + $parts = preg_split( '/\s+/', $raw_theme, - 1, PREG_SPLIT_NO_EMPTY );
406 + foreach ( $parts as $part ) {
407 + $tok = sanitize_html_class( (string) $part );
408 + if ( '' === $tok ) { continue; }
409 + $theme_class_arr[ $tok ] = true;
410 + }
411 + $theme_class_arr = array_keys( $theme_class_arr );
412 + /**
413 + * Find the first wrapper container:
414 + * <div class="... wpbc_container wpbc_form ...">
415 + *
416 + * BUG CHECK:
417 + * - The pattern is "tempered" to find the class attribute containing both tokens.
418 + * - It does not require order (wpbc_container can appear before/after wpbc_form).
419 + * - It matches the opening <div ...> tag only.
420 + */
421 + $pattern = '/<div\b[^>]*\bclass\s*=\s*(["\'])(?:(?!\1).)*\bwpbc_container\b(?:(?!\1).)*\bwpbc_form\b(?:(?!\1).)*\1[^>]*>/i';
422 +
423 + return preg_replace_callback( $pattern, function ( $m ) use ( $theme_class_arr ) {
424 +
425 + $tag = $m[0];
426 +
427 + if ( preg_match( '/\bclass\s*=\s*(["\'])(.*?)\1/i', $tag, $cm ) ) {
428 +
429 + $quote = $cm[1];
430 + $classes = (string) $cm[2];
431 +
432 + // Optional but recommended: remove any existing wpbc_theme_* to avoid conflicts.
433 + $classes = preg_replace( '/\bwpbc_theme_[A-Za-z0-9_-]+\b/', '', $classes );
434 + $classes = trim( preg_replace( '/\s+/', ' ', $classes ) );
435 +
436 + // Add missing theme tokens.
437 + foreach ( $theme_class_arr as $tok ) {
438 + if ( false === strpos( ' ' . $classes . ' ', ' ' . $tok . ' ' ) ) {
439 + $classes = trim( $classes . ' ' . $tok );
440 + }
441 + }
442 +
443 + $tag = preg_replace( '/\bclass\s*=\s*(["\'])(.*?)\1/i', 'class=' . $quote . esc_attr( $classes ) . $quote, $tag, 1 );
444 + }
445 +
446 + return $tag;
447 + }, $wrapped_html, 1 );
448 +
449 +}
450 +add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_theme_class', 10, 4 );
451 +
452 +
453 +/**
454 + * ['options']['booking_type_of_day_selections'] -> Set per-form day selection mode for calendar init.
455 + *
456 + * Uses shared calendar mode helpers for: single | multiple | range.
457 + *
458 + * @param string $wrapped_html
459 + * @param array $bfb_settings
460 + * @param int $resource_id
461 + * @param string $custom_booking_form_name
462 + *
463 + * @return string
464 + */
465 +function wpbc_booking_form__wrapped_html__before_inline_scripts__apply_day_selection_mode( $wrapped_html, $bfb_settings, $resource_id, $custom_booking_form_name ) {
466 +
467 + $wrapped_html = (string) $wrapped_html;
468 + $bfb_settings = is_array( $bfb_settings ) ? $bfb_settings : array();
469 +
470 + $mode = '';
471 + if ( ! empty( $bfb_settings['options'] ) && is_array( $bfb_settings['options'] ) ) {
472 + if ( isset( $bfb_settings['options']['booking_type_of_day_selections'] ) ) {
473 + $mode = (string) $bfb_settings['options']['booking_type_of_day_selections'];
474 + }
475 + }
476 +
477 + if ( ! in_array( $mode, array( 'single', 'multiple', 'range' ), true ) ) {
478 + return $wrapped_html;
479 + }
480 +
481 + $rid = (int) $resource_id;
482 + $mode_functions = array(
483 + 'single' => 'wpbc_cal_ready_days_select__single',
484 + 'multiple' => 'wpbc_cal_ready_days_select__multiple',
485 + 'range' => 'wpbc_cal_ready_days_select__range_mode',
486 + );
487 + $fn = $mode_functions[ $mode ];
488 +
489 + $js_body = '(function(){';
490 + $js_body .= 'var rid=' . $rid . ';';
491 + $js_body .= 'var tries=0;';
492 + $js_body .= 'function run(){';
493 + $js_body .= ' if (typeof window.' . $fn . ' === "function") { window.' . $fn . '(rid); return; }';
494 + $js_body .= ' if (tries++ < 40) { setTimeout(run,50); }';
495 + $js_body .= '}';
496 + $js_body .= 'run();';
497 + $js_body .= '}());';
498 +
499 + $can_use_wp_inline = ( ! wp_doing_ajax() );
500 +
501 + // Elementor safe adding inline scripts after loaded of 'wpbc_all' script.
502 + if ( $can_use_wp_inline && class_exists( 'WPBC_FE_Assets' ) && function_exists( 'wp_script_is' ) ) {
503 +
504 + $assets_key = 'wpbc:day-select:' . $rid . ':' . md5( (string) $custom_booking_form_name . ':' . $mode );
505 +
506 + $added = WPBC_FE_Assets::add_jq_ready_js_to_wp_script( 'wpbc_all', $js_body, $assets_key );
507 +
508 + if ( $added ) {
509 + return $wrapped_html; // no <script> printed in content.
510 + }
511 + }
512 +
513 + // Fallback only if WP inline could not be attached.
514 + $wrapped_html .= "\n" . '<script type="text/javascript">' . "\n" . $js_body . "\n" . '</script>' . "\n";
515 + return $wrapped_html;
516 +}
517 +add_filter( 'wpbc_booking_form__wrapped_html__before_inline_scripts', 'wpbc_booking_form__wrapped_html__before_inline_scripts__apply_day_selection_mode', 9, 4 );