| 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 ); |
| 244 |
|