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

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