PluginProbe
Booking Calendar / 11.5
Booking Calendar v11.5
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 / save-load / bfb-activate.php

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

1,250 lines 37.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Form Builder - Activation helpers.
4 *
5 * Responsibilities:
6 * 1) Create BFB DB table (wp_booking_form_structures) on activation (if missing).
7 * 2) Ensure the "Booking Form Preview" page exists during activation.
8 *
9 * @package Booking Calendar.
10 * @author wpdevelop, oplugins
11 * @web-site https://wpbookingcalendar.com/
12 * @email info@wpbookingcalendar.com
13 *
14 * @since 11.0.x
15 * @file ../includes/page-form-builder/save-load/bfb-activate.php
16 * @modified 2026-02-23
17 */
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // Exit if accessed directly.
21 }
22
23 /**
24 * Return option key where preview page ID is stored.
25 *
26 * Must match WPBC_BFB_Preview_Service::get_page_option_key().
27 *
28 * @return string
29 */
30 function wpbc_bfb_activation__get_preview_page_option_key() {
31 return 'wpbc_bfb_preview_page_id';
32 }
33
34 /**
35 * Return meta key used to mark the preview page (for recovery).
36 *
37 * @return string
38 */
39 function wpbc_bfb_activation__get_preview_page_meta_key() {
40 return '_wpbc_bfb_preview_page';
41 }
42
43 /**
44 * Return canonical slug for the preview page.
45 *
46 * @return string
47 */
48 function wpbc_bfb_activation__get_preview_page_slug() {
49 return 'wpbc-bfb-preview';
50 }
51
52 /**
53 * Try to find an administrator user ID (best-effort).
54 *
55 * @return int Admin user ID, or 0 if not found.
56 */
57 function wpbc_bfb_activation__get_any_admin_user_id() {
58
59 $users = get_users( array(
60 'role' => 'administrator',
61 'number' => 1,
62 'fields' => array( 'ID' ),
63 ) );
64
65 if ( empty( $users ) ) {
66 return 0;
67 }
68
69 if ( is_object( $users[0] ) && isset( $users[0]->ID ) ) {
70 return (int) $users[0]->ID;
71 }
72
73 if ( is_array( $users[0] ) && isset( $users[0]['ID'] ) ) {
74 return (int) $users[0]['ID'];
75 }
76
77 return 0;
78 }
79
80 /**
81 * Temporarily switch current user to an admin user if the current user
82 * cannot create private pages (activation edge-cases like WP-CLI).
83 *
84 * @return int Previous user ID (0 if none). Caller should restore with wp_set_current_user().
85 */
86 function wpbc_bfb_activation__maybe_switch_to_admin_user() {
87
88 $prev_user_id = (int) get_current_user_id();
89
90 // If current user can create pages, do nothing.
91 if ( $prev_user_id > 0 && user_can( $prev_user_id, 'publish_pages' ) ) {
92 return $prev_user_id;
93 }
94
95 $admin_id = wpbc_bfb_activation__get_any_admin_user_id();
96 if ( $admin_id > 0 ) {
97 wp_set_current_user( $admin_id );
98 }
99
100 return $prev_user_id;
101 }
102
103 /**
104 * Restore previous user after temporary switch.
105 *
106 * @param int $prev_user_id Previous user ID.
107 *
108 * @return void
109 */
110 function wpbc_bfb_activation__restore_user( $prev_user_id ) {
111
112 $prev_user_id = (int) $prev_user_id;
113
114 // Restore to previous user if we had one; otherwise, reset to 0.
115 if ( $prev_user_id > 0 ) {
116 wp_set_current_user( $prev_user_id );
117 } else {
118 wp_set_current_user( 0 );
119 }
120 }
121
122 /**
123 * Validate that a page ID is a real WP page.
124 *
125 * @param int $page_id Page ID.
126 *
127 * @return bool
128 */
129 function wpbc_bfb_activation__is_valid_page_id( $page_id ) {
130
131 $page_id = (int) $page_id;
132
133 if ( $page_id <= 0 ) {
134 return false;
135 }
136
137 $page = get_post( $page_id );
138
139 return ( $page instanceof WP_Post && 'page' === $page->post_type );
140 }
141
142 /**
143 * Try to locate an existing preview page by meta marker.
144 *
145 * @return int Page ID or 0.
146 */
147 function wpbc_bfb_activation__find_preview_page_by_meta() {
148
149 $meta_key = wpbc_bfb_activation__get_preview_page_meta_key();
150
151 $q = new WP_Query( array(
152 'post_type' => 'page',
153 'post_status' => array( 'private', 'publish', 'draft', 'pending' ),
154 'posts_per_page' => 1,
155 'fields' => 'ids',
156 'no_found_rows' => true,
157 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
158 'meta_key' => $meta_key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
159 'meta_value' => '1', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
160 ) );
161
162 if ( empty( $q->posts ) ) {
163 return 0;
164 }
165
166 return (int) $q->posts[0];
167 }
168
169 /**
170 * Try to locate an existing preview page by slug.
171 *
172 * @return int Page ID or 0.
173 */
174 function wpbc_bfb_activation__find_preview_page_by_slug() {
175
176 $slug = wpbc_bfb_activation__get_preview_page_slug();
177
178 $page = get_page_by_path( $slug, OBJECT, 'page' );
179 if ( $page instanceof WP_Post ) {
180 return (int) $page->ID;
181 }
182
183 return 0;
184 }
185
186
187 /**
188 * Force a safe template for the BFB Preview page.
189 *
190 * Why:
191 * - The Preview page is a technical/service page.
192 * - It should not inherit decorative page templates like "Page with wide Image".
193 * - For block themes, prefer a clean template like "page-no-title" or "page".
194 * - For classic themes, use the default page template. // FixIn: 10.15.2.1
195 *
196 * @param int $page_id Page ID.
197 *
198 * @return bool
199 */
200 function wpbc_bfb_preview__force_safe_template( $page_id ) {
201
202 $page_id = absint( $page_id );
203
204 if ( $page_id <= 0 ) {
205 return false;
206 }
207
208 /*
209 * Always clear any previously assigned template first.
210 * This is important when an existing Preview page was previously using
211 * an unwanted template, for example "Page with wide Image".
212 */
213 delete_post_meta( $page_id, '_wp_page_template' );
214
215 // Block themes: prefer a cleaner built-in page template, if available.
216 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() && function_exists( 'get_block_templates' ) ) {
217
218 $theme_slug = get_stylesheet();
219 $templates = get_block_templates(
220 array(
221 'post_type' => 'page',
222 ),
223 'wp_template'
224 );
225 $safe_template_slug = '';
226
227 foreach ( $templates as $template ) {
228
229 if ( empty( $template->slug ) || empty( $template->theme ) ) {
230 continue;
231 }
232
233 if ( $theme_slug !== $template->theme ) {
234 continue;
235 }
236
237 if ( 'page-no-title' === $template->slug ) {
238 $safe_template_slug = 'page-no-title';
239 break;
240 }
241
242 if ( ( '' === $safe_template_slug ) && ( 'page' === $template->slug ) ) {
243 $safe_template_slug = 'page';
244 }
245 }
246
247 if ( '' !== $safe_template_slug ) {
248 $result = wp_update_post(
249 array(
250 'ID' => $page_id,
251 'page_template' => $safe_template_slug,
252 ),
253 true
254 );
255
256 return ( ! is_wp_error( $result ) );
257 }
258 }
259
260 return true;
261 }
262
263
264 /**
265 * The preview page has status 'publish', because the secure the output by your nonce + cap
266 *
267 * Preview security is already enforced by:
268 *
269 * wpbc_bfb_preview=1 + token + nonce
270 *
271 * is_user_logged_in() + current_user_can( wpbc_bfb_get_manage_cap() )
272 *
273 * So the preview page itself does not need to be private.
274 *
275 * @return false|int
276 */
277 function wpbc_bfb_activation__create_preview_page_raw() {
278
279 /* Reuse an existing preview page on demos, but never auto-create one. */
280 if ( function_exists( 'wpbc_is_this_demo' ) && wpbc_is_this_demo() ) {
281 return false;
282 }
283
284 $page_data = array(
285 'post_title' => __( 'Booking Form Preview', 'booking' ),
286 'post_content' => '', // Content will be injected dynamically at preview time by bfb-preview.php.
287 'post_status' => 'publish',
288 'post_type' => 'page',
289 'post_name' => wpbc_bfb_activation__get_preview_page_slug(),
290 'comment_status' => 'closed',
291 'ping_status' => 'closed',
292 );
293
294 $page_id = wp_insert_post( $page_data, true );
295
296 if ( ( ! is_wp_error( $page_id ) ) && ( ! empty( $page_id ) ) ) {
297 // Success.
298 wpbc_bfb_preview__force_safe_template( $page_id );
299 } else {
300 if ( is_wp_error( $page_id ) ) {
301 return false;
302 }
303 if ( ! $page_id ) {
304 return false;
305 }
306 $page_id = false;
307 }
308
309 return (int) $page_id;
310 }
311
312
313 /**
314 * Create the "Booking Form Preview" page and store option.
315 *
316 * @return int|false Page ID on success, false on failure.
317 */
318 function wpbc_bfb_activation__create_preview_page() {
319
320 $prev_user_id = wpbc_bfb_activation__maybe_switch_to_admin_user();
321
322 $page_id = wpbc_bfb_activation__create_preview_page_raw();
323
324 // Restore user context.
325 wpbc_bfb_activation__restore_user( $prev_user_id );
326
327 if ( empty( $page_id ) || is_wp_error( $page_id ) ) {
328 return false;
329 }
330
331 $page_id = (int) $page_id;
332
333 // Mark page for recovery if option is deleted later.
334 add_post_meta( $page_id, wpbc_bfb_activation__get_preview_page_meta_key(), '1', true );
335
336 // Store the page ID in option (same key used by preview service).
337 update_option( wpbc_bfb_activation__get_preview_page_option_key(), $page_id );
338
339 return $page_id;
340 }
341
342 /**
343 * Ensure preview page exists (activation-time).
344 *
345 * - Validates stored option page ID.
346 * - If missing, tries to find by meta marker.
347 * - If missing, tries to find by slug.
348 * - If still missing, creates a new private page.
349 *
350 * @return int|false Page ID, or false on failure.
351 */
352 function wpbc_bfb_activation__ensure_preview_page_exists() {
353
354 $option_key = wpbc_bfb_activation__get_preview_page_option_key();
355 $page_id = (int) get_option( $option_key );
356
357 // 1) Option points to a valid page.
358 if ( wpbc_bfb_activation__is_valid_page_id( $page_id ) ) {
359
360 // Enforce publish status (best effort).
361 $page = get_post( $page_id );
362 if ( $page instanceof WP_Post && 'publish' !== $page->post_status ) {
363 wp_update_post( array(
364 'ID' => (int) $page_id,
365 'post_status' => 'publish',
366 ) );
367 }
368
369 // Ensure meta marker exists.
370 wpbc_bfb_preview__force_safe_template( (int) $page_id );
371 add_post_meta( (int) $page_id, wpbc_bfb_activation__get_preview_page_meta_key(), '1', true );
372
373 return (int) $page_id;
374 }
375
376 // 2) Try recovery by meta marker.
377 $found_id = wpbc_bfb_activation__find_preview_page_by_meta();
378 if ( $found_id > 0 && wpbc_bfb_activation__is_valid_page_id( $found_id ) ) {
379 $found_page = get_post( $found_id );
380 if ( $found_page instanceof WP_Post && 'publish' !== $found_page->post_status ) {
381 wp_update_post( array(
382 'ID' => (int) $found_id,
383 'post_status' => 'publish',
384 ) );
385 }
386 wpbc_bfb_preview__force_safe_template( (int) $found_id );
387 update_option( $option_key, (int) $found_id );
388 return (int) $found_id;
389 }
390
391 // 3) Try recovery by slug.
392 $found_id = wpbc_bfb_activation__find_preview_page_by_slug();
393 if ( $found_id > 0 && wpbc_bfb_activation__is_valid_page_id( $found_id ) ) {
394 $found_page = get_post( $found_id );
395 if ( $found_page instanceof WP_Post && 'publish' !== $found_page->post_status ) {
396 wp_update_post( array(
397 'ID' => (int) $found_id,
398 'post_status' => 'publish',
399 ) );
400 }
401 wpbc_bfb_preview__force_safe_template( (int) $found_id );
402 // Mark it as ours (so next time meta lookup works).
403 add_post_meta( (int) $found_id, wpbc_bfb_activation__get_preview_page_meta_key(), '1', true );
404 update_option( $option_key, (int) $found_id );
405 return (int) $found_id;
406 }
407
408 // 4) Create.
409 return wpbc_bfb_activation__create_preview_page();
410 }
411
412 /**
413 * Activation hook entry: ensure the preview page exists.
414 *
415 * @return void
416 */
417 function wpbc_bfb_activation__preview_page() {
418 wpbc_bfb_activation__ensure_preview_page_exists();
419 }
420 add_bk_action( 'wpbc_free_version_activation', 'wpbc_bfb_activation__preview_page' );
421
422
423 /**
424 * A c t i v a t e - Create new DB Table for Booking Form Builder structures.
425 *
426 * This table stores:
427 * - Canonical form builder structure as JSON (structure_json).
428 * - Additional settings as JSON (settings_json).
429 * - Optional precompiled HTML can be added later (not in this schema).
430 * - Ownership via owner_user_id (for MultiUser).
431 *
432 * @return void
433 */
434 function wpbc_bfb_activation__form_structures_table() {
435
436 global $wpdb;
437
438 $charset_collate = ( ! empty( $wpdb->charset ) ) ? 'DEFAULT CHARACTER SET ' . $wpdb->charset : '';
439 $charset_collate .= ( ! empty( $wpdb->collate ) ) ? ' COLLATE ' . $wpdb->collate : '';
440
441 if ( ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
442
443
444
445 $simple_sql = "CREATE TABLE {$wpdb->prefix}booking_form_structures (
446 booking_form_id bigint(20) unsigned NOT NULL auto_increment,
447 form_slug VARCHAR(191) NOT NULL,
448 status VARCHAR(32) NOT NULL,
449 owner_user_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
450
451 scope varchar(64) NOT NULL default 'global',
452 version int(11) NOT NULL default 1,
453 booking_resource_id BIGINT UNSIGNED NULL,
454
455 engine varchar(32) NOT NULL default 'bfb',
456 engine_version varchar(16) NOT NULL default '1.0',
457 structure_json LONGTEXT NOT NULL,
458 settings_json LONGTEXT NULL,
459 advanced_form LONGTEXT NULL,
460 content_form LONGTEXT NULL,
461
462 is_default TINYINT(1) NOT NULL DEFAULT 0,
463 title VARCHAR(255) NOT NULL,
464 description TEXT NULL,
465 picture_url text NULL,
466
467 created_at DATETIME NOT NULL,
468 updated_at DATETIME NOT NULL,
469
470 created_by bigint(20) unsigned DEFAULT NULL,
471 updated_by bigint(20) unsigned DEFAULT NULL,
472 PRIMARY KEY (booking_form_id),
473 UNIQUE KEY form_slug_status (form_slug, status, owner_user_id)
474 ) {$charset_collate}";
475
476 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
477 $wpdb->query( $simple_sql );
478
479 do_action( 'wpbc_bfb_activation__form_structures_table__after_create' );
480
481 wpbc_bfb__import_old_booking_forms();
482 } else {
483 do_action( 'wpbc_bfb_activation__form_structures_table__table_already_exists' );
484 }
485 }
486 add_bk_action( 'wpbc_free_version_activation', 'wpbc_bfb_activation__form_structures_table' );
487
488
489 /**
490 * D e a c t i v a t e - Drop Booking Form Builder structures table.
491 *
492 * @return void
493 */
494 function wpbc_bfb_deactivation__form_structures_table() {
495
496 global $wpdb;
497
498
499
500 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
501 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}booking_form_structures" );
502
503 delete_bk_option( 'wpbc_bfb_legacy_import_done_for_pro' );
504 }
505 add_bk_action( 'wpbc_free_version_deactivation', 'wpbc_bfb_deactivation__form_structures_table' );
506
507
508 /**
509 * Import legacy "standard" booking form as initial BFB structure.
510 *
511 * @return void
512 */
513 function wpbc_bfb_is_brand_new_install__before_version_update() {
514
515 $stored_version = get_option( 'booking_version_num', false );
516
517 return ( false === $stored_version );
518 }
519
520 /**
521 * Let the initial standard BFB form follow the global calendar days-selection setting.
522 *
523 * Some bundled templates intentionally override days selection for their own use case.
524 * The installed "standard" form should not carry that template-level override, because
525 * the global Settings > Calendar page is the canonical default for a new site.
526 *
527 * @param array $template_record Form Builder template record.
528 *
529 * @return array
530 */
531 function wpbc_bfb_activation__set_standard_form_default_days_selection( $template_record ) {
532
533 if ( empty( $template_record ) || ! is_array( $template_record ) ) {
534 return $template_record;
535 }
536
537 $settings = array();
538 if ( ! empty( $template_record['settings_json'] ) ) {
539 $decoded = json_decode( (string) $template_record['settings_json'], true );
540 if ( is_array( $decoded ) ) {
541 $settings = $decoded;
542 }
543 }
544
545 if ( empty( $settings['options'] ) || ! is_array( $settings['options'] ) ) {
546 $settings['options'] = array();
547 }
548
549 $settings['options']['booking_type_of_day_selections'] = '';
550
551 $template_record['settings_json'] = function_exists( 'wpbc_form_config__encode_json' )
552 ? wpbc_form_config__encode_json( $settings )
553 : wp_json_encode( $settings );
554
555 return $template_record;
556 }
557
558 /**
559 * Maybe create the initial standard BFB form from a bundled template.
560 *
561 * Used only during brand new plugin activation, before booking_version_num
562 * gets updated. Upgrades keep migrating the existing legacy configuration.
563 *
564 * @return bool
565 */
566 function wpbc_bfb_maybe_create_initial_standard_form_from_template() {
567
568 if ( ! wpbc_bfb_is_brand_new_install__before_version_update() ) {
569 return false;
570 }
571
572 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
573 return false;
574 }
575
576 if ( function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
577 // Default Booking Form Template - Just after Booking Calendar activation!
578 // $template_record = wpbc_get_bfb_template_record_by_key( 'time_appointments_3_steps_review_with_hints' ); // Time appointments.
579 $template_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' ); // Full Days - 2 Columns - with dates hints.
580 } else {
581 $template_record = array();
582 }
583
584 if ( empty( $template_record ) || ! is_array( $template_record ) ) {
585 return false;
586 }
587
588 $template_record = wpbc_bfb_activation__set_standard_form_default_days_selection( $template_record );
589
590 $template_record['form_slug'] = 'standard';
591 $template_record['status'] = 'published';
592 $template_record['scope'] = 'global';
593 $template_record['owner_user_id'] = 0;
594 $template_record['booking_resource_id'] = null;
595 $template_record['is_default'] = 1;
596 $template_record['title'] = __( 'Standard', 'booking' );
597 $template_record['description'] = '';
598 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
599 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
600 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
601 }
602
603 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
604
605 return ! empty( $booking_form_id );
606 }
607
608
609 /**
610 * Get the dedicated bundled template for the current live-demo edition.
611 *
612 * @param int $owner_user_id Optional MultiUser owner ID.
613 *
614 * @return string Template key, or an empty string outside a supported demo.
615 */
616 function wpbc_bfb_live_demo__get_template_key( $owner_user_id = 0 ) {
617
618 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
619 return '';
620 }
621
622 $owner_user_id = absint( $owner_user_id );
623
624 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
625 return ( $owner_user_id > 0 ) ? 'demo_multiuser_owner_1' : 'demo_multiuser';
626 }
627 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
628 return 'demo_business_large';
629 }
630 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
631 return 'demo_business_medium';
632 }
633 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
634 return 'demo_business_small';
635 }
636 if ( class_exists( 'wpdev_bk_personal' ) ) {
637 return 'demo_personal';
638 }
639
640 return '';
641 }
642
643
644 /**
645 * Get the global Form Style used by the current live-demo edition.
646 *
647 * @return string Form Style preset key.
648 */
649 function wpbc_bfb_live_demo__get_form_style() {
650
651 if ( class_exists( 'wpdev_bk_multiuser' ) || class_exists( 'wpdev_bk_biz_l' ) ) {
652 return 'light_bordered';
653 }
654 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
655 return 'light_soft';
656 }
657 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
658 return 'light_bordered';
659 }
660 if ( class_exists( 'wpdev_bk_personal' ) ) {
661 return 'light_none';
662 }
663
664 return 'light_bordered';
665 }
666
667
668 /**
669 * Provision a live-demo standard form from a real bundled BFB template.
670 *
671 * Normal owner activation is idempotent and preserves an existing form. The
672 * explicit demo reset path may request replacement to restore fixture data.
673 *
674 * @param int $owner_user_id Owner ID, or 0 for the global/Super Admin form.
675 * @param string $template_key Optional explicit bundled template key.
676 * @param bool $replace Whether to replace an existing standard form.
677 *
678 * @return int|false Booking form ID on success, false on failure.
679 */
680 function wpbc_bfb_live_demo__provision_standard_form( $owner_user_id = 0, $template_key = '', $replace = false ) {
681
682 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
683 return false;
684 }
685 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
686 return false;
687 }
688 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
689 return false;
690 }
691
692 $owner_user_id = absint( $owner_user_id );
693 $template_key = sanitize_key( (string) $template_key );
694 if ( '' === $template_key ) {
695 $template_key = wpbc_bfb_live_demo__get_template_key( $owner_user_id );
696 }
697 if ( '' === $template_key ) {
698 return false;
699 }
700
701 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
702 if ( ! empty( $existing->booking_form_id ) && ! $replace ) {
703 return (int) $existing->booking_form_id;
704 }
705
706 $template_record = function_exists( 'wpbc_get_bfb_template_record_by_key' )
707 ? wpbc_get_bfb_template_record_by_key( $template_key )
708 : array();
709
710 if (
711 empty( $template_record ) ||
712 ! is_array( $template_record ) ||
713 empty( $template_record['structure_json'] ) ||
714 empty( $template_record['engine'] ) ||
715 'bfb' !== (string) $template_record['engine']
716 ) {
717 return false;
718 }
719
720 $template_record['form_slug'] = 'standard';
721 $template_record['status'] = 'published';
722 $template_record['scope'] = ( $owner_user_id > 0 ) ? 'user' : 'global';
723 $template_record['owner_user_id'] = $owner_user_id;
724 $template_record['booking_resource_id'] = null;
725 $template_record['is_default'] = 1;
726 $template_record['title'] = __( 'Standard', 'booking' );
727 $template_record['description'] = '';
728 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
729
730 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
731 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
732 }
733
734 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
735
736 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
737 }
738
739
740 /**
741 * Return regular-owner fixtures used by the MultiUser live demo.
742 *
743 * @return array Owner user ID => bundled template key.
744 */
745 function wpbc_bfb_live_demo__get_multiuser_owner_template_map() {
746
747 $owner_templates = array(
748 3 => 'demo_multiuser_owner_1',
749 4 => 'demo_multiuser_owner_2',
750 5 => 'demo_multiuser_owner_3',
751 );
752
753 /**
754 * Allow a private demo installation to use different owner IDs while
755 * retaining the same centralized provisioning flow.
756 *
757 * @param array $owner_templates Owner user ID => template key.
758 */
759 $owner_templates = apply_filters( 'wpbc_bfb_live_demo_multiuser_owner_templates', $owner_templates );
760
761 return is_array( $owner_templates ) ? $owner_templates : array();
762 }
763
764
765 /**
766 * Apply all standard-form fixtures required by the active live demo.
767 *
768 * This is the canonical final activation step. It intentionally replaces demo
769 * fixtures, but never runs on customer installations.
770 *
771 * @return bool True when every applicable fixture was saved.
772 */
773 function wpbc_bfb_live_demo__provision_all_standard_forms() {
774
775 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
776 return false;
777 }
778
779 // Pro-only reactivation does not necessarily rerun the Free activation
780 // callback that normally synchronizes bundled template rows.
781 if ( function_exists( 'wpbc_bfb_activation__seed_templates' ) ) {
782 wpbc_bfb_activation__seed_templates();
783 }
784
785 $is_success = ( false !== wpbc_bfb_live_demo__provision_standard_form( 0, '', true ) );
786
787 // Form Style is a site-wide setting, not part of an individual form row.
788 // Write directly to the global option so the result is deterministic even
789 // when finalization runs from a regular MultiUser administrator session.
790 update_option( 'booking_form_style', wpbc_bfb_live_demo__get_form_style() );
791
792 // Live demos intentionally showcase the shared accent experience. Customer
793 // installations keep the backward-compatible disabled default.
794 $form_accent_defaults = function_exists( 'wpbc_bfb_settings__get_default_form_accent_options' )
795 ? wpbc_bfb_settings__get_default_form_accent_options()
796 : array( 'booking_form_accent_color' => WPBC_DEFAULT_FORM_ACCENT_COLOR );
797 update_option( 'booking_form_accent_enabled', 'On' );
798 update_option( 'booking_form_accent_color', $form_accent_defaults['booking_form_accent_color'] );
799
800 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
801 foreach ( wpbc_bfb_live_demo__get_multiuser_owner_template_map() as $owner_user_id => $template_key ) {
802 $owner_user_id = absint( $owner_user_id );
803 $template_key = sanitize_key( (string) $template_key );
804 if ( $owner_user_id <= 0 || '' === $template_key ) {
805 $is_success = false;
806 continue;
807 }
808
809 if ( function_exists( 'get_userdata' ) && ! get_userdata( $owner_user_id ) ) {
810 $is_success = false;
811 continue;
812 }
813
814 if ( false === wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, $template_key, true ) ) {
815 $is_success = false;
816 }
817 }
818 }
819
820 return $is_success;
821 }
822
823
824 /**
825 * Ensure that a regular MultiUser owner has an independent standard BFB form.
826 *
827 * Existing owner forms are never overwritten. On normal installations the
828 * current global standard form is cloned, preserving the Super Admin's chosen
829 * Builder structure. Live demos use their dedicated bundled fixture instead.
830 *
831 * @param int $owner_user_id Regular MultiUser owner ID.
832 *
833 * @return int|false Booking form ID on success, false on failure.
834 */
835 function wpbc_bfb_ensure_standard_form_for_owner( $owner_user_id ) {
836
837 $owner_user_id = absint( $owner_user_id );
838 if ( $owner_user_id <= 0 || ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
839 return false;
840 }
841 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
842 return false;
843 }
844
845 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
846 if ( ! empty( $existing->booking_form_id ) ) {
847 return (int) $existing->booking_form_id;
848 }
849
850 if ( function_exists( 'wpbc_is_this_demo' ) && wpbc_is_this_demo() ) {
851 return wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, '', false );
852 }
853
854 $global_standard = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', 0, 'published' );
855 $form_record = ! empty( $global_standard ) ? get_object_vars( $global_standard ) : array();
856
857 if ( empty( $form_record['structure_json'] ) && function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
858 $form_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' );
859 }
860 if (
861 empty( $form_record ) ||
862 ! is_array( $form_record ) ||
863 empty( $form_record['structure_json'] ) ||
864 empty( $form_record['engine'] ) ||
865 'bfb' !== (string) $form_record['engine']
866 ) {
867 return false;
868 }
869
870 unset(
871 $form_record['booking_form_id'],
872 $form_record['created_at'],
873 $form_record['created_by'],
874 $form_record['updated_at'],
875 $form_record['updated_by']
876 );
877
878 $form_record['form_slug'] = 'standard';
879 $form_record['status'] = 'published';
880 $form_record['scope'] = 'user';
881 $form_record['owner_user_id'] = $owner_user_id;
882 $form_record['booking_resource_id'] = null;
883 $form_record['is_default'] = 1;
884 $form_record['title'] = __( 'Standard', 'booking' );
885 $form_record['description'] = '';
886
887 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $form_record );
888
889 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
890 }
891
892 /**
893 * Import legacy "standard" booking form as initial BFB structure.
894 *
895 * @return void
896 */
897 function wpbc_bfb__import_old_booking_forms() {
898
899 if ( wpbc_bfb_maybe_create_initial_standard_form_from_template() ) {
900 return;
901 }
902
903 if ( function_exists( 'wpbc_bfb_import_legacy_forms' ) ) {
904 wpbc_bfb_import_legacy_forms(
905 array(
906 'mode' => 'all_global',
907 'import_standard' => true,
908 'import_custom' => true,
909 'skip_if_exists' => true,
910 'set_bfb_form_not_defined' => false,
911 )
912 );
913 }
914 }
915
916
917 /**
918 * Remove BFB Preview page from rendered nav menus.
919 *
920 * @param array $sorted_menu_items Menu item objects.
921 * @param stdClass $args Menu args.
922 *
923 * @return array
924 */
925 function wpbc_bfb_preview__exclude_from_nav_menus( $sorted_menu_items, $args ) {
926
927 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
928
929 if ( $page_id <= 0 ) {
930 return $sorted_menu_items;
931 }
932
933 $filtered_items = array();
934
935 foreach ( $sorted_menu_items as $menu_item ) {
936
937 $object_id = isset( $menu_item->object_id ) ? absint( $menu_item->object_id ) : 0;
938 $object = isset( $menu_item->object ) ? (string) $menu_item->object : '';
939
940 if ( ( 'page' === $object ) && ( $page_id === $object_id ) ) {
941 continue;
942 }
943
944 $filtered_items[] = $menu_item;
945 }
946
947 return $filtered_items;
948 }
949 add_filter( 'wp_nav_menu_objects', 'wpbc_bfb_preview__exclude_from_nav_menus', 10, 2 );
950
951
952 /**
953 * Exclude BFB Preview page from wp_list_pages() outputs.
954 *
955 * @param array $exclude_array Excluded page IDs.
956 *
957 * @return array
958 */
959 function wpbc_bfb_preview__exclude_from_page_lists( $exclude_array ) {
960
961 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
962
963 if ( $page_id > 0 ) {
964 $exclude_array[] = $page_id;
965 }
966
967 return array_unique( array_map( 'absint', $exclude_array ) );
968 }
969 add_filter( 'wp_list_pages_excludes', 'wpbc_bfb_preview__exclude_from_page_lists' );
970
971
972 /**
973 * Remove BFB Preview page from get_pages() results.
974 *
975 * This is important for block themes, because the core Page List block
976 * renders its items from get_pages().
977 *
978 * @param array $pages Array of page objects.
979 * @param array $parsed_args Parsed get_pages() args.
980 *
981 * @return array
982 */
983 function wpbc_bfb_preview__exclude_from_get_pages( $pages, $parsed_args ) {
984
985 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
986
987 if ( $page_id <= 0 ) {
988 return $pages;
989 }
990
991 $filtered_pages = array();
992
993 foreach ( $pages as $page_obj ) {
994
995 if ( empty( $page_obj ) || empty( $page_obj->ID ) ) {
996 $filtered_pages[] = $page_obj;
997 continue;
998 }
999
1000 if ( absint( $page_obj->ID ) === $page_id ) {
1001 continue;
1002 }
1003
1004 $filtered_pages[] = $page_obj;
1005 }
1006
1007 return $filtered_pages;
1008 }
1009 add_filter( 'get_pages', 'wpbc_bfb_preview__exclude_from_get_pages', 10, 2 );
1010
1011
1012 /**
1013 * Remove BFB Preview page from rendered Navigation Link blocks.
1014 *
1015 * This handles block-theme menus where the Preview page was manually
1016 * inserted as a Navigation Link item.
1017 *
1018 * @param string $block_content Rendered block HTML.
1019 * @param array $block Parsed block data.
1020 * @param WP_Block $instance Block instance.
1021 *
1022 * @return string
1023 */
1024 function wpbc_bfb_preview__exclude_from_navigation_link_block( $block_content, $block, $instance ) {
1025
1026 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1027
1028 if ( $page_id <= 0 ) {
1029 return $block_content;
1030 }
1031
1032 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1033 return $block_content;
1034 }
1035
1036 $attrs = $block['attrs'];
1037 $preview_url = get_permalink( $page_id );
1038
1039 $block_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1040 $block_type = isset( $attrs['type'] ) ? (string) $attrs['type'] : '';
1041 $block_kind = isset( $attrs['kind'] ) ? (string) $attrs['kind'] : '';
1042 $block_url = isset( $attrs['url'] ) ? (string) $attrs['url'] : '';
1043
1044 if ( $block_id === $page_id ) {
1045 return '';
1046 }
1047
1048 if (
1049 ( 'page' === $block_type )
1050 &&
1051 ( ( 'post-type' === $block_kind ) || ( '' === $block_kind ) )
1052 &&
1053 ( ! empty( $block_id ) )
1054 &&
1055 ( $block_id === $page_id )
1056 ) {
1057 return '';
1058 }
1059
1060 if ( ( ! empty( $preview_url ) ) && ( ! empty( $block_url ) ) ) {
1061
1062 $normalized_preview_url = untrailingslashit( $preview_url );
1063 $normalized_block_url = untrailingslashit( $block_url );
1064
1065 if ( $normalized_preview_url === $normalized_block_url ) {
1066 return '';
1067 }
1068 }
1069
1070 return $block_content;
1071 }
1072 add_filter( 'render_block_core/navigation-link', 'wpbc_bfb_preview__exclude_from_navigation_link_block', 10, 3 );
1073
1074
1075 /**
1076 * Remove BFB Preview page from rendered Page List Item blocks.
1077 *
1078 * This is important for block themes like Twenty Twenty-Four,
1079 * because the Navigation block can use a Page List block that renders
1080 * individual core/page-list-item blocks.
1081 *
1082 * @param string $block_content Rendered block HTML.
1083 * @param array $block Parsed block data.
1084 * @param WP_Block $instance Block instance.
1085 *
1086 * @return string
1087 */
1088 function wpbc_bfb_preview__exclude_from_page_list_item_block( $block_content, $block, $instance ) {
1089
1090 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1091
1092 if ( $page_id <= 0 ) {
1093 return $block_content;
1094 }
1095
1096 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1097 return $block_content;
1098 }
1099
1100 $attrs = $block['attrs'];
1101 $preview_url = get_permalink( $page_id );
1102
1103 $item_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1104 $item_link = isset( $attrs['link'] ) ? (string) $attrs['link'] : '';
1105
1106 if ( $item_id === $page_id ) {
1107 return '';
1108 }
1109
1110 if ( ( ! empty( $preview_url ) ) && ( ! empty( $item_link ) ) ) {
1111 if ( untrailingslashit( $preview_url ) === untrailingslashit( $item_link ) ) {
1112 return '';
1113 }
1114 }
1115
1116 return $block_content;
1117 }
1118 add_filter( 'render_block_core/page-list-item', 'wpbc_bfb_preview__exclude_from_page_list_item_block', 10, 3 );
1119
1120
1121 // FixIn: 10.15.3.1.
1122 /**
1123 * Check whether current request is the BFB Preview page request.
1124 *
1125 * @return bool
1126 */
1127 function wpbc_bfb_is_preview_request() {
1128
1129 if ( empty( $_GET['wpbc_bfb_preview'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1130 return false;
1131 }
1132
1133 return ( '1' === sanitize_text_field( wp_unslash( $_GET['wpbc_bfb_preview'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1134 }
1135
1136 /**
1137 * Add noindex / noarchive robots directives for BFB Preview.
1138 *
1139 * @param array $robots Robots directives.
1140 *
1141 * @return array
1142 */
1143 function wpbc_bfb_preview__wp_robots( $robots ) {
1144
1145 if ( ! wpbc_bfb_is_preview_request() ) {
1146 return $robots;
1147 }
1148
1149 return wp_robots_sensitive_page( $robots );
1150 }
1151 add_filter( 'wp_robots', 'wpbc_bfb_preview__wp_robots' );
1152
1153 /**
1154 * Send no-cache headers for BFB Preview responses.
1155 *
1156 * @return void
1157 */
1158 function wpbc_bfb_preview__nocache_headers() {
1159
1160 if ( ! wpbc_bfb_is_preview_request() ) {
1161 return;
1162 }
1163
1164 if ( ! headers_sent() ) {
1165 nocache_headers();
1166 }
1167 }
1168 add_action( 'template_redirect', 'wpbc_bfb_preview__nocache_headers', 1 );
1169
1170 /**
1171 * Import booking forms on activation of Pro versions of Booking Calendar.
1172 *
1173 * $mode = 'current_context'; // Import booking forms only for the == Current User == (usually - "Regular User").
1174 * $mode = 'all_global'; // Import booking forms for == Super Booking Admin == user ( user_id = 0 ). or for pro versions in all other situations.
1175 * $mode = 'all_users'; // Import booking forms for == All Users ==.
1176 *
1177 * @return void
1178 */
1179 function wpbc_bfb_import_legacy_forms__on_activation() {
1180
1181 if ( ! is_admin() ) {
1182 return;
1183 }
1184
1185 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1186 return;
1187 }
1188
1189 delete_transient( 'wpbc_pro_versions__late_activation_60' );
1190
1191 $import_version = '11.3.1';
1192
1193 // Live demos use dedicated BFB fixtures as their canonical source. Importing
1194 // legacy options here would overwrite the fixtures installed during the
1195 // activation request, especially because the old demo importer used
1196 // skip_if_exists=false.
1197 if ( wpbc_is_this_demo() ) {
1198 if ( wpbc_bfb_live_demo__provision_all_standard_forms() ) {
1199 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1200 } else {
1201 // Keep a bounded retry available if activation dependencies were not
1202 // ready yet on this request.
1203 $retry_count = is_numeric( $is_in_action_late_activation ) ? absint( $is_in_action_late_activation ) : 0;
1204 if ( $retry_count < 3 ) {
1205 set_transient( 'wpbc_pro_versions__late_activation_60', $retry_count + 1, HOUR_IN_SECONDS );
1206 }
1207 }
1208 return;
1209 }
1210
1211 if ( $import_version === get_option( 'wpbc_bfb_legacy_import_done_for_pro', '' ) ) {
1212 return;
1213 }
1214
1215 $summary = wpbc_bfb_import_legacy_forms(
1216 array(
1217 'mode' => ( class_exists( 'wpdev_bk_multiuser' ) ) ? 'all_users' : 'all_global',
1218 'import_standard' => true,
1219 'import_custom' => true,
1220 'skip_if_exists' => true,
1221 'set_bfb_form_not_defined' => true,
1222 )
1223 );
1224 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1225 }
1226 add_action( 'init', 'wpbc_bfb_import_legacy_forms__on_activation', 1000 );
1227
1228
1229 /**
1230 * Schedule one deferred legacy-form import after Pro activation. The import runs on a later request while this transient exists.
1231 *
1232 * @return void
1233 */
1234 function wpbc_pro_versions__late_activation(){
1235
1236 // A regular MultiUser owner activation runs the Pro activation callbacks in
1237 // its own context. It must not schedule a later demo-wide reset.
1238 if ( false !== apply_bk_filter( 'wpbc_is_user_in_activation_process' ) ) {
1239 return;
1240 }
1241
1242 // Get any existing copy of our transient data
1243 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1244 // It wasn't there, so regenerate the data and save the transient
1245 $is_in_action_late_activation = true;
1246 set_transient( 'wpbc_pro_versions__late_activation_60', $is_in_action_late_activation, HOUR_IN_SECONDS );
1247 }
1248 }
1249 add_bk_action( 'wpbc_other_versions_activation', 'wpbc_pro_versions__late_activation' );
1250