PluginProbe
Booking Calendar / 11.6.1
Booking Calendar v11.6.1
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.6.1, at includes/page-form-builder/save-load/bfb-activate.php

1,271 lines 37.7 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 * Get the bundled template key used for a brand-new Standard form.
560 *
561 * Booking Calendar Free starts with the compact single-column form whose date
562 * hints remain visible immediately below the calendar. Paid editions retain
563 * their existing edition-aware two-column starter form.
564 *
565 * @return string Stable bundled template key for the active edition.
566 */
567 function wpbc_bfb_activation__get_initial_standard_template_key() {
568
569 return class_exists( 'wpdev_bk_personal' )
570 ? 'dates_2_columns_sidebar_hints'
571 : 'dates_form_with_inline_hints';
572 }
573
574 /**
575 * Maybe create the initial standard BFB form from a bundled template.
576 *
577 * Used only during brand new plugin activation, before booking_version_num
578 * gets updated. Upgrades keep migrating the existing legacy configuration.
579 *
580 * @return bool
581 */
582 function wpbc_bfb_maybe_create_initial_standard_form_from_template() {
583
584 if ( ! wpbc_bfb_is_brand_new_install__before_version_update() ) {
585 return false;
586 }
587
588 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
589 return false;
590 }
591
592 if ( function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
593 $template_key = wpbc_bfb_activation__get_initial_standard_template_key();
594 $template_record = wpbc_get_bfb_template_record_by_key( $template_key );
595
596 // Preserve a usable first-install form if a registry filter removes the
597 // new Free starter template.
598 if ( empty( $template_record ) && 'dates_2_columns_sidebar_hints' !== $template_key ) {
599 $template_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' );
600 }
601 } else {
602 $template_record = array();
603 }
604
605 if ( empty( $template_record ) || ! is_array( $template_record ) ) {
606 return false;
607 }
608
609 $template_record = wpbc_bfb_activation__set_standard_form_default_days_selection( $template_record );
610
611 $template_record['form_slug'] = 'standard';
612 $template_record['status'] = 'published';
613 $template_record['scope'] = 'global';
614 $template_record['owner_user_id'] = 0;
615 $template_record['booking_resource_id'] = null;
616 $template_record['is_default'] = 1;
617 $template_record['title'] = __( 'Standard', 'booking' );
618 $template_record['description'] = '';
619 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
620 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
621 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
622 }
623
624 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
625
626 return ! empty( $booking_form_id );
627 }
628
629
630 /**
631 * Get the dedicated bundled template for the current live-demo edition.
632 *
633 * @param int $owner_user_id Optional MultiUser owner ID.
634 *
635 * @return string Template key, or an empty string outside a supported demo.
636 */
637 function wpbc_bfb_live_demo__get_template_key( $owner_user_id = 0 ) {
638
639 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
640 return '';
641 }
642
643 $owner_user_id = absint( $owner_user_id );
644
645 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
646 return ( $owner_user_id > 0 ) ? 'demo_multiuser_owner_1' : 'demo_multiuser';
647 }
648 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
649 return 'demo_business_large';
650 }
651 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
652 return 'demo_business_medium';
653 }
654 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
655 return 'demo_business_small';
656 }
657 if ( class_exists( 'wpdev_bk_personal' ) ) {
658 return 'demo_personal';
659 }
660
661 return '';
662 }
663
664
665 /**
666 * Get the global Form Style used by the current live-demo edition.
667 *
668 * @return string Form Style preset key.
669 */
670 function wpbc_bfb_live_demo__get_form_style() {
671
672 if ( class_exists( 'wpdev_bk_multiuser' ) || class_exists( 'wpdev_bk_biz_l' ) ) {
673 return 'light_bordered';
674 }
675 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
676 return 'light_soft';
677 }
678 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
679 return 'light_bordered';
680 }
681 if ( class_exists( 'wpdev_bk_personal' ) ) {
682 return 'light_none';
683 }
684
685 return 'light_bordered';
686 }
687
688
689 /**
690 * Provision a live-demo standard form from a real bundled BFB template.
691 *
692 * Normal owner activation is idempotent and preserves an existing form. The
693 * explicit demo reset path may request replacement to restore fixture data.
694 *
695 * @param int $owner_user_id Owner ID, or 0 for the global/Super Admin form.
696 * @param string $template_key Optional explicit bundled template key.
697 * @param bool $replace Whether to replace an existing standard form.
698 *
699 * @return int|false Booking form ID on success, false on failure.
700 */
701 function wpbc_bfb_live_demo__provision_standard_form( $owner_user_id = 0, $template_key = '', $replace = false ) {
702
703 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
704 return false;
705 }
706 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
707 return false;
708 }
709 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
710 return false;
711 }
712
713 $owner_user_id = absint( $owner_user_id );
714 $template_key = sanitize_key( (string) $template_key );
715 if ( '' === $template_key ) {
716 $template_key = wpbc_bfb_live_demo__get_template_key( $owner_user_id );
717 }
718 if ( '' === $template_key ) {
719 return false;
720 }
721
722 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
723 if ( ! empty( $existing->booking_form_id ) && ! $replace ) {
724 return (int) $existing->booking_form_id;
725 }
726
727 $template_record = function_exists( 'wpbc_get_bfb_template_record_by_key' )
728 ? wpbc_get_bfb_template_record_by_key( $template_key )
729 : array();
730
731 if (
732 empty( $template_record ) ||
733 ! is_array( $template_record ) ||
734 empty( $template_record['structure_json'] ) ||
735 empty( $template_record['engine'] ) ||
736 'bfb' !== (string) $template_record['engine']
737 ) {
738 return false;
739 }
740
741 $template_record['form_slug'] = 'standard';
742 $template_record['status'] = 'published';
743 $template_record['scope'] = ( $owner_user_id > 0 ) ? 'user' : 'global';
744 $template_record['owner_user_id'] = $owner_user_id;
745 $template_record['booking_resource_id'] = null;
746 $template_record['is_default'] = 1;
747 $template_record['title'] = __( 'Standard', 'booking' );
748 $template_record['description'] = '';
749 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
750
751 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
752 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
753 }
754
755 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
756
757 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
758 }
759
760
761 /**
762 * Return regular-owner fixtures used by the MultiUser live demo.
763 *
764 * @return array Owner user ID => bundled template key.
765 */
766 function wpbc_bfb_live_demo__get_multiuser_owner_template_map() {
767
768 $owner_templates = array(
769 3 => 'demo_multiuser_owner_1',
770 4 => 'demo_multiuser_owner_2',
771 5 => 'demo_multiuser_owner_3',
772 );
773
774 /**
775 * Allow a private demo installation to use different owner IDs while
776 * retaining the same centralized provisioning flow.
777 *
778 * @param array $owner_templates Owner user ID => template key.
779 */
780 $owner_templates = apply_filters( 'wpbc_bfb_live_demo_multiuser_owner_templates', $owner_templates );
781
782 return is_array( $owner_templates ) ? $owner_templates : array();
783 }
784
785
786 /**
787 * Apply all standard-form fixtures required by the active live demo.
788 *
789 * This is the canonical final activation step. It intentionally replaces demo
790 * fixtures, but never runs on customer installations.
791 *
792 * @return bool True when every applicable fixture was saved.
793 */
794 function wpbc_bfb_live_demo__provision_all_standard_forms() {
795
796 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
797 return false;
798 }
799
800 // Pro-only reactivation does not necessarily rerun the Free activation
801 // callback that normally synchronizes bundled template rows.
802 if ( function_exists( 'wpbc_bfb_activation__seed_templates' ) ) {
803 wpbc_bfb_activation__seed_templates();
804 }
805
806 $is_success = ( false !== wpbc_bfb_live_demo__provision_standard_form( 0, '', true ) );
807
808 // Form Style is a site-wide setting, not part of an individual form row.
809 // Write directly to the global option so the result is deterministic even
810 // when finalization runs from a regular MultiUser administrator session.
811 update_option( 'booking_form_style', wpbc_bfb_live_demo__get_form_style() );
812
813 // Live demos intentionally showcase the shared accent experience. Customer
814 // installations keep the backward-compatible disabled default.
815 $form_accent_defaults = function_exists( 'wpbc_bfb_settings__get_default_form_accent_options' )
816 ? wpbc_bfb_settings__get_default_form_accent_options()
817 : array( 'booking_form_accent_color' => WPBC_DEFAULT_FORM_ACCENT_COLOR );
818 update_option( 'booking_form_accent_enabled', 'On' );
819 update_option( 'booking_form_accent_color', $form_accent_defaults['booking_form_accent_color'] );
820
821 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
822 foreach ( wpbc_bfb_live_demo__get_multiuser_owner_template_map() as $owner_user_id => $template_key ) {
823 $owner_user_id = absint( $owner_user_id );
824 $template_key = sanitize_key( (string) $template_key );
825 if ( $owner_user_id <= 0 || '' === $template_key ) {
826 $is_success = false;
827 continue;
828 }
829
830 if ( function_exists( 'get_userdata' ) && ! get_userdata( $owner_user_id ) ) {
831 $is_success = false;
832 continue;
833 }
834
835 if ( false === wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, $template_key, true ) ) {
836 $is_success = false;
837 }
838 }
839 }
840
841 return $is_success;
842 }
843
844
845 /**
846 * Ensure that a regular MultiUser owner has an independent standard BFB form.
847 *
848 * Existing owner forms are never overwritten. On normal installations the
849 * current global standard form is cloned, preserving the Super Admin's chosen
850 * Builder structure. Live demos use their dedicated bundled fixture instead.
851 *
852 * @param int $owner_user_id Regular MultiUser owner ID.
853 *
854 * @return int|false Booking form ID on success, false on failure.
855 */
856 function wpbc_bfb_ensure_standard_form_for_owner( $owner_user_id ) {
857
858 $owner_user_id = absint( $owner_user_id );
859 if ( $owner_user_id <= 0 || ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
860 return false;
861 }
862 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
863 return false;
864 }
865
866 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
867 if ( ! empty( $existing->booking_form_id ) ) {
868 return (int) $existing->booking_form_id;
869 }
870
871 if ( function_exists( 'wpbc_is_this_demo' ) && wpbc_is_this_demo() ) {
872 return wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, '', false );
873 }
874
875 $global_standard = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', 0, 'published' );
876 $form_record = ! empty( $global_standard ) ? get_object_vars( $global_standard ) : array();
877
878 if ( empty( $form_record['structure_json'] ) && function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
879 $form_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' );
880 }
881 if (
882 empty( $form_record ) ||
883 ! is_array( $form_record ) ||
884 empty( $form_record['structure_json'] ) ||
885 empty( $form_record['engine'] ) ||
886 'bfb' !== (string) $form_record['engine']
887 ) {
888 return false;
889 }
890
891 unset(
892 $form_record['booking_form_id'],
893 $form_record['created_at'],
894 $form_record['created_by'],
895 $form_record['updated_at'],
896 $form_record['updated_by']
897 );
898
899 $form_record['form_slug'] = 'standard';
900 $form_record['status'] = 'published';
901 $form_record['scope'] = 'user';
902 $form_record['owner_user_id'] = $owner_user_id;
903 $form_record['booking_resource_id'] = null;
904 $form_record['is_default'] = 1;
905 $form_record['title'] = __( 'Standard', 'booking' );
906 $form_record['description'] = '';
907
908 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $form_record );
909
910 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
911 }
912
913 /**
914 * Import legacy "standard" booking form as initial BFB structure.
915 *
916 * @return void
917 */
918 function wpbc_bfb__import_old_booking_forms() {
919
920 if ( wpbc_bfb_maybe_create_initial_standard_form_from_template() ) {
921 return;
922 }
923
924 if ( function_exists( 'wpbc_bfb_import_legacy_forms' ) ) {
925 wpbc_bfb_import_legacy_forms(
926 array(
927 'mode' => 'all_global',
928 'import_standard' => true,
929 'import_custom' => true,
930 'skip_if_exists' => true,
931 'set_bfb_form_not_defined' => false,
932 )
933 );
934 }
935 }
936
937
938 /**
939 * Remove BFB Preview page from rendered nav menus.
940 *
941 * @param array $sorted_menu_items Menu item objects.
942 * @param stdClass $args Menu args.
943 *
944 * @return array
945 */
946 function wpbc_bfb_preview__exclude_from_nav_menus( $sorted_menu_items, $args ) {
947
948 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
949
950 if ( $page_id <= 0 ) {
951 return $sorted_menu_items;
952 }
953
954 $filtered_items = array();
955
956 foreach ( $sorted_menu_items as $menu_item ) {
957
958 $object_id = isset( $menu_item->object_id ) ? absint( $menu_item->object_id ) : 0;
959 $object = isset( $menu_item->object ) ? (string) $menu_item->object : '';
960
961 if ( ( 'page' === $object ) && ( $page_id === $object_id ) ) {
962 continue;
963 }
964
965 $filtered_items[] = $menu_item;
966 }
967
968 return $filtered_items;
969 }
970 add_filter( 'wp_nav_menu_objects', 'wpbc_bfb_preview__exclude_from_nav_menus', 10, 2 );
971
972
973 /**
974 * Exclude BFB Preview page from wp_list_pages() outputs.
975 *
976 * @param array $exclude_array Excluded page IDs.
977 *
978 * @return array
979 */
980 function wpbc_bfb_preview__exclude_from_page_lists( $exclude_array ) {
981
982 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
983
984 if ( $page_id > 0 ) {
985 $exclude_array[] = $page_id;
986 }
987
988 return array_unique( array_map( 'absint', $exclude_array ) );
989 }
990 add_filter( 'wp_list_pages_excludes', 'wpbc_bfb_preview__exclude_from_page_lists' );
991
992
993 /**
994 * Remove BFB Preview page from get_pages() results.
995 *
996 * This is important for block themes, because the core Page List block
997 * renders its items from get_pages().
998 *
999 * @param array $pages Array of page objects.
1000 * @param array $parsed_args Parsed get_pages() args.
1001 *
1002 * @return array
1003 */
1004 function wpbc_bfb_preview__exclude_from_get_pages( $pages, $parsed_args ) {
1005
1006 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1007
1008 if ( $page_id <= 0 ) {
1009 return $pages;
1010 }
1011
1012 $filtered_pages = array();
1013
1014 foreach ( $pages as $page_obj ) {
1015
1016 if ( empty( $page_obj ) || empty( $page_obj->ID ) ) {
1017 $filtered_pages[] = $page_obj;
1018 continue;
1019 }
1020
1021 if ( absint( $page_obj->ID ) === $page_id ) {
1022 continue;
1023 }
1024
1025 $filtered_pages[] = $page_obj;
1026 }
1027
1028 return $filtered_pages;
1029 }
1030 add_filter( 'get_pages', 'wpbc_bfb_preview__exclude_from_get_pages', 10, 2 );
1031
1032
1033 /**
1034 * Remove BFB Preview page from rendered Navigation Link blocks.
1035 *
1036 * This handles block-theme menus where the Preview page was manually
1037 * inserted as a Navigation Link item.
1038 *
1039 * @param string $block_content Rendered block HTML.
1040 * @param array $block Parsed block data.
1041 * @param WP_Block $instance Block instance.
1042 *
1043 * @return string
1044 */
1045 function wpbc_bfb_preview__exclude_from_navigation_link_block( $block_content, $block, $instance ) {
1046
1047 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1048
1049 if ( $page_id <= 0 ) {
1050 return $block_content;
1051 }
1052
1053 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1054 return $block_content;
1055 }
1056
1057 $attrs = $block['attrs'];
1058 $preview_url = get_permalink( $page_id );
1059
1060 $block_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1061 $block_type = isset( $attrs['type'] ) ? (string) $attrs['type'] : '';
1062 $block_kind = isset( $attrs['kind'] ) ? (string) $attrs['kind'] : '';
1063 $block_url = isset( $attrs['url'] ) ? (string) $attrs['url'] : '';
1064
1065 if ( $block_id === $page_id ) {
1066 return '';
1067 }
1068
1069 if (
1070 ( 'page' === $block_type )
1071 &&
1072 ( ( 'post-type' === $block_kind ) || ( '' === $block_kind ) )
1073 &&
1074 ( ! empty( $block_id ) )
1075 &&
1076 ( $block_id === $page_id )
1077 ) {
1078 return '';
1079 }
1080
1081 if ( ( ! empty( $preview_url ) ) && ( ! empty( $block_url ) ) ) {
1082
1083 $normalized_preview_url = untrailingslashit( $preview_url );
1084 $normalized_block_url = untrailingslashit( $block_url );
1085
1086 if ( $normalized_preview_url === $normalized_block_url ) {
1087 return '';
1088 }
1089 }
1090
1091 return $block_content;
1092 }
1093 add_filter( 'render_block_core/navigation-link', 'wpbc_bfb_preview__exclude_from_navigation_link_block', 10, 3 );
1094
1095
1096 /**
1097 * Remove BFB Preview page from rendered Page List Item blocks.
1098 *
1099 * This is important for block themes like Twenty Twenty-Four,
1100 * because the Navigation block can use a Page List block that renders
1101 * individual core/page-list-item blocks.
1102 *
1103 * @param string $block_content Rendered block HTML.
1104 * @param array $block Parsed block data.
1105 * @param WP_Block $instance Block instance.
1106 *
1107 * @return string
1108 */
1109 function wpbc_bfb_preview__exclude_from_page_list_item_block( $block_content, $block, $instance ) {
1110
1111 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1112
1113 if ( $page_id <= 0 ) {
1114 return $block_content;
1115 }
1116
1117 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1118 return $block_content;
1119 }
1120
1121 $attrs = $block['attrs'];
1122 $preview_url = get_permalink( $page_id );
1123
1124 $item_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1125 $item_link = isset( $attrs['link'] ) ? (string) $attrs['link'] : '';
1126
1127 if ( $item_id === $page_id ) {
1128 return '';
1129 }
1130
1131 if ( ( ! empty( $preview_url ) ) && ( ! empty( $item_link ) ) ) {
1132 if ( untrailingslashit( $preview_url ) === untrailingslashit( $item_link ) ) {
1133 return '';
1134 }
1135 }
1136
1137 return $block_content;
1138 }
1139 add_filter( 'render_block_core/page-list-item', 'wpbc_bfb_preview__exclude_from_page_list_item_block', 10, 3 );
1140
1141
1142 // FixIn: 10.15.3.1.
1143 /**
1144 * Check whether current request is the BFB Preview page request.
1145 *
1146 * @return bool
1147 */
1148 function wpbc_bfb_is_preview_request() {
1149
1150 if ( empty( $_GET['wpbc_bfb_preview'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1151 return false;
1152 }
1153
1154 return ( '1' === sanitize_text_field( wp_unslash( $_GET['wpbc_bfb_preview'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1155 }
1156
1157 /**
1158 * Add noindex / noarchive robots directives for BFB Preview.
1159 *
1160 * @param array $robots Robots directives.
1161 *
1162 * @return array
1163 */
1164 function wpbc_bfb_preview__wp_robots( $robots ) {
1165
1166 if ( ! wpbc_bfb_is_preview_request() ) {
1167 return $robots;
1168 }
1169
1170 return wp_robots_sensitive_page( $robots );
1171 }
1172 add_filter( 'wp_robots', 'wpbc_bfb_preview__wp_robots' );
1173
1174 /**
1175 * Send no-cache headers for BFB Preview responses.
1176 *
1177 * @return void
1178 */
1179 function wpbc_bfb_preview__nocache_headers() {
1180
1181 if ( ! wpbc_bfb_is_preview_request() ) {
1182 return;
1183 }
1184
1185 if ( ! headers_sent() ) {
1186 nocache_headers();
1187 }
1188 }
1189 add_action( 'template_redirect', 'wpbc_bfb_preview__nocache_headers', 1 );
1190
1191 /**
1192 * Import booking forms on activation of Pro versions of Booking Calendar.
1193 *
1194 * $mode = 'current_context'; // Import booking forms only for the == Current User == (usually - "Regular User").
1195 * $mode = 'all_global'; // Import booking forms for == Super Booking Admin == user ( user_id = 0 ). or for pro versions in all other situations.
1196 * $mode = 'all_users'; // Import booking forms for == All Users ==.
1197 *
1198 * @return void
1199 */
1200 function wpbc_bfb_import_legacy_forms__on_activation() {
1201
1202 if ( ! is_admin() ) {
1203 return;
1204 }
1205
1206 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1207 return;
1208 }
1209
1210 delete_transient( 'wpbc_pro_versions__late_activation_60' );
1211
1212 $import_version = '11.3.1';
1213
1214 // Live demos use dedicated BFB fixtures as their canonical source. Importing
1215 // legacy options here would overwrite the fixtures installed during the
1216 // activation request, especially because the old demo importer used
1217 // skip_if_exists=false.
1218 if ( wpbc_is_this_demo() ) {
1219 if ( wpbc_bfb_live_demo__provision_all_standard_forms() ) {
1220 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1221 } else {
1222 // Keep a bounded retry available if activation dependencies were not
1223 // ready yet on this request.
1224 $retry_count = is_numeric( $is_in_action_late_activation ) ? absint( $is_in_action_late_activation ) : 0;
1225 if ( $retry_count < 3 ) {
1226 set_transient( 'wpbc_pro_versions__late_activation_60', $retry_count + 1, HOUR_IN_SECONDS );
1227 }
1228 }
1229 return;
1230 }
1231
1232 if ( $import_version === get_option( 'wpbc_bfb_legacy_import_done_for_pro', '' ) ) {
1233 return;
1234 }
1235
1236 $summary = wpbc_bfb_import_legacy_forms(
1237 array(
1238 'mode' => ( class_exists( 'wpdev_bk_multiuser' ) ) ? 'all_users' : 'all_global',
1239 'import_standard' => true,
1240 'import_custom' => true,
1241 'skip_if_exists' => true,
1242 'set_bfb_form_not_defined' => true,
1243 )
1244 );
1245 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1246 }
1247 add_action( 'init', 'wpbc_bfb_import_legacy_forms__on_activation', 1000 );
1248
1249
1250 /**
1251 * Schedule one deferred legacy-form import after Pro activation. The import runs on a later request while this transient exists.
1252 *
1253 * @return void
1254 */
1255 function wpbc_pro_versions__late_activation(){
1256
1257 // A regular MultiUser owner activation runs the Pro activation callbacks in
1258 // its own context. It must not schedule a later demo-wide reset.
1259 if ( false !== apply_bk_filter( 'wpbc_is_user_in_activation_process' ) ) {
1260 return;
1261 }
1262
1263 // Get any existing copy of our transient data
1264 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1265 // It wasn't there, so regenerate the data and save the transient
1266 $is_in_action_late_activation = true;
1267 set_transient( 'wpbc_pro_versions__late_activation_60', $is_in_action_late_activation, HOUR_IN_SECONDS );
1268 }
1269 }
1270 add_bk_action( 'wpbc_other_versions_activation', 'wpbc_pro_versions__late_activation' );
1271