PluginProbe
Booking Calendar / 11.2.1
Booking Calendar v11.2.1
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 10.11.2 All 203 releases
booking / includes / _front_end / class-fe-form-source-resolver.php

class-fe-form-source-resolver.php in Booking Calendar 11.2.1, at includes/_front_end/class-fe-form-source-resolver.php

556 lines 18.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Front-End Booking Form Source Resolver
4 *
5 * Decides which engine to use to build the booking form body:
6 * - bfb_db : load exported shortcodes from booking_form_structures (only when BFB enabled)
7 * - legacy : use legacy wpdev_bk_personal->get_booking_form()
8 * - simple : fallback wpbc_simple_form__get_booking_form__as_html()
9 *
10 * Keeps legacy behavior by default:
11 * - If BFB is disabled => never touches DB and returns legacy/simple only.
12 *
13 * @package Booking Calendar
14 * @since 11.0.x
15 * @file ../includes/_front_end/class-fe-form-source-resolver.php
16 */
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 require_once WPBC_PLUGIN_DIR . '/includes/_front_end/class-fe-settings.php';
23
24 /**
25 * MultiUser ownership helper (core-safe; MU add-on hooks via filters).
26 *
27 * @since 11.0.x
28 */
29 class WPBC_FE_MU {
30
31
32 public static function get_owner_user_id_for_resource( $resource_id ) {
33
34 $resource_id = (int) $resource_id;
35
36 $user_id = apply_bk_filter( 'get_user_of_this_bk_resource', false, $resource_id );
37 if ( $user_id ) {
38 return $user_id;
39 }
40
41 return 0;
42 }
43
44
45 public static function is_user_super_booking_admin( $user_id ) {
46
47 $user_id = intval( $user_id );
48
49 if ( ( class_exists( 'wpdev_bk_multiuser' ) ) && ( $user_id > 0 ) ) {
50 $is_user_super_admin = apply_bk_filter( 'is_user_super_admin', $user_id );
51 if ( ! $is_user_super_admin ) {
52 return false;
53 }
54 }
55
56 return true;
57 }
58
59 public static function get_current_user_id() {
60 return wpbc_get_current_user_id();
61 }
62 }
63
64
65 /**
66 * Resolver: decides BFB vs legacy vs simple and returns normalized loader args.
67 *
68 * @since 11.0.x
69 */
70 class WPBC_FE_Form_Source_Resolver {
71
72 /**
73 * Resolve form source.
74 *
75 * Input keys:
76 * - resource_id (int)
77 * - form_slug (string) // from shortcode form_type
78 * - form_status (string) // published|preview
79 * - custom_params (array) // parsed from options parser
80 * - legacy_instance (wpdev_booking|null) // optional
81 *
82 * Output:
83 * - engine: 'bfb_db'|'legacy'|'simple'
84 * - apply_after_load_filter: bool
85 * - bfb_loader_args: array
86 * - fallback_chain: array
87 *
88 * @param array $req
89 *
90 * @return array
91 */
92 public static function resolve( $req ) {
93
94 $req = is_array( $req ) ? $req : array();
95
96 $resource_id = isset( $req['resource_id'] ) ? (int) $req['resource_id'] : 0;
97 $form_slug_raw = isset( $req['form_slug'] ) ? (string) $req['form_slug'] : '';
98 $form_status_raw = isset( $req['form_status'] ) ? (string) $req['form_status'] : '';
99 $custom_params = ( isset( $req['custom_params'] ) && is_array( $req['custom_params'] ) ) ? $req['custom_params'] : array();
100
101 $legacy_instance = isset( $req['legacy_instance'] ) ? $req['legacy_instance'] : null;
102
103 // ---------------------------------------------------------------------
104 // Step A: determine slug + status (contract).
105 // ---------------------------------------------------------------------
106 $form_slug = sanitize_text_field( $form_slug_raw );
107 if ( '' === $form_slug ) {
108 $form_slug = 'standard';
109 }
110
111 $status = sanitize_key( $form_status_raw );
112
113 // Normalize synonyms.
114 if ( in_array( $status, array( 'publish', 'published' ), true ) ) {
115 $status = 'published';
116 }
117 if ( 'preview' !== $status ) {
118 $status = 'published';
119 }
120
121 // ---------------------------------------------------------------------
122 // If BFB is NOT enabled => legacy only (no DB touches).
123 // ---------------------------------------------------------------------
124 if ( ! class_exists( 'WPBC_Frontend_Settings' ) || ! WPBC_Frontend_Settings::is_bfb_enabled( null ) ) {
125 return self::fallback_to_legacy_or_simple( $legacy_instance );
126 }
127
128 // If BFB runtime is not present => legacy only.
129 if ( ! class_exists( 'WPBC_BFB_Form_Loader' ) ) {
130 return self::fallback_to_legacy_or_simple( $legacy_instance );
131 }
132
133 // If storage is not available => legacy only.
134 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) || ! method_exists( 'WPBC_BFB_Form_Storage', 'get_form_row_by_key' ) ) {
135 return self::fallback_to_legacy_or_simple( $legacy_instance );
136 }
137
138 // Optional: if table does not exist, skip DB.
139 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
140 return self::fallback_to_legacy_or_simple( $legacy_instance );
141 }
142
143 // ---------------------------------------------------------------------
144 // Step B: determine owner_user_id (MultiUser, filterable).
145 // ---------------------------------------------------------------------
146 $owner_user_id = 0;
147
148 // Caller may explicitly pass owner_user_id for future scopes.
149 if ( isset( $req['owner_user_id'] ) ) {
150 $owner_user_id = max( 0, (int) $req['owner_user_id'] );
151 } else {
152 $owner_user_id = WPBC_FE_MU::get_owner_user_id_for_resource( $resource_id );
153 $is_super_user = WPBC_FE_MU::is_user_super_booking_admin( $owner_user_id );
154
155 // If owner_user_id is is_super_user booking admin => treat as global (0).
156 if ( $is_super_user ) {
157 $owner_user_id = 0;
158 } else {
159 $owner_user_id = max( 0, (int) $owner_user_id );
160
161 if ( $owner_user_id > 0 ) {
162 // If this is "Regular user"? Check if we can load "custom booking form" ?
163 if ( ( get_bk_option( 'booking_is_custom_forms_for_regular_users' ) !== 'On' ) && ( 'standard' !== $form_slug ) ) {
164 $form_slug = 'standard';
165 }
166 }
167 }
168 }
169
170
171 // ---------------------------------------------------------------------
172 // Step C: query order (owner_user_id -> global -> standard fallback).
173 // Resolver decides the final key to ask the loader for.
174 // ---------------------------------------------------------------------
175 $fallback_chain = array();
176
177 // Info: Hook for addons.
178 $allow_standard_fallback = (bool) apply_filters( 'wpbc_fe_resolver_allow_fallback_to_standard', true, $form_slug, $status, $owner_user_id, $req );
179
180 // Try: (slug,status,owner_user_id) then (slug,status,global) Return like this: $found = [ "form_id":1, "form_slug":"standard", "status":"published", "owner_user_id":0 ]
181 $found = self::try_find_row( $form_slug, $status, $owner_user_id, $fallback_chain );
182
183
184 // If not found, and user is not super booking admin, then find row for "SUPER BOOKING ADMIN".
185 // INFO: block this falback. 2026-03-05 20:25. Do not fallback to supr booking admin user. Fallback to legacy mode, instead.
186 // if ( ! $found && ( $owner_user_id > 0 ) ) {
187 // $found = self::try_find_row( $form_slug, $status, 0, $fallback_chain );
188 // }
189
190 // If missing and slug != standard => try STANDARD (same status).
191 if ( ! $found && $allow_standard_fallback && ( 'standard' !== $form_slug ) ) {
192
193 $found = self::try_find_row( 'standard', $status, $owner_user_id, $fallback_chain );
194
195 // The same for the super booking admin.
196 if ( ! $found && ( $owner_user_id > 0 ) ) {
197 $found = self::try_find_row( 'standard', $status, 0, $fallback_chain );
198 }
199 }
200
201 if ( $found ) {
202
203 // Build args for BFB loader.
204 $bfb_args = array(
205 'form_slug' => (string) $found['form_slug'],
206 'status' => (string) $found['status'],
207 'owner_user_id' => (int) $found['owner_user_id'],
208 'resource_id' => (int) $resource_id,
209 );
210
211 // Pass form_id if known (fast, deterministic).
212 if ( ! empty( $found['form_id'] ) ) {
213 $bfb_args['form_id'] = (int) $found['form_id'];
214 }
215
216 $result = array(
217 'engine' => 'bfb_db',
218 'apply_after_load_filter'=> true,
219 'bfb_loader_args' => $bfb_args,
220 'fallback_chain' => $fallback_chain,
221 );
222
223 /**
224 * // Info: Hook for addons. Final override point (future scopes, preview rules, etc).
225 *
226 * @param array $result
227 * @param array $req
228 */
229 return (array) apply_filters( 'wpbc_fe_form_source_resolution', $result, $req );
230 }
231
232 // Not found in DB => legacy fallback (existing behavior).
233 return self::fallback_to_legacy_or_simple( $legacy_instance );
234 }
235
236 // ---------------------------------------------------------------------
237 // Internals
238 // ---------------------------------------------------------------------
239
240 /**
241 * Try find DB row for (slug,status,owner) and record attempt in chain.
242 *
243 * @param string $slug
244 * @param string $status
245 * @param int $owner_user_id
246 * @param array $chain (by ref)
247 *
248 * @return array|false
249 */
250 private static function try_find_row( $slug, $status, $owner_user_id, &$chain ) {
251
252 $slug = sanitize_text_field( (string) $slug );
253 $status = sanitize_key( (string) $status );
254 $owner_user_id = max( 0, (int) $owner_user_id );
255
256 $chain[] = array(
257 'form_slug' => $slug,
258 'status' => $status,
259 'owner_user_id' => $owner_user_id,
260 );
261 // Here we already have the booking form configuration: row.advanced_form = "<div>..." also row.content_form = '...', row.settings_json = '{...}'
262 $row = WPBC_BFB_Form_Storage::get_form_row_by_key( $slug, $status, $owner_user_id );
263
264 if ( ! $row || empty( $row->booking_form_id ) ) {
265 return false;
266 }
267
268 return array(
269 'form_id' => (int) $row->booking_form_id,
270 'form_slug' => $slug,
271 'status' => $status,
272 'owner_user_id' => isset( $row->owner_user_id ) ? max( 0, (int) $row->owner_user_id ) : $owner_user_id,
273 );
274 }
275
276 /**
277 * Legacy/simple fallback result.
278 *
279 * @param mixed $legacy_instance
280 *
281 * @return array
282 */
283 private static function fallback_to_legacy_or_simple( $legacy_instance ) {
284
285 if ( ( ! empty( $legacy_instance ) ) && ( false !== $legacy_instance->wpdev_bk_personal ) && ( 'On' != get_bk_option( 'booking_is_use_simple_booking_form' ) ) ) {
286 return array(
287 'engine' => 'legacy',
288 'apply_after_load_filter' => false,
289 'bfb_loader_args' => array(),
290 'fallback_chain' => array(),
291 );
292 }
293
294 return array(
295 'engine' => 'simple',
296 'apply_after_load_filter' => true,
297 'bfb_loader_args' => array(),
298 'fallback_chain' => array(),
299 );
300 }
301 }
302
303
304 /**
305 * BFB resolver for "Booking Form Data" template (content_form).
306 *
307 * Purpose:
308 * - When BFB DB engine is used for a form, the correct booking "data template"
309 * must come from wp_booking_form_structures.content_form (not booking_form_show option).
310 * - This is used by wpbc_get__booking_form_data__show() to replace [field] shortcodes
311 * with values from booking form_data string.
312 *
313 * Design notes:
314 * - Uses WPBC_FE_Form_Source_Resolver::resolve() to keep the same decision chain as front-end rendering.
315 * - Reads the template via wpbc_bfb_get_booking_form_pair() (must expose content_form).
316 * - Safe no-op when BFB / resolver is not available.
317 *
318 * @since 11.0.x
319 */
320 class WPBC_BFB_Booking_Data_Content_Resolver {
321
322 /**
323 * Try to override legacy $booking_form_show by BFB content_form (resolver-driven).
324 *
325 * @param string $booking_form_show Legacy resolved booking_form_show.
326 * @param int $resource_id Booking resource ID.
327 * @param string $form_slug Form slug/name (legacy: 'standard' or custom form name).
328 * @param string $form_data Booking form_data string from DB.
329 * @param array $params Optional. Context override (e.g. form_status/preview token).
330 *
331 * @return string Possibly overridden template.
332 */
333 public static function maybe_override_booking_form_show_by_bfb( $booking_form_show, $resource_id, $form_slug, $form_data, $params = array() ) {
334
335 $booking_form_show = (string) $booking_form_show;
336 $resource_id = (int) $resource_id;
337 $form_slug = (string) $form_slug;
338 $form_data = (string) $form_data;
339
340 if ( '' === $form_slug ) {
341 $form_slug = 'standard';
342 }
343
344 // Hard requirements for BFB resolver path.
345 if ( ! class_exists( 'WPBC_FE_Form_Source_Resolver' ) ) {
346 return $booking_form_show;
347 }
348 if ( ! class_exists( 'WPBC_BFB_Form_Loader' ) ) {
349 return $booking_form_show;
350 }
351
352 // ------------------------------------------------------------
353 // Preview-aware context (critical fix).
354 // ------------------------------------------------------------
355 $ctx = array();
356 if ( function_exists( 'wpbc_get_request_form_context' ) ) {
357 $ctx = wpbc_get_request_form_context();
358 }
359 $ctx = wp_parse_args( ( is_array( $params ) ? $params : array() ), ( is_array( $ctx ) ? $ctx : array() ) );
360
361 $form_status = ( isset( $ctx['form_status'] ) ) ? sanitize_key( $ctx['form_status'] ) : 'published';
362 if ( in_array( $form_status, array( 'publish', 'published' ), true ) ) {
363 $form_status = 'published';
364 }
365 if ( 'preview' !== $form_status ) {
366 $form_status = 'published';
367 }
368
369 // If preview session has exported content_form payload => use it (fast + correct).
370 if ( ( 'preview' === $form_status ) && function_exists( 'wpbc_bfb_preview__maybe_get_payload_from_context' ) ) {
371
372 $payload = wpbc_bfb_preview__maybe_get_payload_from_context( $ctx );
373
374 if ( ! empty( $payload ) && isset( $payload['content_form'] ) && ( '' !== (string) $payload['content_form'] ) ) {
375
376 $content_form = (string) $payload['content_form'];
377
378 // Keep legacy behavior: custom html shortcodes + language.
379 $content_form = wpbc_bf__replace_custom_html_shortcodes( $content_form );
380 $content_form = wpbc_lang( $content_form );
381
382 return $content_form;
383 }
384 }
385
386 // ------------------------------------------------------------
387 // Resolver-driven DB load (status must NOT be hard-coded).
388 // ------------------------------------------------------------
389 $custom_params = array();
390
391 // Optional: allow future-proof explicit form_id embedded into booking form_data.
392 $bfb_form_id = self::get_bfb_form_id_from_form_data( $form_data, $resource_id );
393 if ( $bfb_form_id > 0 ) {
394 $custom_params['bfb_form_id'] = (int) $bfb_form_id;
395 }
396
397 $req = array(
398 'resource_id' => $resource_id,
399 'form_slug' => $form_slug,
400 'form_status' => $form_status, // <-- critical: use preview/published
401 'custom_params' => $custom_params,
402 'legacy_instance' => null,
403
404 // Future-proof: let filters access the full preview context if they need it.
405 'ctx' => $ctx,
406 );
407
408 $resolved = WPBC_FE_Form_Source_Resolver::resolve( $req );
409
410 // If preview status is requested but nothing found, try published as fallback (keeps BFB).
411 if ( ( empty( $resolved['engine'] ) || ( 'bfb_db' !== $resolved['engine'] ) ) && ( 'preview' === $form_status ) ) {
412 $req['form_status'] = 'published';
413 $resolved = WPBC_FE_Form_Source_Resolver::resolve( $req );
414 }
415
416 if ( empty( $resolved['engine'] ) || ( 'bfb_db' !== $resolved['engine'] ) ) {
417 return $booking_form_show;
418 }
419
420 $bfb_loader_args = array();
421 if ( ! empty( $resolved['bfb_loader_args'] ) && is_array( $resolved['bfb_loader_args'] ) ) {
422 $bfb_loader_args = $resolved['bfb_loader_args'];
423 }
424
425 // Keep old behavior: allow explicit override from booking data (if present).
426 if ( ( $bfb_form_id > 0 ) && empty( $bfb_loader_args['form_id'] ) ) {
427 $bfb_loader_args['form_id'] = (int) $bfb_form_id;
428 }
429
430 // Signal loader that we need the "content_form" (booking fields data template).
431 $bfb_loader_args['return'] = 'content_form';
432
433 $bfb_pair = wpbc_bfb_get_booking_form_pair( $bfb_loader_args );
434
435 $content_form = self::extract_content_form_from_pair( $bfb_pair );
436
437 if ( '' === trim( $content_form ) ) {
438 return $booking_form_show;
439 }
440
441 // Keep legacy behavior: custom html shortcodes + language.
442 $content_form = wpbc_bf__replace_custom_html_shortcodes( $content_form );
443 $content_form = wpbc_lang( $content_form );
444
445 return $content_form;
446 }
447
448
449 /**
450 * Extract content template from loader return (supports multiple formats).
451 *
452 * Supported formats:
453 * 1) array( 'content' => '...' ) <- current WPBC_BFB_Form_Loader output
454 * 2) array( 'content_form' => '...' ) <- optional future alias
455 * 3) array( 'form' => array( 'content' => '...' ) ) <- future-proof
456 * 4) array( 'form' => array( 'content_form' => '...' ) ) <- future-proof
457 *
458 * @param mixed $bfb_pair Loader result.
459 *
460 * @return string
461 */
462 private static function extract_content_form_from_pair( $bfb_pair ) {
463
464 if ( ! is_array( $bfb_pair ) ) {
465 return '';
466 }
467
468 /**
469 * Safety: if loader fell back to legacy, do NOT override booking_form_show.
470 * WPBC_BFB_Form_Loader returns 'source' => 'builder'|'legacy'.
471 */
472 if ( isset( $bfb_pair['source'] ) && ( 'builder' !== $bfb_pair['source'] ) ) {
473 return '';
474 }
475
476 // Current loader output key.
477 if ( isset( $bfb_pair['content'] ) && is_string( $bfb_pair['content'] ) ) {
478 return (string) $bfb_pair['content'];
479 }
480
481 // Optional alias (if some implementations already return it).
482 if ( isset( $bfb_pair['content_form'] ) && is_string( $bfb_pair['content_form'] ) ) {
483 return (string) $bfb_pair['content_form'];
484 }
485
486 // Future-proof nested formats.
487 if ( isset( $bfb_pair['form'] ) && is_array( $bfb_pair['form'] ) ) {
488
489 if ( isset( $bfb_pair['form']['content'] ) && is_string( $bfb_pair['form']['content'] ) ) {
490 return (string) $bfb_pair['form']['content'];
491 }
492
493 if ( isset( $bfb_pair['form']['content_form'] ) && is_string( $bfb_pair['form']['content_form'] ) ) {
494 return (string) $bfb_pair['form']['content_form'];
495 }
496 }
497
498 return '';
499 }
500
501
502 /**
503 * (Optional / future-proof) Parse BFB form_id stored inside booking form_data.
504 *
505 * Supported patterns:
506 * - "wpbc_bfb_form_id{resource_id}^{ID}"
507 * - "wpbc_bfb_form_id^{ID}"
508 *
509 * @param string $form_data
510 * @param int $resource_id
511 *
512 * @return int
513 */
514 private static function get_bfb_form_id_from_form_data( $form_data, $resource_id ) {
515
516 $form_data = (string) $form_data;
517 $resource_id = (int) $resource_id;
518
519 if ( '' === $form_data ) {
520 return 0;
521 }
522
523 $prefixes = array(
524 'wpbc_bfb_form_id' . $resource_id . '^',
525 'wpbc_bfb_form_id^',
526 );
527
528 foreach ( $prefixes as $prefix ) {
529
530 $pos = strpos( $form_data, $prefix );
531 if ( false === $pos ) {
532 continue;
533 }
534
535 $chunk = substr( $form_data, $pos + strlen( $prefix ) );
536
537 // Ends at "~" if present.
538 if ( false !== strpos( $chunk, '~' ) ) {
539 $chunk = substr( $chunk, 0, strpos( $chunk, '~' ) );
540 }
541
542 $chunk = trim( $chunk );
543 if ( '' === $chunk ) {
544 continue;
545 }
546
547 $form_id = (int) $chunk;
548 if ( $form_id > 0 ) {
549 return $form_id;
550 }
551 }
552
553 return 0;
554 }
555 }
556