PluginProbe
Booking Calendar / 11.6
Booking Calendar v11.6
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
booking / includes / page-form-builder / legacy-import / bfb-import-simple-form.php

bfb-import-simple-form.php in Booking Calendar 11.6, at includes/page-form-builder/legacy-import/bfb-import-simple-form.php

1,207 lines 34.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Calendar – Form Builder. Export "Simple booking form" -> BFB structure.
4 *
5 *
6 *
7 * This file replaces the old "form_simple__export_to_bfb.php" logic and
8 * is dedicated to exporting the legacy Simple booking form "visual structure"
9 * into the new BFB structure used by the Form Builder.
10 *
11 * Responsibilities:
12 * - Load visual Simple Form structure from DB.
13 * - Group fields into rows/columns (same logic as the legacy visual editor).
14 * - Build a BFB structure array:
15 * [
16 * {
17 * "page": 1,
18 * "content": [
19 * { "type": "section", "data": { "columns": [ { "width": "...", "items": [ { "type": "field", "data": {...} } ] } ] } }
20 * ]
21 * }
22 * ]
23 * - Optionally store the result as JSON in an option for later import into BFB.
24 *
25 * @since 10.14.0
26 *
27 * @package Booking_Calendar
28 * @file: ../includes/page-form-builder/save-load/bfb-import-simple-form.php
29 */
30
31 if ( ! defined( 'ABSPATH' ) ) {
32 exit;
33 }
34
35
36 /**
37 * Export current Simple Form visual structure into a BFB structure array.
38 *
39 * @param array|false $visual_form_structure Optional preloaded Simple Form structure.
40 *
41 * @return array BFB structure ready to JSON-encode.
42 */
43 function wpbc_simple_form__export_to_bfb_structure( $visual_form_structure = false ) {
44
45 // 1) Get visual structure from DB if not passed.
46 if ( empty( $visual_form_structure ) ) {
47 if ( function_exists( 'wpbc_simple_form__db__get_visual_form_structure' ) ) {
48 $visual_form_structure = wpbc_simple_form__db__get_visual_form_structure();
49 } else {
50 $visual_form_structure = array();
51 }
52 }
53 $visual_form_structure = maybe_unserialize( $visual_form_structure );
54
55 if ( ! is_array( $visual_form_structure ) ) {
56 $visual_form_structure = array();
57 }
58
59 // 2) Layout meta.
60 $layout_type = function_exists( 'get_bk_option' ) ? get_bk_option( 'booking_form_structure_type' ) : '';
61 if ( empty( $layout_type ) ) {
62 $layout_type = 'vertical';
63 }
64
65 $max_cols = function_exists( 'get_bk_option' ) ? get_bk_option( 'booking_form_layout_max_cols' ) : 1;
66 $max_cols = max( 1, intval( $max_cols ) );
67
68 $is_timepicker = false;
69 if ( function_exists( 'get_bk_option' ) ) {
70 $is_timepicker = ( 'On' === get_bk_option( 'booking_timeslot_picker' ) );
71 }
72
73 // 3) Normalize fields & filter inactive non-obligatory.
74 $normalized_fields = array();
75 foreach ( $visual_form_structure as $form_field ) {
76
77 $defaults = array(
78 'type' => 'text',
79 'name' => 'unique_name',
80 'obligatory' => 'Off',
81 'if_exist_required' => 'Off',
82 'active' => 'On',
83 'required' => 'Off',
84 'label' => '',
85 'value' => '',
86 'placeholder' => '',
87 'cssclass' => '',
88 'html_id' => '',
89 );
90 $form_field = wp_parse_args( $form_field, $defaults );
91
92 // Skip inactive non-obligatory fields.
93 if ( ( 'Off' === $form_field['active'] ) && ( 'On' !== $form_field['obligatory'] ) ) {
94 continue;
95 }
96
97 $normalized_fields[] = $form_field;
98 }
99
100 // 3.1) Ensure at least one calendar (Simple Form has a fallback).
101 $has_calendar = false;
102 foreach ( $normalized_fields as $f ) {
103 if ( 'calendar' === $f['type'] || 'calendar' === $f['name'] ) {
104 $has_calendar = true;
105 break;
106 }
107 }
108 if ( ! $has_calendar ) {
109 // Prepend a default calendar field (similar to Simple Form fallback: [calendar] at top).
110 $normalized_fields = array_merge(
111 array(
112 array(
113 'type' => 'calendar',
114 'name' => 'calendar',
115 'label' => __( 'Select Date', 'booking' ),
116 'value' => '',
117 'placeholder' => '',
118 'cssclass' => '',
119 'html_id' => '',
120 'obligatory' => 'On',
121 'if_exist_required' => 'Off',
122 'required' => 'On',
123 'active' => 'On',
124 ),
125 ),
126 $normalized_fields
127 );
128 }
129
130 $builder_structure = array();
131 $field_seq = 0;
132 $section_seq = 0;
133
134 // 4) Wizard layouts -> multi-page structure.
135 if ( in_array( $layout_type, array( 'wizard_2columns', 'wizard_services_a' ), true ) ) {
136
137 $builder_structure = wpbc_simple_form__export_wizard_to_bfb_structure(
138 $normalized_fields,
139 $layout_type,
140 $max_cols,
141 $is_timepicker,
142 $field_seq,
143 $section_seq
144 );
145
146 } else {
147 // 5) Standard layouts (vertical, form_center, form_right) -> single page.
148 $sections = wpbc_simple_form__build_bfb_sections_from_fields(
149 $normalized_fields,
150 $max_cols,
151 $is_timepicker,
152 $field_seq,
153 $section_seq
154 );
155
156 if ( ! empty( $sections ) ) {
157 $builder_structure[] = array(
158 'page' => 1,
159 'layout_type' => $layout_type, // meta only, safe to ignore.
160 'max_cols' => $max_cols, // meta only, safe to ignore.
161 'content' => $sections,
162 );
163 }
164 }
165
166 return $builder_structure;
167 }
168
169 // ---------------------------------------------------------------------------------------------------------------------
170 // == Helpers ==
171 // ---------------------------------------------------------------------------------------------------------------------
172 /**
173 * Export Simple Form "wizard" layouts into a multi-page BFB structure.
174 *
175 * @param array $fields Normalized fields (active/obligatory only).
176 * @param string $layout_type 'wizard_2columns' | 'wizard_services_a'.
177 * @param int $max_cols booking_form_layout_max_cols.
178 * @param bool $is_timepicker timeslot picker On/Off.
179 * @param int $field_seq Sequence for unique field IDs (by reference).
180 * @param int $section_seq Sequence for unique section IDs (by reference).
181 *
182 * @return array
183 */
184 function wpbc_simple_form__export_wizard_to_bfb_structure( $fields, $layout_type, $max_cols, $is_timepicker, &$field_seq, &$section_seq ) {
185
186 $calendar_field = null;
187 $duration_field = null;
188 $time_fields_map = array(); // name => field.
189 $submit_field = null;
190 $other_fields = array();
191
192 // Split fields into special buckets + "other".
193 foreach ( $fields as $f ) {
194
195 $type = isset( $f['type'] ) ? $f['type'] : '';
196 $name = isset( $f['name'] ) ? $f['name'] : '';
197
198 if ( 'calendar' === $type || 'calendar' === $name ) {
199 if ( null === $calendar_field ) {
200 $calendar_field = $f;
201 continue;
202 }
203 }
204
205 if ( 'durationtime' === $name && null === $duration_field ) {
206 $duration_field = $f;
207 continue;
208 }
209
210 if ( in_array( $name, array( 'rangetime', 'starttime', 'endtime' ), true ) ) {
211 // Keep first occurrence only, preserve order later.
212 if ( ! isset( $time_fields_map[ $name ] ) ) {
213 $time_fields_map[ $name ] = $f;
214 }
215 continue;
216 }
217
218 if ( 'submit' === $type && null === $submit_field ) {
219 $submit_field = $f;
220 continue;
221 }
222
223 $other_fields[] = $f;
224 }
225
226 $builder_structure = array();
227 $has_duration = ( null !== $duration_field );
228 $wizard_steps_count = $has_duration ? 3 : 2;
229 $page_index = 1;
230 $is_services_timeline = ( 'wizard_services_a' === $layout_type );
231
232 // ---------------------------------------------------------------------
233 // STEP 1: if durationtime exists -> only this field + Next.
234 // ---------------------------------------------------------------------
235 if ( $has_duration ) {
236
237 $page_content = array();
238
239 if ( $is_services_timeline ) {
240 $page_content[] = wpbc_simple_form__build_steps_timeline_section(
241 $wizard_steps_count,
242 $page_index,
243 $field_seq,
244 $section_seq
245 );
246 }
247
248 $sections = wpbc_simple_form__build_bfb_sections_from_fields(
249 array( $duration_field ),
250 $max_cols,
251 $is_timepicker,
252 $field_seq,
253 $section_seq
254 );
255
256 $page_content = array_merge( $page_content, $sections );
257
258 if ( ! empty( $page_content ) ) {
259
260 $step_index = $page_index;
261 $is_last_step = ( $step_index === $wizard_steps_count );
262 $has_back = false; // First step: no Back button.
263 $include_next = ! $is_last_step; // Here always true (duration -> 3 steps).
264 $submit_for_nav = null; // Submit only on last step.
265
266 $page_content[] = wpbc_simple_form__build_wizard_nav_section(
267 $step_index,
268 $wizard_steps_count,
269 $has_back,
270 $include_next,
271 $submit_for_nav,
272 $field_seq,
273 $section_seq
274 );
275
276 $builder_structure[] = array(
277 'page' => $step_index,
278 'layout_type' => $layout_type,
279 'wizard_step' => $step_index,
280 'content' => $page_content,
281 );
282
283 ++$page_index;
284 }
285 }
286
287 // ---------------------------------------------------------------------
288 // STEP 1 (no duration) or STEP 2 (with duration): calendar + time fields.
289 // ---------------------------------------------------------------------
290 $has_calendar_or_time = ( null !== $calendar_field ) || ! empty( $time_fields_map );
291
292 if ( $has_calendar_or_time ) {
293
294 $page_content = array();
295
296 if ( $is_services_timeline ) {
297 $page_content[] = wpbc_simple_form__build_steps_timeline_section(
298 $wizard_steps_count,
299 $page_index,
300 $field_seq,
301 $section_seq
302 );
303 }
304
305 $has_time_fields = ! empty( $time_fields_map );
306
307 // Special wizard layout: calendar + vertical divider + time fields in the right column.
308 if ( ( null !== $calendar_field ) && $has_time_fields ) {
309
310 $page_content[] = wpbc_simple_form__build_wizard_calendar_times_section(
311 $calendar_field,
312 $time_fields_map,
313 $field_seq,
314 $section_seq
315 );
316
317 } else {
318 // Fallback: generic grouping (e.g. calendar only or time fields only).
319 if ( null !== $calendar_field ) {
320 $sections = wpbc_simple_form__build_bfb_sections_from_fields(
321 array( $calendar_field ),
322 $max_cols,
323 $is_timepicker,
324 $field_seq,
325 $section_seq
326 );
327 $page_content = array_merge( $page_content, $sections );
328 }
329
330 foreach ( array( 'rangetime', 'starttime', 'endtime' ) as $time_name ) {
331 if ( isset( $time_fields_map[ $time_name ] ) ) {
332 $sections = wpbc_simple_form__build_bfb_sections_from_fields(
333 array( $time_fields_map[ $time_name ] ),
334 $max_cols,
335 $is_timepicker,
336 $field_seq,
337 $section_seq
338 );
339 $page_content = array_merge( $page_content, $sections );
340 }
341 }
342 }
343
344 if ( ! empty( $page_content ) ) {
345
346 $step_index = $page_index;
347 $is_last_step = ( $step_index === $wizard_steps_count );
348 $has_back = ( $step_index > 1 );
349 $include_next = ! $is_last_step; // If 2-step wizard -> step 1; if 3-step wizard -> step 2.
350 $submit_for_nav = null; // Submit only on last step.
351
352 $page_content[] = wpbc_simple_form__build_wizard_nav_section(
353 $step_index,
354 $wizard_steps_count,
355 $has_back,
356 $include_next,
357 $submit_for_nav,
358 $field_seq,
359 $section_seq
360 );
361
362 $builder_structure[] = array(
363 'page' => $step_index,
364 'layout_type' => $layout_type,
365 'wizard_step' => $step_index,
366 'content' => $page_content,
367 );
368
369 ++$page_index;
370 }
371 }
372
373 // ---------------------------------------------------------------------
374 // Last STEP: remaining fields + final submit (if exists).
375 // - If duration -> this is step 3.
376 // - If no duration -> this is step 2.
377 // ---------------------------------------------------------------------
378 if ( ! empty( $other_fields ) || null !== $submit_field ) {
379
380 $page_content = array();
381
382 if ( $is_services_timeline ) {
383 $page_content[] = wpbc_simple_form__build_steps_timeline_section(
384 $wizard_steps_count,
385 $page_index,
386 $field_seq,
387 $section_seq
388 );
389 }
390
391 // Other fields in the same grid logic as classic Simple Form.
392 if ( ! empty( $other_fields ) ) {
393 $sections = wpbc_simple_form__build_bfb_sections_from_fields(
394 $other_fields,
395 $max_cols,
396 $is_timepicker,
397 $field_seq,
398 $section_seq
399 );
400 $page_content = array_merge( $page_content, $sections );
401 }
402
403 if ( ! empty( $page_content ) ) {
404
405 $step_index = $page_index;
406 $is_last_step = true;
407 $has_back = ( $step_index > 1 );
408 $include_next = false; // Last step: no Next.
409 $submit_for_nav = $submit_field; // Submit goes together with Back + divider.
410
411 $page_content[] = wpbc_simple_form__build_wizard_nav_section(
412 $step_index,
413 $wizard_steps_count,
414 $has_back,
415 $include_next,
416 $submit_for_nav,
417 $field_seq,
418 $section_seq
419 );
420
421 $builder_structure[] = array(
422 'page' => $step_index,
423 'layout_type' => $layout_type,
424 'wizard_step' => $step_index,
425 'content' => $page_content,
426 );
427 }
428 }
429
430 return $builder_structure;
431 }
432
433 /**
434 * Group fields into rows (exact same logic as Simple Form)
435 *
436 * @param array $fields Normalized fields.
437 * @param int $max_cols booking_form_layout_max_cols.
438 * @param bool $is_timepicker Whether timeslot picker is enabled for 'rangetime'.
439 *
440 * @return array Array of rows; each row is an array of $form_field arrays.
441 */
442 function wpbc_simple_form__group_fields_into_rows_for_bfb( $fields, $max_cols, $is_timepicker ) {
443
444 $rows = array();
445 $curr_row = 0;
446 $rows[ $curr_row ] = array();
447
448 foreach ( $fields as $form_field ) {
449
450 $type = isset( $form_field['type'] ) ? $form_field['type'] : '';
451 $name = isset( $form_field['name'] ) ? $form_field['name'] : '';
452
453 $is_full_width_type = in_array( $type, array( 'textarea', 'calendar', 'submit' ), true );
454 $is_full_width_time = ( ( 'rangetime' === $name ) && $is_timepicker );
455
456 if (
457 ( count( $rows[ $curr_row ] ) >= $max_cols ) ||
458 $is_full_width_type ||
459 $is_full_width_time
460 ) {
461 ++$curr_row;
462 $rows[ $curr_row ] = array();
463 }
464
465 $rows[ $curr_row ][] = $form_field;
466
467 if ( $is_full_width_type || $is_full_width_time ) {
468 ++$curr_row;
469 $rows[ $curr_row ] = array();
470 }
471 }
472
473 return $rows;
474 }
475
476 /**
477 * Convert rows into BFB “section” structures.
478 *
479 * Each row becomes one "section", with N equally sized columns.
480 *
481 * @param array $fields Normalized fields.
482 * @param int $max_cols booking_form_layout_max_cols.
483 * @param bool $is_timepicker Whether timeslot picker is enabled.
484 * @param int $field_seq Field ID sequence (by reference).
485 * @param int $section_seq Section ID sequence (by reference).
486 *
487 * @return array List of BFB sections.
488 */
489 function wpbc_simple_form__build_bfb_sections_from_fields( $fields, $max_cols, $is_timepicker, &$field_seq, &$section_seq ) {
490
491 $sections = array();
492 $rows = wpbc_simple_form__group_fields_into_rows_for_bfb( $fields, $max_cols, $is_timepicker );
493
494 foreach ( $rows as $row_fields ) {
495
496 if ( empty( $row_fields ) ) {
497 continue;
498 }
499
500 $cols_count = count( $row_fields );
501 $cols_count = max( 1, $cols_count );
502 $col_width = round( 100 / $cols_count, 4 );
503
504 $columns = array();
505
506 foreach ( $row_fields as $form_field ) {
507
508 ++$field_seq;
509 $item = wpbc_simple_form__map_field_to_bfb_item( $form_field, $field_seq );
510
511 // Skip if mapping failed.
512 if ( empty( $item ) || ! is_array( $item ) ) {
513 continue;
514 }
515
516 $columns[] = array(
517 'width' => sprintf( '%.4f%%', $col_width ),
518 'items' => array( $item ),
519 );
520 }
521
522 if ( empty( $columns ) ) {
523 continue;
524 }
525
526 ++$section_seq;
527 $section_id = 'section-simple-' . $section_seq;
528
529 $sections[] = array(
530 'type' => 'section',
531 'data' => array(
532 'id' => $section_id,
533 'label' => __( 'Section', 'booking' ),
534 'html_id' => '',
535 'cssclass' => '',
536 // Leave col_styles empty – BFB Column Styles will apply defaults.
537 'col_styles'=> '',
538 'columns' => $columns,
539 ),
540 );
541 }
542
543 return $sections;
544 }
545
546 /**
547 * Helper for Steps Timeline (only used for wizard_services_a)
548 *
549 * @param int $steps_count Total number of wizard steps (2 or 3).
550 * @param int $active_step Step index for this page (1-based).
551 * @param int $field_seq Field ID sequence (by reference).
552 * @param int $section_seq Section ID sequence (by reference).
553 *
554 * @return array BFB section with one "steps_timeline" field.
555 */
556 function wpbc_simple_form__build_steps_timeline_section( $steps_count, $active_step, &$field_seq, &$section_seq ) {
557
558 ++$field_seq;
559 $field_id = 'steps_timeline_' . $field_seq;
560
561 $item = array(
562 'type' => 'field',
563 'data' => array(
564 'id' => $field_id,
565 'type' => 'steps_timeline',
566 'usage_key' => 'steps_timeline',
567 'label' => '',
568 'html_id' => '',
569 'cssclass' => '',
570 'steps_count' => intval( $steps_count ),
571 'active_step' => intval( $active_step ),
572 ),
573 );
574
575 ++$section_seq;
576 $section_id = 'section-wizard-timeline-' . $section_seq;
577
578 return array(
579 'type' => 'section',
580 'data' => array(
581 'id' => $section_id,
582 'label' => __( 'Step header', 'booking' ),
583 'html_id' => '',
584 'cssclass' => 'wpbc_bfb__section--timeline',
585 'col_styles'=> '',
586 'columns' => array(
587 array(
588 'width' => '100.0000%',
589 'items' => array( $item ),
590 ),
591 ),
592 ),
593 );
594 }
595
596 /**
597 * Wizard calendar + time fields section.
598 *
599 * Produces a section like:
600 * - Left column: calendar
601 * - Middle skinny column: vertical divider
602 * - Right column: rangetime / starttime / endtime stacked
603 *
604 * Matches the example structure used by Wizard (several steps) template.
605 *
606 * @param array $calendar_field Simple Form "calendar" field.
607 * @param array $time_fields_map ['rangetime' => field, 'starttime' => field, 'endtime' => field] subset.
608 * @param int $field_seq Field ID sequence (by reference).
609 * @param int $section_seq Section ID sequence (by reference).
610 *
611 * @return array BFB section.
612 */
613 function wpbc_simple_form__build_wizard_calendar_times_section( $calendar_field, $time_fields_map, &$field_seq, &$section_seq ) {
614
615 $columns = array();
616 $time_items = array();
617 $calendar_item = null;
618
619 // 1) Calendar in left column.
620 if ( ! empty( $calendar_field ) ) {
621 ++$field_seq;
622 $calendar_item = wpbc_simple_form__map_field_to_bfb_item( $calendar_field, $field_seq );
623 }
624
625 // 2) Time fields stacked in the right column: rangetime -> starttime -> endtime (if exist).
626 foreach ( array( 'rangetime', 'starttime', 'endtime' ) as $time_name ) {
627 if ( isset( $time_fields_map[ $time_name ] ) && is_array( $time_fields_map[ $time_name ] ) ) {
628 ++$field_seq;
629 $item = wpbc_simple_form__map_field_to_bfb_item( $time_fields_map[ $time_name ], $field_seq );
630 if ( ! empty( $item ) ) {
631 $time_items[] = $item;
632 }
633 }
634 }
635
636 // If we somehow have no calendar AND no time items, bail out.
637 if ( empty( $calendar_item ) && empty( $time_items ) ) {
638 return array();
639 }
640
641 // 3) Vertical divider in the middle column.
642 ++$field_seq;
643 $divider_id = 'divider-vertical-' . $field_seq;
644
645 $divider_item = array(
646 'type' => 'field',
647 'data' => array(
648 'type' => 'divider',
649 'usage_key' => 'divider_vertical',
650 'orientation' => 'vertical',
651 'line_style' => 'solid',
652 'thickness_px' => 1,
653 'length' => '90%',
654 'align' => 'middle',
655 'color' => '#ccc',
656 'label' => 'Divider_vertical',
657 'margin_top_px' => 0,
658 'margin_bottom_px'=> 0,
659 'margin_left_px' => 0,
660 'margin_right_px' => 0,
661 'cssclass_extra' => '',
662 'id' => $divider_id,
663 'name' => $divider_id,
664 'html_id' => '',
665 ),
666 );
667
668 // 4) Assemble columns:
669 // Left column ~ 41.1303%
670 // Middle column ~ 0.1% (divider)
671 // Right column ~ 52.7697%
672 if ( $calendar_item ) {
673 $columns[] = array(
674 'width' => '41.1303%',
675 'items' => array( $calendar_item ),
676 );
677 }
678
679 $columns[] = array(
680 'width' => '0.1%',
681 'items' => array( $divider_item ),
682 );
683
684 if ( ! empty( $time_items ) ) {
685 $columns[] = array(
686 'width' => '52.7697%',
687 'items' => $time_items,
688 );
689 }
690
691 // If somehow there are less than 2 real columns, fallback to equal widths.
692 if ( count( $columns ) < 2 ) {
693 $cols_count = count( $columns );
694 $col_width = ( $cols_count > 0 ) ? round( 100 / $cols_count, 4 ) . '%' : '100.0000%';
695 foreach ( $columns as &$c ) {
696 $c['width'] = $col_width;
697 }
698 unset( $c );
699 }
700
701 // 5) Section wrapper.
702 ++$section_seq;
703 $section_id = 'section-simple-wizard-' . $section_seq;
704
705 return array(
706 'type' => 'section',
707 'data' => array(
708 'id' => $section_id,
709 'label' => __( 'Section', 'booking' ),
710 'html_id' => '',
711 'cssclass' => '',
712 // Matches your example col_styles for calendar/time layout.
713 'col_styles'=> '[{"aself":"stretch"},{"dir":"row","wrap":"wrap","jc":"center","ai":"center","aself":"stretch"},{"aself":"stretch"}]',
714 'columns' => $columns,
715 ),
716 );
717 }
718
719 /**
720 * Wizard navigation section: horizontal divider + Back/Next/Submit buttons.
721 *
722 * Layout per step:
723 * - First step (no duration OR duration step): Divider + Next
724 * - Middle step (only in 3-step wizard): Divider + Back + Next
725 * - Last step: Divider + Back + Submit
726 *
727 * Matches the example like:
728 * [divider-horizontal] [wizard_nav_back] [submit]
729 *
730 * @param int $step_index Current wizard step (1-based).
731 * @param int $steps_count Total number of steps (2 or 3).
732 * @param bool $has_back Whether to include a Back button.
733 * @param bool $include_next Whether to include a Next button.
734 * @param array|null $submit_field Simple Form "submit" field (only for last step), or null.
735 * @param int $field_seq Field ID sequence (by reference).
736 * @param int $section_seq Section ID sequence (by reference).
737 *
738 * @return array BFB section.
739 */
740 function wpbc_simple_form__build_wizard_nav_section( $step_index, $steps_count, $has_back, $include_next, $submit_field, &$field_seq, &$section_seq ) {
741
742 $items = array();
743
744 // 1) Horizontal divider (always present).
745 ++$field_seq;
746 $divider_id = 'divider-vertical-he' . $field_seq;
747
748 $items[] = array(
749 'type' => 'field',
750 'data' => array(
751 'type' => 'divider',
752 'usage_key' => 'divider_vertical',
753 'orientation' => 'horizontal',
754 'line_style' => 'solid',
755 'thickness_px' => 1,
756 'length' => '100%',
757 'align' => 'center',
758 'color' => '#ccc',
759 'label' => 'Divider_vertical',
760 'margin_top_px' => 0,
761 'margin_bottom_px'=> 0,
762 'margin_left_px' => 0,
763 'margin_right_px' => 0,
764 'cssclass_extra' => '',
765 'id' => $divider_id,
766 'name' => $divider_id,
767 'html_id' => '',
768 ),
769 );
770
771 // 2) Back button (if required).
772 if ( $has_back ) {
773 ++$field_seq;
774 $back_id = 'wizard_nav_back-' . $step_index;
775
776 $items[] = array(
777 'type' => 'field',
778 'data' => array(
779 'id' => $back_id,
780 'type' => 'wizard_nav',
781 'usage_key' => 'wizard_nav_back',
782 'direction' => 'back',
783 'target_step' => max( 1, $step_index - 1 ),
784 'label' => __( 'Back', 'booking' ),
785 'name' => $back_id,
786 'cssclass_extra' => '',
787 'html_id' => '',
788 ),
789 );
790 }
791
792 // 3) Next button (for all non-final steps).
793 if ( $include_next && $step_index < $steps_count ) {
794 ++$field_seq;
795 $next_id = ( 1 === $step_index ) ? 'wizard_nav_next' : 'wizard_nav_next-' . $step_index;
796
797 $items[] = array(
798 'type' => 'field',
799 'data' => array(
800 'id' => $next_id,
801 'type' => 'wizard_nav',
802 'usage_key' => 'wizard_nav_next',
803 'direction' => 'next',
804 'target_step' => $step_index + 1,
805 'label' => __( 'Next', 'booking' ),
806 'name' => $next_id,
807 'cssclass_extra' => '',
808 'html_id' => '',
809 ),
810 );
811 }
812
813 // 4) Submit button only on final step (if submit field exists).
814 if ( ( $step_index === $steps_count ) && ! empty( $submit_field ) && is_array( $submit_field ) ) {
815 ++$field_seq;
816 $submit_item = wpbc_simple_form__map_field_to_bfb_item( $submit_field, $field_seq );
817 if ( ! empty( $submit_item ) ) {
818 $items[] = $submit_item;
819 }
820 }
821
822 // 5) Section wrapper – 1 column with all nav items, aligned to bottom-right.
823 ++$section_seq;
824 $section_id = 'section-wizard-nav-' . $section_seq;
825
826 return array(
827 'type' => 'section',
828 'data' => array(
829 'id' => $section_id,
830 'label' => __( 'Wizard navigation', 'booking' ),
831 'html_id' => '',
832 'cssclass' => '',
833 // Same col_styles pattern as your example.
834 'col_styles'=> '[{"dir":"row","wrap":"wrap","jc":"flex-end","ai":"flex-end","gap":"9px","aself":"flex-end"}]',
835 'columns' => array(
836 array(
837 'width' => '100%',
838 'items' => $items,
839 ),
840 ),
841 ),
842 );
843 }
844
845
846 // =====================================================================================================================
847 // == Map Fields ==
848 // =====================================================================================================================
849
850 /**
851 * Map one Simple Form field to a BFB "field" item.
852 *
853 * BFB item shape:
854 * [
855 * 'type' => 'field',
856 * 'data' => [
857 * 'id' => 'calendar',
858 * 'type' => 'calendar',
859 * 'usage_key' => 'calendar',
860 * 'label' => 'Select dates',
861 * 'name' => 'calendar',
862 * ... pack-specific keys ...
863 * ]
864 * ]
865 *
866 * @param array $form_field Normalized simple form field.
867 * @param int $seq Sequence number for unique IDs.
868 *
869 * @return array BFB item (type=field) or empty array on skip.
870 */
871 function wpbc_simple_form__map_field_to_bfb_item( $form_field, $seq ) {
872
873 $defaults = array(
874 'type' => 'text',
875 'name' => 'unique_name',
876 'label' => '',
877 'value' => '',
878 'placeholder' => '',
879 'cssclass' => '',
880 'html_id' => '',
881 'obligatory' => 'Off',
882 'if_exist_required' => 'Off',
883 'required' => 'Off',
884 'active' => 'On',
885 );
886 $form_field = wp_parse_args( $form_field, $defaults );
887
888 $type = $form_field['type'];
889 $name = $form_field['name'];
890 $label = $form_field['label'];
891
892 if ( '' === $label ) {
893 $label = ucfirst( str_replace( array( '_', '-' ), ' ', $name ) );
894 }
895
896 $pack = wpbc_simple_form__guess_bfb_pack( $form_field );
897
898 $required_flag = ( 'On' === $form_field['required'] ) ||
899 ( 'On' === $form_field['if_exist_required'] ) ||
900 ( 'On' === $form_field['obligatory'] );
901 $required = $required_flag ? 1 : 0;
902
903 // Choose a stable field ID for the Builder.
904 $field_id = $form_field['html_id'];
905 if ( '' === $field_id ) {
906 $field_id = $name;
907 }
908 if ( '' === $field_id ) {
909 $field_id = $pack . '-' . $seq;
910 }
911 $field_id = sanitize_key( $field_id );
912
913 $data = array(
914 'id' => $field_id,
915 'type' => $pack,
916 'usage_key' => $pack,
917 'label' => $label,
918 'name' => $name,
919 'html_id' => (string) $form_field['html_id'],
920 'cssclass' => (string) $form_field['cssclass'],
921 );
922
923 switch ( $pack ) {
924
925 case 'calendar':
926 // Minimal calendar config – BFB pack will handle advanced options.
927 $data['min_width'] = '250px';
928 $data['resource_id'] = 1; // If you know resource here, adjust.
929 $data['months'] = 1;
930 break;
931
932 case 'text':
933 case 'email':
934 $data['min_width'] = '8em';
935 $data['placeholder'] = isset( $form_field['placeholder'] ) ? (string) $form_field['placeholder'] : '';
936 $data['required'] = $required;
937 break;
938
939 case 'textarea':
940 $data['min_width'] = '240px';
941 $data['placeholder'] = isset( $form_field['placeholder'] ) ? (string) $form_field['placeholder'] : '';
942 $data['required'] = $required;
943 break;
944
945 case 'select':
946 $data['min_width'] = '100px';
947 $data['placeholder'] = '--- Select ---';
948
949 $data['options'] = array();
950 $opts = wpbc_simple_form__parse_select_options( $form_field );
951
952 foreach ( $opts as $opt ) {
953 $data['options'][] = array(
954 'label' => $opt['label'],
955 'value' => $opt['value'],
956 'selected' => false,
957 );
958 }
959
960 $data['required'] = $required;
961 break;
962
963 case 'checkbox':
964 /**
965 * SIMPLE FORM -> BFB: single checkbox
966 *
967 * Simple booking form has only one checkbox per field (not a group).
968 * We still provide ONE option with Title/Value, so the exporter can
969 * use it if needed, but we also set use_label_element=1 so it can
970 * generate:
971 *
972 * [checkbox name use_label_element "Label"]
973 *
974 * instead of group markup.
975 */
976 $data['min_width'] = '240px';
977 $data['use_label_element'] = 1;
978 $data['required'] = $required;
979 // Reset Label.
980 $data['label'] = '';
981
982 $data['options'] = array();
983
984 // Try to parse a Title/Value pair from "value" (same helper as select/radio).
985 $opts = wpbc_simple_form__parse_select_options( $form_field );
986
987 if ( ! empty( $opts ) ) {
988 // Use ONLY the first option, because Simple Form represents a single checkbox.
989 $first = $opts[0];
990
991 $data['options'][] = array(
992 'label' => $first['label'],
993 'value' => $first['value'],
994 'selected' => false,
995 );
996 } else {
997 // Fallback: use label as title and name as value.
998 $data['options'][] = array(
999 'label' => $label,
1000 'value' => $name,
1001 'selected' => false,
1002 );
1003 }
1004
1005 // IMPORTANT: do NOT set "multiple" or "layout_inline" here – this is a single checkbox.
1006 // IMPORTANT: do NOT set placeholder "--- Select ---" for a single checkbox.
1007 break;
1008
1009 case 'radio':
1010 $data['min_width'] = '240px';
1011 $data['options'] = array();
1012
1013 $opts = wpbc_simple_form__parse_select_options( $form_field );
1014 foreach ( $opts as $opt ) {
1015 $data['options'][] = array(
1016 'label' => $opt['label'],
1017 'value' => $opt['value'],
1018 'selected' => false,
1019 );
1020 }
1021
1022 $data['layout_inline'] = 1;
1023 $data['required'] = $required;
1024 break;
1025
1026 case 'submit':
1027 $data['label'] = $label ? $label : __( 'Send', 'booking' );
1028 $data['cssclass'] = 'wpbc_bfb__btn wpbc_bfb__btn--primary';
1029 break;
1030
1031 case 'rangetime':
1032 case 'starttime':
1033 case 'endtime':
1034 case 'durationtime':
1035 // Treat as time-based select for now; BFB time packs can enhance later.
1036 $data['min_width'] = '100px';
1037 $data['placeholder'] = '--- Select ---';
1038 $data['options'] = array();
1039
1040 $opts = wpbc_simple_form__parse_select_options( $form_field );
1041 foreach ( $opts as $opt ) {
1042 $data['options'][] = array(
1043 'label' => $opt['label'],
1044 'value' => $opt['value'],
1045 'selected' => false,
1046 );
1047 }
1048
1049 $data['required'] = $required;
1050 break;
1051
1052 case 'captcha':
1053 // Minimal – BFB "captcha" pack will use its own defaults.
1054 break;
1055
1056 default:
1057 // Fallback: just add required if meaningful.
1058 if ( $required ) {
1059 $data['required'] = $required;
1060 }
1061 break;
1062 }
1063
1064 if ( ! isset( $data['html_id'] ) ) {
1065 $data['html_id'] = '';
1066 }
1067 if ( ! isset( $data['cssclass'] ) ) {
1068 $data['cssclass'] = '';
1069 }
1070
1071 return array(
1072 'type' => 'field',
1073 'data' => $data,
1074 );
1075 }
1076
1077 // ---------------------------------------------------------------------------------------------------------------------
1078 // == Helpers ==
1079 // ---------------------------------------------------------------------------------------------------------------------
1080
1081 /**
1082 * Guess BFB pack name based on Simple Form type/name.
1083 *
1084 * @param array $form_field
1085 *
1086 * @return string
1087 */
1088 function wpbc_simple_form__guess_bfb_pack( $form_field ) {
1089
1090 $type = isset( $form_field['type'] ) ? $form_field['type'] : '';
1091 $name = isset( $form_field['name'] ) ? $form_field['name'] : '';
1092
1093 // Time-related packs.
1094 if ( in_array( $name, array( 'rangetime', 'durationtime', 'starttime', 'endtime' ), true ) ) {
1095 return $name; // 'rangetime', 'durationtime', 'starttime', 'endtime' packs.
1096 }
1097
1098 // Special system fields.
1099 if ( 'calendar' === $type || 'calendar' === $name ) {
1100 return 'calendar';
1101 }
1102 if ( 'captcha' === $type || 'captcha' === $name ) {
1103 return 'captcha';
1104 }
1105 if ( 'submit' === $type || 'submit' === $name ) {
1106 return 'submit';
1107 }
1108
1109 // Common fields.
1110 if ( 'text' === $type ) {
1111 return 'text';
1112 }
1113 if ( 'email' === $type ) {
1114 return 'email';
1115 }
1116 if ( 'textarea' === $type ) {
1117 return 'textarea';
1118 }
1119 if ( in_array( $type, array( 'select', 'selectbox' ), true ) ) {
1120 return 'select';
1121 }
1122 if ( 'checkbox' === $type ) {
1123 return 'checkbox';
1124 }
1125 if ( 'radio' === $type ) {
1126 return 'radio';
1127 }
1128
1129 // Fallback.
1130 return $type ? $type : 'text';
1131 }
1132
1133 /**
1134 * Parse selectbox/radio/checkbox values from Simple Form into structured options.
1135 *
1136 * Supports simple lines:
1137 * Label
1138 * …and paired format:
1139 * Label@@value
1140 *
1141 * @param array $form_field
1142 *
1143 * @return array
1144 */
1145 function wpbc_simple_form__parse_select_options( $form_field ) {
1146
1147 $options = array();
1148 $raw_value = isset( $form_field['value'] ) ? (string) $form_field['value'] : '';
1149 $lines = preg_split( '/\r\n|\r|\n/', $raw_value );
1150
1151 if ( empty( $lines ) ) {
1152 return $options;
1153 }
1154
1155 foreach ( $lines as $line ) {
1156 $line = trim( $line );
1157 if ( '' === $line ) {
1158 continue;
1159 }
1160
1161 $label = $line;
1162 $value = $line;
1163
1164 if ( false !== strpos( $line, '@@' ) ) {
1165 $parts = explode( '@@', $line, 2 );
1166 $label = trim( $parts[0] );
1167 $value = trim( $parts[1] );
1168 }
1169
1170 // Strip quotes for safety.
1171 $label = str_replace( array( '"', "'" ), '', $label );
1172 $value = str_replace( array( '"', "'" ), '', $value );
1173
1174 $options[] = array(
1175 'label' => $label,
1176 'value' => $value,
1177 );
1178 }
1179
1180 return $options;
1181 }
1182
1183
1184 // =====================================================================================================================
1185 // == Ajax Handler of "Import from Simple Form" button ==
1186 // =====================================================================================================================
1187
1188 /**
1189 * AJAX: import Simple Form -> BFB structure.
1190 */
1191 function wpbc_bfb_ajax_import_simple_form() {
1192 check_ajax_referer( 'wpbc_bfb_import_simple_form', 'nonce' );
1193
1194 $structure = wpbc_simple_form__export_to_bfb_structure();
1195
1196 if ( empty( $structure ) ) {
1197 wp_send_json_error( array( 'message' => 'Empty structure.' ) );
1198 }
1199
1200 wp_send_json_success(
1201 array(
1202 'structure' => $structure,
1203 )
1204 );
1205 }
1206 add_action( 'wp_ajax_wpbc_bfb_import_simple_form', 'wpbc_bfb_ajax_import_simple_form' );
1207