PluginProbe
Booking Calendar / 10.15.6
Booking Calendar v10.15.6
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 / page-form-builder / save-load / bfb-form-manager.php

bfb-form-manager.php in Booking Calendar 10.15.6, at includes/page-form-builder/save-load/bfb-form-manager.php

724 lines 26.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly.
4 }
5
6 /**
7 * Helper functions for saving / loading between different versions of booking forms.
8 *
9 * This file is the bridge between:
10 *
11 * - Old world: booking_form, booking_form_show, booking_forms_extended, etc.
12 * - New world: booking_form_structures table.
13 *
14 * It introduces a normalized FormConfig array:
15 *
16 * [
17 * 'id' => booking_form_id,
18 * 'form_name' => 'standard' or custom key (maps to form_slug),
19 * 'engine' => 'bfb' | 'legacy_advanced' | 'legacy_simple',
20 * 'engine_version' => '1.0',
21 * 'title' => string,
22 * 'description' => string,
23 * 'scope' => string,
24 * 'status' => string,
25 * 'is_default' => int (0|1),
26 * 'booking_resource_id' => int|null,
27 * 'owner_user_id' => int,
28 * 'structure_json' => string, // BFB structure or legacy stub/simple_form.
29 * 'settings' => array, // Decoded settings_json.
30 * 'advanced_form' => string, // Booking form shortcode configuration.
31 * 'content_form' => string, // Fields content template.
32 * 'picture_url' => string,
33 * 'created_at' => string,
34 * 'updated_at' => string,
35 * ]
36 *
37 * What we do:
38 * - Re-use booking_form_structures as canonical FormConfig storage.
39 * Each row now carries:
40 * - form_slug (your form_name): 'standard', custom names, etc.
41 * - owner_user_id for MultiUser ownership.
42 * - engine, engine_version.
43 * - structure_json (BFB structure, or legacy stub/simple_form).
44 * - advanced_form + content_form (what front-end uses today).
45 *
46 * Main public API:
47 * - wpbc_form_config_load():
48 * Tries the booking_form_structures table first, falls back to legacy
49 * wp_options-based configuration if no row exists.
50 *
51 * - wpbc_form_config_save():
52 * Used by the new Builder save controller. Writes to
53 * booking_form_structures via WPBC_BFB_Form_Storage::save_form().
54 * Optionally syncs booking_form / booking_forms_extended so all old
55 * runtime code keeps working.
56 *
57 * @package Booking Calendar.
58 * @author wpdevelop, oplugins
59 * @web-site https://wpbookingcalendar.com/
60 * @email info@wpbookingcalendar.com
61 *
62 * @modified 2025-12-07
63 * @version 1.1
64 * @file ../includes/page-form-builder/save-load/bfb-form-manager.php
65 */
66
67 // == 1. Small JSON helper ==
68
69 /**
70 * Safely JSON-encode arbitrary data for storage.
71 *
72 * Wrapper around wp_json_encode() that guarantees a string return value.
73 * If encoding fails, an empty string is returned.
74 *
75 * @param mixed $data Arbitrary data to be encoded.
76 *
77 * @return string JSON string, or empty string on failure.
78 */
79 function wpbc_form_config__encode_json( $data ) {
80 $json = wp_json_encode( $data );
81
82 return ( false === $json ) ? '' : $json;
83 }
84
85 // == 2. Build FormConfig from DB row ==
86
87 /**
88 * Normalize raw DB row from booking_form_structures into a FormConfig array.
89 *
90 * This is the single place where columns from booking_form_structures are
91 * mapped into the canonical FormConfig keys used in the new Form Manager.
92 *
93 * @param object $row Database row object. Typically returned by
94 * WPBC_BFB_Form_Storage::get_current_form_by_key().
95 *
96 * @return array|null Normalized FormConfig array or null if the row is empty.
97 */
98 function wpbc_form_config__from_row( $row ) {
99
100 if ( ! $row ) {
101 return null;
102 }
103
104 $settings = array();
105 if ( ! empty( $row->settings_json ) ) {
106 $decoded = json_decode( $row->settings_json, true );
107 if ( is_array( $decoded ) ) {
108 $settings = $decoded;
109 }
110 }
111
112 return array(
113 'id' => (int) $row->booking_form_id,
114 'owner_user_id' => isset( $row->owner_user_id ) ? (int) $row->owner_user_id : 0,
115 // External API uses "form_name" (key used in Builder / shortcodes); in DB it is stored as form_slug.
116 'form_name' => isset( $row->form_slug ) ? (string) $row->form_slug : '',
117 'engine' => ! empty( $row->engine ) ? (string) $row->engine : 'bfb',
118 'engine_version' => ! empty( $row->engine_version ) ? (string) $row->engine_version : '1.0',
119 'title' => isset( $row->title ) ? (string) $row->title : '',
120 'description' => isset( $row->description ) ? (string) $row->description : '',
121 'scope' => isset( $row->scope ) ? (string) $row->scope : 'global',
122 'status' => isset( $row->status ) ? (string) $row->status : 'published',
123 'is_default' => isset( $row->is_default ) ? (int) $row->is_default : 0,
124 'booking_resource_id' => isset( $row->booking_resource_id ) ? (int) $row->booking_resource_id : null,
125 'structure_json' => isset( $row->structure_json ) ? (string) $row->structure_json : '',
126 'settings' => $settings,
127 'advanced_form' => isset( $row->advanced_form ) ? (string) $row->advanced_form : '',
128 'content_form' => isset( $row->content_form ) ? (string) $row->content_form : '',
129 'picture_url' => isset( $row->picture_url ) ? (string) $row->picture_url : '',
130 'created_at' => isset( $row->created_at ) ? (string) $row->created_at : '',
131 'updated_at' => isset( $row->updated_at ) ? (string) $row->updated_at : '',
132 );
133 }
134
135 // == 3. Detect if a form already exists in storage ==
136
137 /**
138 * Check if a FormConfig row already exists in storage.
139 *
140 * Currently this lookup is global per site (form_slug only, status 'published')
141 * and does not yet filter by owner_user_id. The user_id parameter is reserved
142 * for future MultiUser-aware lookups.
143 *
144 * @param string $form_key Unique form key/slug (e.g. 'standard').
145 * @param int $user_id Optional. Owner user ID. Default 0 (global, not used yet).
146 *
147 * @return bool True if a row exists, false otherwise.
148 */
149 function wpbc_form_config_exists_in_storage( $form_key, $user_id = 0 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
150
151 $form_key = (string) $form_key;
152 if ( '' === $form_key ) {
153 return false;
154 }
155
156 // Do not filter by owner_user_id for now – global per site, status = 'published'.
157 $row = WPBC_BFB_Form_Storage::get_current_form_by_key( $form_key, $user_id, 'published' );
158
159 return ( $row && ! empty( $row->booking_form_id ) );
160 }
161
162
163 // == 4. Legacy loader (fallback from old options) ==
164
165 /**
166 * Build FormConfig from legacy options when no row exists in booking_form_structures.
167 *
168 * Behaviour:
169 * - Standard form uses:
170 * - booking_form (advanced form).
171 * - booking_form_show (content form).
172 * - booking_form_visual (Simple form structure, if present).
173 * - Custom forms use booking_forms_extended (serialized array of forms).
174 *
175 * Returned FormConfig uses:
176 * - engine: 'legacy_advanced' or 'legacy_simple' depending on available data.
177 * - structure_json: encoded visual/simple_form structure or legacy stub.
178 *
179 * @param string $form_key Form key. 'standard' or custom key used in legacy options.
180 * @param int $user_id Owner user ID for bookkeeping. Default 0.
181 *
182 * @return array|null FormConfig array on success or null if no legacy form found.
183 */
184 function wpbc_form_config__load_from_legacy( $form_key, $user_id = 0 ) {
185
186 $form_key = (string) $form_key;
187 if ( '' === $form_key ) {
188 $form_key = wpbc_bfb_get_default_form_key();
189 }
190
191 // 1) Standard form.
192 if ( 'standard' === $form_key ) {
193
194 $engine = 'legacy_advanced';
195 $advanced_form = wpbc_bfb_get_legacy_option_value( 'booking_form', $user_id );
196 $content_form = wpbc_bfb_get_legacy_option_value( 'booking_form_show', $user_id );
197
198 $visual = wpbc_bfb_get_legacy_option_value( 'booking_form_visual', $user_id );
199 $visual = maybe_unserialize( $visual );
200
201 $structure_json = wpbc_form_config__encode_json( array() );
202
203 // Free version only: use Simple Form structure as fallback Builder structure.
204 if ( ! class_exists( 'wpdev_bk_personal' ) ) {
205 if ( is_array( $visual ) && ! empty( $visual ) ) {
206 $engine = 'legacy_simple';
207 $structure_arr = wpbc_simple_form__export_to_bfb_structure( $visual );
208 $structure_json = wpbc_form_config__encode_json( $structure_arr );
209 }
210 }
211
212 return array(
213 'id' => 0,
214 'owner_user_id' => (int) $user_id,
215 'form_name' => 'standard',
216 'engine' => $engine,
217 'engine_version' => '1.0',
218 'title' => 'Standard',
219 'description' => '',
220 'scope' => 'global',
221 'status' => 'published',
222 'is_default' => 1,
223 'booking_resource_id' => null,
224 'structure_json' => $structure_json,
225 'settings' => array(),
226 'advanced_form' => (string) $advanced_form,
227 'content_form' => (string) $content_form,
228 'picture_url' => '',
229 'created_at' => '',
230 'updated_at' => '',
231 );
232 }
233
234 // 2) Custom forms from booking_forms_extended.
235 $extended = wpbc_bfb_get_legacy_option_value( 'booking_forms_extended', $user_id );
236 $extended = maybe_unserialize( $extended );
237
238 if ( is_array( $extended ) ) {
239 foreach ( $extended as $one ) {
240
241 if ( empty( $one['name'] ) || (string) $one['name'] !== $form_key ) {
242 continue;
243 }
244
245 $adv = isset( $one['form'] ) ? $one['form'] : '';
246 $cnt = isset( $one['content'] ) ? $one['content'] : '';
247 $engine = 'legacy_advanced';
248 $structure_json = wpbc_form_config__encode_json( array() );
249
250 // Only if Advanced Form is absent, or plugin works in Free mode.
251 if ( ( ! class_exists( 'wpdev_bk_personal' ) ) || empty( $adv ) ) {
252 if ( ! empty( $one['simple_form'] ) && is_array( $one['simple_form'] ) ) {
253 $engine = 'legacy_simple';
254 $structure_arr = wpbc_simple_form__export_to_bfb_structure( $one['simple_form'] );
255 $structure_json = wpbc_form_config__encode_json( $structure_arr );
256 }
257 }
258
259 return array(
260 'id' => 0,
261 'owner_user_id' => (int) $user_id,
262 'form_name' => $form_key,
263 'engine' => $engine,
264 'engine_version' => '1.0',
265 'title' => isset( $one['title'] ) ? (string) $one['title'] : (string) $form_key,
266 'description' => isset( $one['description'] ) ? (string) $one['description'] : '',
267 'scope' => 'global',
268 'status' => 'published',
269 'is_default' => ! empty( $one['is_default'] ) ? 1 : 0,
270 'booking_resource_id' => isset( $one['booking_resource_id'] ) ? intval( $one['booking_resource_id'] ) : null,
271 'structure_json' => $structure_json,
272 'settings' => array(),
273 'advanced_form' => (string) $adv,
274 'content_form' => (string) $cnt,
275 'picture_url' => '',
276 'created_at' => '',
277 'updated_at' => '',
278 );
279 }
280 }
281
282 return null;
283 }
284
285 // == 5. Public loader: try DB first, then legacy ==
286
287 /**
288 * Load unified FormConfig for a given form_key and optional owner.
289 *
290 * This is the main read API for booking form configuration:
291 * - First tries the booking_form_structures table (new engine).
292 * Currently we load status = 'published' row via
293 * WPBC_BFB_Form_Storage::get_current_form_by_key().
294 * - If nothing is found, falls back to legacy options via
295 * wpbc_form_config__load_from_legacy().
296 *
297 * @param string $form_key Form key. 'standard' or custom key. Default 'standard'.
298 * @param int $user_id Owner user ID for MultiUser environment. Default 0.
299 *
300 * @return array|null Normalized FormConfig array or null if nothing found.
301 */
302 function wpbc_form_config_load( $form_key = 'standard', $user_id = 0, $status = 'published', $is_fallback_to_legacy = true ) {
303
304 $form_key = (string) $form_key;
305 if ( '' === $form_key ) {
306 $form_key = 'standard';
307
308 }
309
310 // 1) Try storage table first (status = 'published').
311 $row = WPBC_BFB_Form_Storage::get_current_form_by_key( $form_key, $user_id, $status );
312 if ( $row ) {
313 return wpbc_form_config__from_row( $row );
314 }
315
316 // 2) Fallback to legacy options.
317 if ( $is_fallback_to_legacy ) {
318
319 return wpbc_form_config__load_from_legacy( $form_key, $user_id );
320
321 }
322
323 // Not found.
324 return null;
325 }
326
327 // == 6. Sync back to legacy options (for runtime compatibility) ==
328
329 /**
330 * Sync a normalized FormConfig back into legacy wp_options for runtime compatibility.
331 *
332 * This keeps all existing Booking Calendar runtime code working while the
333 * new Form Builder engine is being rolled out.
334 *
335 * Behaviour:
336 * - 'standard' form maps to:
337 * - booking_form (advanced_form).
338 * - booking_form_show (content_form).
339 * - Custom forms are stored in booking_forms_extended (serialized array).
340 *
341 * @param array $cfg FormConfig array produced by the Form Manager.
342 *
343 * @return void
344 */
345 function wpbc_form_config__sync_legacy_options( array $cfg ) {
346
347 if ( empty( $cfg['form_name'] ) ) {
348 return;
349 }
350
351 $form_name = (string) $cfg['form_name'];
352 $advanced_form = isset( $cfg['advanced_form'] ) ? (string) $cfg['advanced_form'] : '';
353 $content_form = isset( $cfg['content_form'] ) ? (string) $cfg['content_form'] : '';
354
355 // Standard form.
356 if ( 'standard' === $form_name ) {
357 if ( '' !== $advanced_form ) {
358 update_bk_option( 'booking_form', $advanced_form );
359 }
360 if ( '' !== $content_form ) {
361 update_bk_option( 'booking_form_show', $content_form );
362 }
363
364 // Optionally update booking_form_visual from structure_json for legacy_simple
365 // (we can add this later when exact mapping is defined).
366 return;
367 }
368
369 // Custom forms: booking_forms_extended.
370 $extended = get_bk_option( 'booking_forms_extended' );
371 $extended = maybe_unserialize( $extended );
372
373 if ( ! is_array( $extended ) ) {
374 $extended = array();
375 }
376
377 $found = false;
378
379 foreach ( $extended as &$one ) {
380 if ( empty( $one['name'] ) || (string) $one['name'] !== $form_name ) {
381 continue;
382 }
383
384 $one['form'] = $advanced_form;
385 $one['content'] = $content_form;
386 $found = true;
387 break;
388 }
389 unset( $one );
390
391 if ( ! $found ) {
392 $extended[] = array(
393 'name' => $form_name,
394 'form' => $advanced_form,
395 'content' => $content_form,
396 );
397 }
398
399 update_bk_option( 'booking_forms_extended', serialize( $extended ) );
400 }
401
402 // == 7. Public saver: BFB (and later other engines) -> DB + legacy ==
403
404 /**
405 * Save BFB Form into DB - Main Save Function!
406 *
407 * Save unified FormConfig into booking_form_structures and sync legacy options.
408 *
409 * Expected minimal keys in $form_config:
410 * - form_name
411 * - engine
412 * - structure_json (for BFB this is full Builder structure JSON)
413 * - advanced_form (booking form shortcode configuration)
414 * - content_form (fields data/content configuration)
415 *
416 * Behaviour:
417 * - Always writes to booking_form_structures via WPBC_BFB_Form_Storage::save_form().
418 * - Uses form_slug = form_name.
419 * - Uses status from $form_config['status'] or 'published' by default.
420 * - Optionally calls wpbc_form_config__sync_legacy_options() to keep
421 * existing legacy runtime code working.
422 *
423 * @param array $form_config Normalized FormConfig array.
424 * @param array $args {
425 * Optional. Additional saving arguments.
426 *
427 * @type bool $sync_legacy Whether to sync legacy wp_options. Default true.
428 * }
429 *
430 * @return int|false booking_form_id on success, or false on failure.
431 */
432 function wpbc_form_config_save( array $form_config, array $args = array() ) {
433
434 $args = wp_parse_args(
435 $args,
436 array(
437 'sync_legacy' => true,
438 )
439 );
440
441 $form_name = isset( $form_config['form_name'] ) ? (string) $form_config['form_name'] : '';
442 if ( '' === $form_name ) {
443 $form_name = 'standard';
444 }
445
446 $engine = isset( $form_config['engine'] ) ? (string) $form_config['engine'] : 'bfb';
447 $engine_version = isset( $form_config['engine_version'] ) ? (string) $form_config['engine_version'] : '1.0';
448
449 $structure_json = isset( $form_config['structure_json'] ) ? (string) $form_config['structure_json'] : '';
450 $settings = isset( $form_config['settings'] ) ? $form_config['settings'] : array();
451 $settings_json = wpbc_form_config__encode_json( $settings ); // Returns '' on error/empty.
452
453 $advanced_form = isset( $form_config['advanced_form'] ) ? (string) $form_config['advanced_form'] : '';
454 $content_form = isset( $form_config['content_form'] ) ? (string) $form_config['content_form'] : '';
455
456 if ( '' === $structure_json && 'bfb' === $engine ) {
457 // For BFB we expect real structure JSON.
458 return false;
459 }
460
461 $owner_user_id = isset( $form_config['owner_user_id'] ) ? (int) $form_config['owner_user_id'] : 0;
462
463 $storage_data = array(
464 // New schema uses form_slug; we map form_name -> form_slug.
465 'form_slug' => $form_name,
466
467 'title' => isset( $form_config['title'] ) ? (string) $form_config['title'] : $form_name,
468 'description' => isset( $form_config['description'] ) ? (string) $form_config['description'] : '',
469 'scope' => isset( $form_config['scope'] ) ? (string) $form_config['scope'] : 'global',
470
471 'booking_resource_id' => isset( $form_config['booking_resource_id'] ) ? (int) $form_config['booking_resource_id'] : null,
472 'owner_user_id' => ! empty( $owner_user_id ) ? $owner_user_id : 0,
473 'is_default' => ! empty( $form_config['is_default'] ) ? 1 : 0,
474 'status' => isset( $form_config['status'] ) ? (string) $form_config['status'] : 'published',
475
476 'engine' => $engine,
477 'engine_version' => $engine_version,
478 'structure_json' => $structure_json,
479 'settings_json' => $settings_json,
480 'advanced_form' => $advanced_form,
481 'content_form' => $content_form,
482
483 // Optional picture/preview image URL.
484 'picture_url' => isset( $form_config['picture_url'] ) ? (string) $form_config['picture_url'] : null,
485 );
486
487 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $storage_data );
488
489 if ( $booking_form_id && ! empty( $args['sync_legacy'] ) ) {
490 wpbc_form_config__sync_legacy_options( $form_config );
491 }
492
493 return $booking_form_id;
494 }
495
496
497 // == Default Load Form !) ==
498
499 /**
500 * Get default form key used by the Form Builder.
501 *
502 * Centralizes the default key for the main booking form, currently 'standard'.
503 *
504 * @return string Default form key.
505 */
506 function wpbc_bfb_get_default_form_key() {
507 return 'standard';
508 }
509
510 /**
511 * Output AJAX boot configuration for the Form Builder.
512 *
513 * Prints a small inline script that exposes WPBC_BFB_Ajax on window. This
514 * configuration is consumed by the JS Builder to perform save / load
515 * requests via admin-ajax.php.
516 *
517 * Should be called only on the Form Builder admin page.
518 *
519 * @return void
520 */
521 function wpbc_bfb_output_ajax_boot_config() {
522
523 $form_key = wpbc_bfb_get_default_form_key(); // 'standard' – or detect from current screen / URL / selection.
524
525 $ajax_config = array(
526 'url' => admin_url( 'admin-ajax.php' ),
527 'nonce_save' => wp_create_nonce( 'wpbc_bfb_form_save' ),
528 'nonce_load' => wp_create_nonce( 'wpbc_bfb_form_load' ),
529 'nonce_create' => wp_create_nonce( 'wpbc_bfb_form_create' ),
530 'nonce_list' => wp_create_nonce( 'wpbc_bfb_form_list' ),
531 'form_name' => $form_key, // info: INIT_FORM_LOAD.
532 'engine' => 'bfb',
533 'engine_version' => '1.0',
534 // Initial load behavior.
535 'initial_load' => 'ajax', // 'ajax' / 'example' / 'blank'.
536 'initial_load_fallback' => 'example', // 'example' / 'blank'.
537 'load_action' => 'WPBC_AJX_BFB_LOAD_FORM_CONFIG',
538 );
539 ?>
540 <script type="text/javascript"> window.WPBC_BFB_Ajax = <?php echo wp_json_encode( $ajax_config ); ?>; </script>
541 <?php
542 }
543
544 /**
545 * Get the "advanced" booking form configuration for a given form key.
546 *
547 * This is a thin convenience wrapper around wpbc_form_config_load() that
548 * returns only the advanced_form string.
549 *
550 * @param string $form_key Form key. Default 'standard'.
551 *
552 * @return string Advanced form configuration or empty string if not found.
553 */
554 function wpbc_get_form_advanced( $form_key = 'standard' ) {
555 $cfg = wpbc_form_config_load( $form_key );
556
557 return ( ! empty( $cfg['advanced_form'] ) ? $cfg['advanced_form'] : '' );
558 }
559
560 /**
561 * Get the "content" (fields data) configuration for a given form key.
562 *
563 * This is a thin convenience wrapper around wpbc_form_config_load() that
564 * returns only the content_form string.
565 *
566 * @param string $form_key Form key. Default 'standard'.
567 *
568 * @return string Content form configuration or empty string if not found.
569 */
570 function wpbc_get_form_content( $form_key = 'standard' ) {
571 $cfg = wpbc_form_config_load( $form_key );
572
573 return ( ! empty( $cfg['content_form'] ) ? $cfg['content_form'] : '' );
574 }
575
576
577
578 /**
579 * Bridge: implement wpbc_bfb_form_loader_from_builder filter using
580 * booking_form_structures table.
581 *
582 * This function is responsible ONLY for pulling already exported
583 * shortcodes/markup from the BFB storage:
584 * - advanced_form -> "booking form" shortcodes.
585 * - content_form -> "Content of booking fields data" shortcodes.
586 *
587 * It does NOT try to interpret structure_json; that is the Builder's job.
588 *
589 * @since 11.0.0
590 */
591
592
593 /**
594 * Load form/content from booking_form_structures for the Form Loader.
595 *
596 * Expected $args keys (normalized by WPBC_BFB_Form_Loader):
597 * - form_slug : string Logical form key (e.g. 'standard').
598 * - form_id : int booking_form_id (optional, takes precedence).
599 * - status : string 'published', 'preview', 'draft', ...
600 * - resource_id : int Booking resource ID (currently informational).
601 * - user_id : int Current user ID (reserved for future MultiUser).
602 *
603 * @param array $empty Default empty pair: ['form' => '', 'content' => ''].
604 * @param array $args Loader arguments.
605 *
606 * @return array Pair ['form' => string, 'content' => string].
607 */
608 function wpbc_bfb__load_from_bfb_table( $empty, $args ) {
609
610 global $wpdb;
611
612 // Ensure we have a consistent empty array.
613 if ( ! is_array( $empty ) ) {
614 $empty = array(
615 'form' => '',
616 'content' => '',
617 );
618 }
619
620 // Normalized args (defensive).
621 $form_id = isset( $args['form_id'] ) ? intval( $args['form_id'] ) : 0;
622 $form_slug = isset( $args['form_slug'] ) ? (string) $args['form_slug'] : '';
623 $status = isset( $args['status'] ) ? strtolower( trim( (string) $args['status'] ) ) : 'published';
624
625 // FixIn: 2026-03-08.
626 $has_user_key = array_key_exists( 'user_id', $args );
627 $owner_user_id = $has_user_key ? max( 0, (int) $args['user_id'] ) : 0;
628
629 $has_owner_key = array_key_exists( 'owner_user_id', $args );
630 $owner_user_id = $has_owner_key ? max( $owner_user_id, (int) $args['owner_user_id'] ) : $owner_user_id;
631
632 $has_owner_key = $has_user_key || $has_owner_key;
633
634 // -----------------------------------------------------------------
635 // Normalize status to match DB semantics.
636 // booking_form_structures uses values like: active, preview, draft, archived.
637 // -----------------------------------------------------------------
638 if ( '' === $status ) {
639 $status = 'published';
640 } elseif ( in_array( $status, array( 'publish', 'published' ), true ) ) {
641 $status = 'published';
642 }
643
644
645 // Nothing to do if we have neither an ID nor a slug.
646 if ( $form_id <= 0 && '' === $form_slug ) {
647 return $empty;
648 }
649
650 // Optional: if helper exists, ensure table really exists.
651 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
652 return $empty;
653 }
654
655 // -----------------------------------------------------------------
656 // Build SELECT depending on whether form_id is available.
657 // -----------------------------------------------------------------
658 if ( $form_id > 0 ) {
659 // Prefer direct lookup by primary key + status.
660 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
661 $row = $wpdb->get_row( $wpdb->prepare( "SELECT advanced_form, content_form, settings_json
662 FROM {$wpdb->prefix}booking_form_structures
663 WHERE booking_form_id = %d
664 AND status = %s
665 LIMIT 1", $form_id, $status ) );
666 } else {
667
668 if ( $has_owner_key && $owner_user_id > 0 ) {
669
670 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
671 $row = $wpdb->get_row( $wpdb->prepare( "SELECT advanced_form, content_form, settings_json
672 FROM {$wpdb->prefix}booking_form_structures
673 WHERE form_slug = %s
674 AND status = %s
675 AND owner_user_id = %d
676 ORDER BY version DESC, updated_at DESC, booking_form_id DESC
677 LIMIT 1", $form_slug, $status, $owner_user_id ) );
678
679 } elseif ( $has_owner_key ) {
680
681 // global: 0 or NULL
682 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
683 $row = $wpdb->get_row( $wpdb->prepare( "SELECT advanced_form, content_form, settings_json
684 FROM {$wpdb->prefix}booking_form_structures
685 WHERE form_slug = %s
686 AND status = %s
687 AND ( owner_user_id = 0 OR owner_user_id IS NULL )
688 ORDER BY version DESC, updated_at DESC, booking_form_id DESC
689 LIMIT 1", $form_slug, $status ) );
690
691 } else {
692
693 // Legacy behavior: no owner filter at all.
694 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
695 $row = $wpdb->get_row( $wpdb->prepare( "SELECT advanced_form, content_form, settings_json
696 FROM {$wpdb->prefix}booking_form_structures
697 WHERE form_slug = %s
698 AND status = %s
699 LIMIT 1", $form_slug, $status ) );
700 }
701 }
702
703
704 if ( ! $row ) {
705 return $empty;
706 }
707
708 // Ensure we always return both keys, even if one is empty.
709 $form = isset( $row->advanced_form ) ? (string) $row->advanced_form : '';
710 $content = isset( $row->content_form ) ? (string) $row->content_form : '';
711 $settings_json = isset( $row->settings_json ) ? (string) $row->settings_json : '';
712
713 return array(
714 'form' => $form,
715 'content' => $content,
716 'settings_json' => $settings_json,
717 );
718 }
719
720 /**
721 * Attach loader to wpbc_bfb_form_loader_from_builder.
722 */
723 add_filter( 'wpbc_bfb_form_loader_from_builder', 'wpbc_bfb__load_from_bfb_table', 10, 2 );
724