PluginProbe
Booking Calendar / 11.8.3
Booking Calendar v11.8.3
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
booking / includes / page-form-builder / save-load / bfb-activate.php

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

1,278 lines 38.6 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 // Activation callbacks also run during version upgrades. Keep page creation
419 // strictly inside the verified first-install boundary.
420 if ( ! function_exists( 'wpbc_should_create_activation_pages' ) || ! wpbc_should_create_activation_pages() ) {
421 return;
422 }
423
424 wpbc_bfb_activation__ensure_preview_page_exists();
425 }
426 add_bk_action( 'wpbc_free_version_activation', 'wpbc_bfb_activation__preview_page' );
427
428
429 /**
430 * A c t i v a t e - Create new DB Table for Booking Form Builder structures.
431 *
432 * This table stores:
433 * - Canonical form builder structure as JSON (structure_json).
434 * - Additional settings as JSON (settings_json).
435 * - Optional precompiled HTML can be added later (not in this schema).
436 * - Ownership via owner_user_id (for MultiUser).
437 *
438 * @return void
439 */
440 function wpbc_bfb_activation__form_structures_table() {
441
442 global $wpdb;
443
444 $charset_collate = ( ! empty( $wpdb->charset ) ) ? 'DEFAULT CHARACTER SET ' . $wpdb->charset : '';
445 $charset_collate .= ( ! empty( $wpdb->collate ) ) ? ' COLLATE ' . $wpdb->collate : '';
446
447 if ( ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
448
449
450
451 $simple_sql = "CREATE TABLE {$wpdb->prefix}booking_form_structures (
452 booking_form_id bigint(20) unsigned NOT NULL auto_increment,
453 form_slug VARCHAR(191) NOT NULL,
454 status VARCHAR(32) NOT NULL,
455 owner_user_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
456
457 scope varchar(64) NOT NULL default 'global',
458 version int(11) NOT NULL default 1,
459 booking_resource_id BIGINT UNSIGNED NULL,
460
461 engine varchar(32) NOT NULL default 'bfb',
462 engine_version varchar(16) NOT NULL default '1.0',
463 structure_json LONGTEXT NOT NULL,
464 settings_json LONGTEXT NULL,
465 advanced_form LONGTEXT NULL,
466 content_form LONGTEXT NULL,
467
468 is_default TINYINT(1) NOT NULL DEFAULT 0,
469 title VARCHAR(255) NOT NULL,
470 description TEXT NULL,
471 picture_url text NULL,
472
473 created_at DATETIME NOT NULL,
474 updated_at DATETIME NOT NULL,
475
476 created_by bigint(20) unsigned DEFAULT NULL,
477 updated_by bigint(20) unsigned DEFAULT NULL,
478 PRIMARY KEY (booking_form_id),
479 UNIQUE KEY form_slug_status (form_slug, status, owner_user_id)
480 ) {$charset_collate}";
481
482 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
483 $wpdb->query( $simple_sql );
484
485 do_action( 'wpbc_bfb_activation__form_structures_table__after_create' );
486
487 wpbc_bfb__import_old_booking_forms();
488 } else {
489 do_action( 'wpbc_bfb_activation__form_structures_table__table_already_exists' );
490 }
491 }
492 add_bk_action( 'wpbc_free_version_activation', 'wpbc_bfb_activation__form_structures_table' );
493
494
495 /**
496 * D e a c t i v a t e - Drop Booking Form Builder structures table.
497 *
498 * @return void
499 */
500 function wpbc_bfb_deactivation__form_structures_table() {
501
502 global $wpdb;
503
504
505
506 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
507 $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}booking_form_structures" );
508
509 delete_bk_option( 'wpbc_bfb_legacy_import_done_for_pro' );
510 }
511 add_bk_action( 'wpbc_free_version_deactivation', 'wpbc_bfb_deactivation__form_structures_table' );
512
513
514 /**
515 * Check the shared activation decision before provisioning a Standard form.
516 *
517 * The wrapper name is retained for compatibility with existing Form Builder
518 * callers and tests. The core detector additionally requires the canonical
519 * booking table to be absent, so a missing version option on an established
520 * installation cannot be mistaken for a new install.
521 *
522 * @return bool True only during a genuine first-install activation request.
523 */
524 function wpbc_bfb_is_brand_new_install__before_version_update() {
525
526 return function_exists( 'wpbc_is_plugin_initial_install' ) && wpbc_is_plugin_initial_install();
527 }
528
529 /**
530 * Let the initial standard BFB form follow the global calendar days-selection setting.
531 *
532 * Some bundled templates intentionally override days selection for their own use case.
533 * The installed "standard" form should not carry that template-level override, because
534 * the global Settings > Calendar page is the canonical default for a new site.
535 *
536 * @param array $template_record Form Builder template record.
537 *
538 * @return array
539 */
540 function wpbc_bfb_activation__set_standard_form_default_days_selection( $template_record ) {
541
542 if ( empty( $template_record ) || ! is_array( $template_record ) ) {
543 return $template_record;
544 }
545
546 $settings = array();
547 if ( ! empty( $template_record['settings_json'] ) ) {
548 $decoded = json_decode( (string) $template_record['settings_json'], true );
549 if ( is_array( $decoded ) ) {
550 $settings = $decoded;
551 }
552 }
553
554 if ( empty( $settings['options'] ) || ! is_array( $settings['options'] ) ) {
555 $settings['options'] = array();
556 }
557
558 $settings['options']['booking_type_of_day_selections'] = '';
559
560 $template_record['settings_json'] = function_exists( 'wpbc_form_config__encode_json' )
561 ? wpbc_form_config__encode_json( $settings )
562 : wp_json_encode( $settings );
563
564 return $template_record;
565 }
566
567 /**
568 * Get the bundled template key used for a brand-new Standard form.
569 *
570 * Every customer edition starts with the spacious vertical full-day form. The
571 * surrounding activation gate limits this choice to a genuine first install,
572 * so upgrades, reactivations, and existing forms remain unchanged.
573 *
574 * @return string Stable bundled template key for a genuine first install.
575 */
576 function wpbc_bfb_activation__get_initial_standard_template_key() {
577
578 return 'dates_vertical_hints_full_days';
579 }
580
581 /**
582 * Maybe create the initial standard BFB form from a bundled template.
583 *
584 * Used only during brand new plugin activation, before booking_version_num
585 * gets updated. Upgrades keep migrating the existing legacy configuration.
586 *
587 * @return bool
588 */
589 function wpbc_bfb_maybe_create_initial_standard_form_from_template() {
590
591 if ( ! wpbc_bfb_is_brand_new_install__before_version_update() ) {
592 return false;
593 }
594
595 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
596 return false;
597 }
598
599 if ( function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
600 $template_key = wpbc_bfb_activation__get_initial_standard_template_key();
601 $template_record = wpbc_get_bfb_template_record_by_key( $template_key );
602
603 // Preserve a usable first-install form if a registry filter removes the
604 // new starter template.
605 if ( empty( $template_record ) && 'dates_2_columns_sidebar_hints' !== $template_key ) {
606 $template_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' );
607 }
608 } else {
609 $template_record = array();
610 }
611
612 if ( empty( $template_record ) || ! is_array( $template_record ) ) {
613 return false;
614 }
615
616 $template_record = wpbc_bfb_activation__set_standard_form_default_days_selection( $template_record );
617
618 $template_record['form_slug'] = 'standard';
619 $template_record['status'] = 'published';
620 $template_record['scope'] = 'global';
621 $template_record['owner_user_id'] = 0;
622 $template_record['booking_resource_id'] = null;
623 $template_record['is_default'] = 1;
624 $template_record['title'] = __( 'Standard', 'booking' );
625 $template_record['description'] = '';
626 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
627 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
628 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
629 }
630
631 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
632
633 return ! empty( $booking_form_id );
634 }
635
636
637 /**
638 * Get the dedicated bundled template for the current live-demo edition.
639 *
640 * @param int $owner_user_id Optional MultiUser owner ID.
641 *
642 * @return string Template key, or an empty string outside a supported demo.
643 */
644 function wpbc_bfb_live_demo__get_template_key( $owner_user_id = 0 ) {
645
646 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
647 return '';
648 }
649
650 $owner_user_id = absint( $owner_user_id );
651
652 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
653 return ( $owner_user_id > 0 ) ? 'demo_multiuser_owner_1' : 'demo_multiuser';
654 }
655 if ( class_exists( 'wpdev_bk_biz_l' ) ) {
656 return 'demo_business_large';
657 }
658 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
659 return 'demo_business_medium';
660 }
661 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
662 return 'demo_business_small';
663 }
664 if ( class_exists( 'wpdev_bk_personal' ) ) {
665 return 'demo_personal';
666 }
667
668 return '';
669 }
670
671
672 /**
673 * Get the global Form Style used by the current live-demo edition.
674 *
675 * @return string Form Style preset key.
676 */
677 function wpbc_bfb_live_demo__get_form_style() {
678
679 if ( class_exists( 'wpdev_bk_multiuser' ) || class_exists( 'wpdev_bk_biz_l' ) ) {
680 return 'light_bordered';
681 }
682 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
683 return 'light_soft';
684 }
685 if ( class_exists( 'wpdev_bk_biz_s' ) ) {
686 return 'light_bordered';
687 }
688 if ( class_exists( 'wpdev_bk_personal' ) ) {
689 return 'light_none';
690 }
691
692 return 'light_bordered';
693 }
694
695
696 /**
697 * Provision a live-demo standard form from a real bundled BFB template.
698 *
699 * Normal owner activation is idempotent and preserves an existing form. The
700 * explicit demo reset path may request replacement to restore fixture data.
701 *
702 * @param int $owner_user_id Owner ID, or 0 for the global/Super Admin form.
703 * @param string $template_key Optional explicit bundled template key.
704 * @param bool $replace Whether to replace an existing standard form.
705 *
706 * @return int|false Booking form ID on success, false on failure.
707 */
708 function wpbc_bfb_live_demo__provision_standard_form( $owner_user_id = 0, $template_key = '', $replace = false ) {
709
710 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
711 return false;
712 }
713 if ( ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
714 return false;
715 }
716 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
717 return false;
718 }
719
720 $owner_user_id = absint( $owner_user_id );
721 $template_key = sanitize_key( (string) $template_key );
722 if ( '' === $template_key ) {
723 $template_key = wpbc_bfb_live_demo__get_template_key( $owner_user_id );
724 }
725 if ( '' === $template_key ) {
726 return false;
727 }
728
729 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
730 if ( ! empty( $existing->booking_form_id ) && ! $replace ) {
731 return (int) $existing->booking_form_id;
732 }
733
734 $template_record = function_exists( 'wpbc_get_bfb_template_record_by_key' )
735 ? wpbc_get_bfb_template_record_by_key( $template_key )
736 : array();
737
738 if (
739 empty( $template_record ) ||
740 ! is_array( $template_record ) ||
741 empty( $template_record['structure_json'] ) ||
742 empty( $template_record['engine'] ) ||
743 'bfb' !== (string) $template_record['engine']
744 ) {
745 return false;
746 }
747
748 $template_record['form_slug'] = 'standard';
749 $template_record['status'] = 'published';
750 $template_record['scope'] = ( $owner_user_id > 0 ) ? 'user' : 'global';
751 $template_record['owner_user_id'] = $owner_user_id;
752 $template_record['booking_resource_id'] = null;
753 $template_record['is_default'] = 1;
754 $template_record['title'] = __( 'Standard', 'booking' );
755 $template_record['description'] = '';
756 $template_record['picture_url'] = isset( $template_record['picture_url'] ) ? (string) $template_record['picture_url'] : '';
757
758 if ( function_exists( 'wpbc_bfb_resolve_picture_url' ) ) {
759 $template_record['picture_url'] = wpbc_bfb_resolve_picture_url( $template_record['picture_url'] );
760 }
761
762 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $template_record );
763
764 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
765 }
766
767
768 /**
769 * Return regular-owner fixtures used by the MultiUser live demo.
770 *
771 * @return array Owner user ID => bundled template key.
772 */
773 function wpbc_bfb_live_demo__get_multiuser_owner_template_map() {
774
775 $owner_templates = array(
776 3 => 'demo_multiuser_owner_1',
777 4 => 'demo_multiuser_owner_2',
778 5 => 'demo_multiuser_owner_3',
779 );
780
781 /**
782 * Allow a private demo installation to use different owner IDs while
783 * retaining the same centralized provisioning flow.
784 *
785 * @param array $owner_templates Owner user ID => template key.
786 */
787 $owner_templates = apply_filters( 'wpbc_bfb_live_demo_multiuser_owner_templates', $owner_templates );
788
789 return is_array( $owner_templates ) ? $owner_templates : array();
790 }
791
792
793 /**
794 * Apply all standard-form fixtures required by the active live demo.
795 *
796 * This is the canonical final activation step. It intentionally replaces demo
797 * fixtures, but never runs on customer installations.
798 *
799 * @return bool True when every applicable fixture was saved.
800 */
801 function wpbc_bfb_live_demo__provision_all_standard_forms() {
802
803 if ( ! function_exists( 'wpbc_is_this_demo' ) || ! wpbc_is_this_demo() ) {
804 return false;
805 }
806
807 // Pro-only reactivation does not necessarily rerun the Free activation
808 // callback that normally synchronizes bundled template rows.
809 if ( function_exists( 'wpbc_bfb_activation__seed_templates' ) ) {
810 wpbc_bfb_activation__seed_templates();
811 }
812
813 $is_success = ( false !== wpbc_bfb_live_demo__provision_standard_form( 0, '', true ) );
814
815 // Form Style is a site-wide setting, not part of an individual form row.
816 // Write directly to the global option so the result is deterministic even
817 // when finalization runs from a regular MultiUser administrator session.
818 update_option( 'booking_form_style', wpbc_bfb_live_demo__get_form_style() );
819
820 // Live demos intentionally showcase the shared accent experience. Customer
821 // installations keep the backward-compatible disabled default.
822 $form_accent_defaults = function_exists( 'wpbc_bfb_settings__get_default_form_accent_options' )
823 ? wpbc_bfb_settings__get_default_form_accent_options()
824 : array( 'booking_form_accent_color' => WPBC_DEFAULT_FORM_ACCENT_COLOR );
825 update_option( 'booking_form_accent_enabled', 'On' );
826 update_option( 'booking_form_accent_color', $form_accent_defaults['booking_form_accent_color'] );
827
828 if ( class_exists( 'wpdev_bk_multiuser' ) ) {
829 foreach ( wpbc_bfb_live_demo__get_multiuser_owner_template_map() as $owner_user_id => $template_key ) {
830 $owner_user_id = absint( $owner_user_id );
831 $template_key = sanitize_key( (string) $template_key );
832 if ( $owner_user_id <= 0 || '' === $template_key ) {
833 $is_success = false;
834 continue;
835 }
836
837 if ( function_exists( 'get_userdata' ) && ! get_userdata( $owner_user_id ) ) {
838 $is_success = false;
839 continue;
840 }
841
842 if ( false === wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, $template_key, true ) ) {
843 $is_success = false;
844 }
845 }
846 }
847
848 return $is_success;
849 }
850
851
852 /**
853 * Ensure that a regular MultiUser owner has an independent standard BFB form.
854 *
855 * Existing owner forms are never overwritten. On normal installations the
856 * current global standard form is cloned, preserving the Super Admin's chosen
857 * Builder structure. Live demos use their dedicated bundled fixture instead.
858 *
859 * @param int $owner_user_id Regular MultiUser owner ID.
860 *
861 * @return int|false Booking form ID on success, false on failure.
862 */
863 function wpbc_bfb_ensure_standard_form_for_owner( $owner_user_id ) {
864
865 $owner_user_id = absint( $owner_user_id );
866 if ( $owner_user_id <= 0 || ! class_exists( 'WPBC_BFB_Form_Storage' ) ) {
867 return false;
868 }
869 if ( function_exists( 'wpbc_is_table_exists' ) && ! wpbc_is_table_exists( 'booking_form_structures' ) ) {
870 return false;
871 }
872
873 $existing = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', $owner_user_id, 'published' );
874 if ( ! empty( $existing->booking_form_id ) ) {
875 return (int) $existing->booking_form_id;
876 }
877
878 if ( function_exists( 'wpbc_is_this_demo' ) && wpbc_is_this_demo() ) {
879 return wpbc_bfb_live_demo__provision_standard_form( $owner_user_id, '', false );
880 }
881
882 $global_standard = WPBC_BFB_Form_Storage::get_current_form_by_key( 'standard', 0, 'published' );
883 $form_record = ! empty( $global_standard ) ? get_object_vars( $global_standard ) : array();
884
885 if ( empty( $form_record['structure_json'] ) && function_exists( 'wpbc_get_bfb_template_record_by_key' ) ) {
886 $form_record = wpbc_get_bfb_template_record_by_key( 'dates_2_columns_sidebar_hints' );
887 }
888 if (
889 empty( $form_record ) ||
890 ! is_array( $form_record ) ||
891 empty( $form_record['structure_json'] ) ||
892 empty( $form_record['engine'] ) ||
893 'bfb' !== (string) $form_record['engine']
894 ) {
895 return false;
896 }
897
898 unset(
899 $form_record['booking_form_id'],
900 $form_record['created_at'],
901 $form_record['created_by'],
902 $form_record['updated_at'],
903 $form_record['updated_by']
904 );
905
906 $form_record['form_slug'] = 'standard';
907 $form_record['status'] = 'published';
908 $form_record['scope'] = 'user';
909 $form_record['owner_user_id'] = $owner_user_id;
910 $form_record['booking_resource_id'] = null;
911 $form_record['is_default'] = 1;
912 $form_record['title'] = __( 'Standard', 'booking' );
913 $form_record['description'] = '';
914
915 $booking_form_id = WPBC_BFB_Form_Storage::save_form( $form_record );
916
917 return ! empty( $booking_form_id ) ? (int) $booking_form_id : false;
918 }
919
920 /**
921 * Import legacy "standard" booking form as initial BFB structure.
922 *
923 * @return void
924 */
925 function wpbc_bfb__import_old_booking_forms() {
926
927 if ( wpbc_bfb_maybe_create_initial_standard_form_from_template() ) {
928 return;
929 }
930
931 if ( function_exists( 'wpbc_bfb_import_legacy_forms' ) ) {
932 wpbc_bfb_import_legacy_forms(
933 array(
934 'mode' => 'all_global',
935 'import_standard' => true,
936 'import_custom' => true,
937 'skip_if_exists' => true,
938 'set_bfb_form_not_defined' => false,
939 )
940 );
941 }
942 }
943
944
945 /**
946 * Remove BFB Preview page from rendered nav menus.
947 *
948 * @param array $sorted_menu_items Menu item objects.
949 * @param stdClass $args Menu args.
950 *
951 * @return array
952 */
953 function wpbc_bfb_preview__exclude_from_nav_menus( $sorted_menu_items, $args ) {
954
955 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
956
957 if ( $page_id <= 0 ) {
958 return $sorted_menu_items;
959 }
960
961 $filtered_items = array();
962
963 foreach ( $sorted_menu_items as $menu_item ) {
964
965 $object_id = isset( $menu_item->object_id ) ? absint( $menu_item->object_id ) : 0;
966 $object = isset( $menu_item->object ) ? (string) $menu_item->object : '';
967
968 if ( ( 'page' === $object ) && ( $page_id === $object_id ) ) {
969 continue;
970 }
971
972 $filtered_items[] = $menu_item;
973 }
974
975 return $filtered_items;
976 }
977 add_filter( 'wp_nav_menu_objects', 'wpbc_bfb_preview__exclude_from_nav_menus', 10, 2 );
978
979
980 /**
981 * Exclude BFB Preview page from wp_list_pages() outputs.
982 *
983 * @param array $exclude_array Excluded page IDs.
984 *
985 * @return array
986 */
987 function wpbc_bfb_preview__exclude_from_page_lists( $exclude_array ) {
988
989 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
990
991 if ( $page_id > 0 ) {
992 $exclude_array[] = $page_id;
993 }
994
995 return array_unique( array_map( 'absint', $exclude_array ) );
996 }
997 add_filter( 'wp_list_pages_excludes', 'wpbc_bfb_preview__exclude_from_page_lists' );
998
999
1000 /**
1001 * Remove BFB Preview page from get_pages() results.
1002 *
1003 * This is important for block themes, because the core Page List block
1004 * renders its items from get_pages().
1005 *
1006 * @param array $pages Array of page objects.
1007 * @param array $parsed_args Parsed get_pages() args.
1008 *
1009 * @return array
1010 */
1011 function wpbc_bfb_preview__exclude_from_get_pages( $pages, $parsed_args ) {
1012
1013 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1014
1015 if ( $page_id <= 0 ) {
1016 return $pages;
1017 }
1018
1019 $filtered_pages = array();
1020
1021 foreach ( $pages as $page_obj ) {
1022
1023 if ( empty( $page_obj ) || empty( $page_obj->ID ) ) {
1024 $filtered_pages[] = $page_obj;
1025 continue;
1026 }
1027
1028 if ( absint( $page_obj->ID ) === $page_id ) {
1029 continue;
1030 }
1031
1032 $filtered_pages[] = $page_obj;
1033 }
1034
1035 return $filtered_pages;
1036 }
1037 add_filter( 'get_pages', 'wpbc_bfb_preview__exclude_from_get_pages', 10, 2 );
1038
1039
1040 /**
1041 * Remove BFB Preview page from rendered Navigation Link blocks.
1042 *
1043 * This handles block-theme menus where the Preview page was manually
1044 * inserted as a Navigation Link item.
1045 *
1046 * @param string $block_content Rendered block HTML.
1047 * @param array $block Parsed block data.
1048 * @param WP_Block $instance Block instance.
1049 *
1050 * @return string
1051 */
1052 function wpbc_bfb_preview__exclude_from_navigation_link_block( $block_content, $block, $instance ) {
1053
1054 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1055
1056 if ( $page_id <= 0 ) {
1057 return $block_content;
1058 }
1059
1060 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1061 return $block_content;
1062 }
1063
1064 $attrs = $block['attrs'];
1065 $preview_url = get_permalink( $page_id );
1066
1067 $block_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1068 $block_type = isset( $attrs['type'] ) ? (string) $attrs['type'] : '';
1069 $block_kind = isset( $attrs['kind'] ) ? (string) $attrs['kind'] : '';
1070 $block_url = isset( $attrs['url'] ) ? (string) $attrs['url'] : '';
1071
1072 if ( $block_id === $page_id ) {
1073 return '';
1074 }
1075
1076 if (
1077 ( 'page' === $block_type )
1078 &&
1079 ( ( 'post-type' === $block_kind ) || ( '' === $block_kind ) )
1080 &&
1081 ( ! empty( $block_id ) )
1082 &&
1083 ( $block_id === $page_id )
1084 ) {
1085 return '';
1086 }
1087
1088 if ( ( ! empty( $preview_url ) ) && ( ! empty( $block_url ) ) ) {
1089
1090 $normalized_preview_url = untrailingslashit( $preview_url );
1091 $normalized_block_url = untrailingslashit( $block_url );
1092
1093 if ( $normalized_preview_url === $normalized_block_url ) {
1094 return '';
1095 }
1096 }
1097
1098 return $block_content;
1099 }
1100 add_filter( 'render_block_core/navigation-link', 'wpbc_bfb_preview__exclude_from_navigation_link_block', 10, 3 );
1101
1102
1103 /**
1104 * Remove BFB Preview page from rendered Page List Item blocks.
1105 *
1106 * This is important for block themes like Twenty Twenty-Four,
1107 * because the Navigation block can use a Page List block that renders
1108 * individual core/page-list-item blocks.
1109 *
1110 * @param string $block_content Rendered block HTML.
1111 * @param array $block Parsed block data.
1112 * @param WP_Block $instance Block instance.
1113 *
1114 * @return string
1115 */
1116 function wpbc_bfb_preview__exclude_from_page_list_item_block( $block_content, $block, $instance ) {
1117
1118 $page_id = absint( get_option( wpbc_bfb_activation__get_preview_page_option_key() ) );
1119
1120 if ( $page_id <= 0 ) {
1121 return $block_content;
1122 }
1123
1124 if ( empty( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
1125 return $block_content;
1126 }
1127
1128 $attrs = $block['attrs'];
1129 $preview_url = get_permalink( $page_id );
1130
1131 $item_id = isset( $attrs['id'] ) ? absint( $attrs['id'] ) : 0;
1132 $item_link = isset( $attrs['link'] ) ? (string) $attrs['link'] : '';
1133
1134 if ( $item_id === $page_id ) {
1135 return '';
1136 }
1137
1138 if ( ( ! empty( $preview_url ) ) && ( ! empty( $item_link ) ) ) {
1139 if ( untrailingslashit( $preview_url ) === untrailingslashit( $item_link ) ) {
1140 return '';
1141 }
1142 }
1143
1144 return $block_content;
1145 }
1146 add_filter( 'render_block_core/page-list-item', 'wpbc_bfb_preview__exclude_from_page_list_item_block', 10, 3 );
1147
1148
1149 // FixIn: 10.15.3.1.
1150 /**
1151 * Check whether current request is the BFB Preview page request.
1152 *
1153 * @return bool
1154 */
1155 function wpbc_bfb_is_preview_request() {
1156
1157 if ( empty( $_GET['wpbc_bfb_preview'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1158 return false;
1159 }
1160
1161 return ( '1' === sanitize_text_field( wp_unslash( $_GET['wpbc_bfb_preview'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1162 }
1163
1164 /**
1165 * Add noindex / noarchive robots directives for BFB Preview.
1166 *
1167 * @param array $robots Robots directives.
1168 *
1169 * @return array
1170 */
1171 function wpbc_bfb_preview__wp_robots( $robots ) {
1172
1173 if ( ! wpbc_bfb_is_preview_request() ) {
1174 return $robots;
1175 }
1176
1177 return wp_robots_sensitive_page( $robots );
1178 }
1179 add_filter( 'wp_robots', 'wpbc_bfb_preview__wp_robots' );
1180
1181 /**
1182 * Send no-cache headers for BFB Preview responses.
1183 *
1184 * @return void
1185 */
1186 function wpbc_bfb_preview__nocache_headers() {
1187
1188 if ( ! wpbc_bfb_is_preview_request() ) {
1189 return;
1190 }
1191
1192 if ( ! headers_sent() ) {
1193 nocache_headers();
1194 }
1195 }
1196 add_action( 'template_redirect', 'wpbc_bfb_preview__nocache_headers', 1 );
1197
1198 /**
1199 * Import booking forms on activation of Pro versions of Booking Calendar.
1200 *
1201 * $mode = 'current_context'; // Import booking forms only for the == Current User == (usually - "Regular User").
1202 * $mode = 'all_global'; // Import booking forms for == Super Booking Admin == user ( user_id = 0 ). or for pro versions in all other situations.
1203 * $mode = 'all_users'; // Import booking forms for == All Users ==.
1204 *
1205 * @return void
1206 */
1207 function wpbc_bfb_import_legacy_forms__on_activation() {
1208
1209 if ( ! is_admin() ) {
1210 return;
1211 }
1212
1213 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1214 return;
1215 }
1216
1217 delete_transient( 'wpbc_pro_versions__late_activation_60' );
1218
1219 $import_version = '11.3.1';
1220
1221 // Live demos use dedicated BFB fixtures as their canonical source. Importing
1222 // legacy options here would overwrite the fixtures installed during the
1223 // activation request, especially because the old demo importer used
1224 // skip_if_exists=false.
1225 if ( wpbc_is_this_demo() ) {
1226 if ( wpbc_bfb_live_demo__provision_all_standard_forms() ) {
1227 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1228 } else {
1229 // Keep a bounded retry available if activation dependencies were not
1230 // ready yet on this request.
1231 $retry_count = is_numeric( $is_in_action_late_activation ) ? absint( $is_in_action_late_activation ) : 0;
1232 if ( $retry_count < 3 ) {
1233 set_transient( 'wpbc_pro_versions__late_activation_60', $retry_count + 1, HOUR_IN_SECONDS );
1234 }
1235 }
1236 return;
1237 }
1238
1239 if ( $import_version === get_option( 'wpbc_bfb_legacy_import_done_for_pro', '' ) ) {
1240 return;
1241 }
1242
1243 $summary = wpbc_bfb_import_legacy_forms(
1244 array(
1245 'mode' => ( class_exists( 'wpdev_bk_multiuser' ) ) ? 'all_users' : 'all_global',
1246 'import_standard' => true,
1247 'import_custom' => true,
1248 'skip_if_exists' => true,
1249 'set_bfb_form_not_defined' => true,
1250 )
1251 );
1252 update_option( 'wpbc_bfb_legacy_import_done_for_pro', $import_version );
1253 }
1254 add_action( 'init', 'wpbc_bfb_import_legacy_forms__on_activation', 1000 );
1255
1256
1257 /**
1258 * Schedule one deferred legacy-form import after Pro activation. The import runs on a later request while this transient exists.
1259 *
1260 * @return void
1261 */
1262 function wpbc_pro_versions__late_activation(){
1263
1264 // A regular MultiUser owner activation runs the Pro activation callbacks in
1265 // its own context. It must not schedule a later demo-wide reset.
1266 if ( false !== apply_bk_filter( 'wpbc_is_user_in_activation_process' ) ) {
1267 return;
1268 }
1269
1270 // Get any existing copy of our transient data
1271 if ( false === ( $is_in_action_late_activation = get_transient( 'wpbc_pro_versions__late_activation_60' ) ) ) {
1272 // It wasn't there, so regenerate the data and save the transient
1273 $is_in_action_late_activation = true;
1274 set_transient( 'wpbc_pro_versions__late_activation_60', $is_in_action_late_activation, HOUR_IN_SECONDS );
1275 }
1276 }
1277 add_bk_action( 'wpbc_other_versions_activation', 'wpbc_pro_versions__late_activation' );
1278