array( 'value' => 'On|Off' ). Setup routing happens before redirects, so the * wizard updates the same preference and its immediate browser cookie when it moves between internal and external * setup screens. Synchronizing both values prevents an older cookie from overriding the active wizard state. * This sends a response cookie when headers remain available and always updates the cookie value for the current * request. * * @param bool $is_full_screen Whether WPBC admin pages should open in fullscreen mode. * * @return bool True when the saved user option changed; otherwise false. */ function wpbc_setup_wizard__set_full_screen_mode_for_current_user( $is_full_screen ) { $user_id = wpbc_get_current_user_id(); if ( empty( $user_id ) ) { return false; } $full_screen_value = $is_full_screen ? 'On' : 'Off'; $cookie_path = ( defined( 'COOKIEPATH' ) && COOKIEPATH ) ? COOKIEPATH : '/'; $cookie_domain = defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : ''; if ( ! headers_sent() ) { setcookie( 'wpbc_admin_full_screen', $full_screen_value, time() + YEAR_IN_SECONDS, $cookie_path, $cookie_domain, is_ssl(), false ); } // Keep cookie-aware page rendering deterministic during the current request. $_COOKIE['wpbc_admin_full_screen'] = $full_screen_value; return update_user_option( $user_id, 'booking_custom_is_full_screen', array( 'value' => $full_screen_value ) ); } /** * Get persisted wizard choices. * * @return array */ function wpbc_setup_wizard__get_booking_wizard_data() { $wizard_data = get_bk_option( 'booking_wizard_data' ); return ( empty( $wizard_data ) || ( ! is_array( $wizard_data ) ) ) ? array() : $wizard_data; } /** * Get selected booking type from wizard history or current options. * * @param array|null $wizard_data Wizard data. * * @return string */ function wpbc_setup_wizard__get_selected_booking_type( $wizard_data = null ) { if ( null === $wizard_data ) { $wizard_data = wpbc_setup_wizard__get_booking_wizard_data(); } if ( isset( $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_types'] ) && ( ! empty( $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_types'] ) ) ) { return (string) $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_types']; } if ( 'On' === get_bk_option( 'booking_range_selection_time_is_active' ) ) { return 'changeover_multi_dates_bookings'; } if ( 'single' === get_bk_option( 'booking_type_of_day_selections' ) ) { return 'time_slots_appointments'; } return 'full_days_bookings'; } /** * Get selected appointment mode from wizard history or current options. * * @param array|null $wizard_data Wizard data. * * @return string */ function wpbc_setup_wizard__get_selected_appointments_type( $wizard_data = null ) { if ( null === $wizard_data ) { $wizard_data = wpbc_setup_wizard__get_booking_wizard_data(); } // Appointment mode always uses the Service duration and start-time flow. if ( 'appointment' === wpbc_setup_wizard__get_selected_mode_id( $wizard_data ) ) { return 'durationtime'; } if ( isset( $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_appointments_type'] ) && ( ! empty( $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_appointments_type'] ) ) ) { return (string) $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_appointments_type']; } return 'rangetime'; } /** * Check whether Booking Modes can resolve the current owner safely. * * Setup progress can be inspected while active plugins are still loading. * Before `init`, WordPress has not guaranteed that current-user pluggable * functions or just-in-time translation loading are ready. Mode registries * must therefore remain untouched until this boundary is reached. * * @return bool True when current-user and translation-dependent mode APIs can * be used safely. */ function wpbc_setup_wizard__is_booking_modes_runtime_ready() { return did_action( 'init' ) && function_exists( 'wp_get_current_user' ); } /** * Get the presentation mode selected in wizard history or owner settings. * * Missing historical mode data is expected on upgrades. In that case the * owner-scoped Booking Modes setting remains authoritative, with Classic as a * compatibility fallback when the feature module is unavailable. * * @param array|null $wizard_data Wizard data. * * @return string Allowed Booking Modes identifier. */ function wpbc_setup_wizard__get_selected_mode_id( $wizard_data = null ) { if ( null === $wizard_data ) { $wizard_data = wpbc_setup_wizard__get_booking_wizard_data(); } $saved_mode_id = isset( $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_mode'] ) ? sanitize_key( (string) $wizard_data['save_and_continue__bookings_types']['wpbc_swp_booking_mode'] ) : ''; $standard_mode_ids = array( 'classic', 'appointment', 'rental' ); if ( ! wpbc_setup_wizard__is_booking_modes_runtime_ready() ) { return in_array( $saved_mode_id, $standard_mode_ids, true ) ? $saved_mode_id : 'classic'; } if ( function_exists( 'wpbc_booking_modes_get_allowed_mode_ids' ) ) { $allowed_mode_ids = wpbc_booking_modes_get_allowed_mode_ids(); if ( in_array( $saved_mode_id, $allowed_mode_ids, true ) ) { return $saved_mode_id; } return wpbc_booking_modes_get_selected_mode_id(); } return 'classic'; } /** * Convert selected booking type to a setup profile. * * @param array|null $wizard_data Wizard data. * * @return string */ function wpbc_setup_wizard__get_selected_profile( $wizard_data = null ) { $booking_type = wpbc_setup_wizard__get_selected_booking_type( $wizard_data ); if ( 'time_slots_appointments' === $booking_type ) { return ( 'durationtime' === wpbc_setup_wizard__get_selected_appointments_type( $wizard_data ) ) ? 'duration_appointments' : 'time_slots'; } if ( 'changeover_multi_dates_bookings' === $booking_type ) { return 'changeover'; } return 'full_day'; } /** * Check whether profile uses booking times. * * @param string|null $profile Profile. * * @return bool */ function wpbc_setup_wizard__is_time_based_profile( $profile = null ) { $profile = ( null === $profile ) ? wpbc_setup_wizard__get_selected_profile() : $profile; return in_array( $profile, array( 'time_slots', 'duration_appointments' ), true ); } /** * Check whether profile uses changeover mode. * * @param string|null $profile Profile. * * @return bool */ function wpbc_setup_wizard__is_changeover_profile( $profile = null ) { $profile = ( null === $profile ) ? wpbc_setup_wizard__get_selected_profile() : $profile; return ( 'changeover' === $profile ); } /** * Get the internal setup wizard route before profile-specific external pages. * * @return array */ function wpbc_setup_wizard__get_intro_route() { $route = array( 'welcome' ); if ( ! wpbc_is_this_demo() ) { $route[] = 'general_info'; } $route[] = 'date_time_formats'; $route[] = 'bookings_types'; return $route; } /** * Get profile-specific setup route matrix after "Booking Type". * * Steps 1-4 stay inside the setup wizard. These compatibility routes remain * behavior-based; `wpbc_setup_wizard__get_profile_route()` can replace their * order for a presentation mode while reusing the same canonical controllers. * * @return array */ function wpbc_setup_wizard__get_profile_route_map() { $full_day_route = array( 'form_structure', 'date_selection', 'date_availability', 'color_theme', 'wizard_publish', 'get_started', ); $changeover_route = array( 'form_structure', 'date_selection', 'changeover_days', 'date_availability', 'color_theme', 'wizard_publish', 'get_started', ); $time_based_route = array( 'form_structure', 'date_selection', 'working_time', 'time_slots_availability', 'color_theme', 'wizard_publish', 'get_started', ); return array( 'full_day' => $full_day_route, 'changeover' => $changeover_route, 'time_slots' => $time_based_route, 'duration_appointments' => $time_based_route, ); } /** * Get profile-specific setup route after "Booking Type". * * @param string|null $profile Profile. * @param bool|null $is_bfb_enabled Deprecated. Kept for backward-compatible calls. * * @return array */ function wpbc_setup_wizard__get_profile_route( $profile = null, $is_bfb_enabled = null ) { $profile = ( null === $profile ) ? wpbc_setup_wizard__get_selected_profile() : $profile; $mode_id = wpbc_setup_wizard__get_selected_mode_id(); $route_map = wpbc_setup_wizard__get_profile_route_map(); if ( 'appointment' === $mode_id ) { $route = array( 'service_provider', 'working_time', 'date_availability', 'time_slots_availability', 'form_structure', 'color_theme', 'wizard_publish', 'get_started', ); } else { $route = isset( $route_map[ $profile ] ) ? $route_map[ $profile ] : $route_map['full_day']; } /** * Filter the mode-aware Setup Wizard route after Step 4. * * @param array $route Ordered external setup steps. * @param string $mode_id Active Booking Modes identifier. * @param string $profile Booking behavior profile. */ $route = apply_filters( 'wpbc_setup_wizard_profile_route', $route, $mode_id, $profile ); return is_array( $route ) ? array_values( $route ) : array(); } /** * Get the next setup step from the current profile route. * * @param string $current_step Current step name. * * @return string */ function wpbc_setup_wizard__get_next_step_name( $current_step ) { $route = array_merge( wpbc_setup_wizard__get_intro_route(), wpbc_setup_wizard__get_profile_route() ); $current_step_index = array_search( $current_step, $route, true ); if ( false === $current_step_index ) { return ''; } return isset( $route[ $current_step_index + 1 ] ) ? $route[ $current_step_index + 1 ] : ''; } /** * Check whether the step is rendered inside the setup wizard page. * * @param string $step_name Step name. * * @return bool */ function wpbc_setup_wizard__is_internal_step( $step_name ) { return in_array( $step_name, array( 'welcome', 'general_info', 'date_time_formats', 'bookings_types' ), true ); } /** * Detect the external setup step from the current WPBC admin page request. * * @return string */ function wpbc_setup_wizard__detect_step_from_admin_request() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $scroll_to_section = isset( $_GET['scroll_to_section'] ) ? sanitize_key( wp_unslash( $_GET['scroll_to_section'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $wpbc_ag_open = isset( $_GET['wpbc_ag_open'] ) ? sanitize_key( wp_unslash( $_GET['wpbc_ag_open'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $wpbc_calendar_section = isset( $_GET['wpbc_calendar_section'] ) ? sanitize_key( wp_unslash( $_GET['wpbc_calendar_section'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $wpbc_setup_step = isset( $_GET['wpbc_setup_step'] ) ? sanitize_key( wp_unslash( $_GET['wpbc_setup_step'] ) ) : ''; if ( '' !== $wpbc_setup_step ) { return $wpbc_setup_step; } if ( 'wpbc-settings' === $page ) { if ( 'builder_booking_form' === $tab ) { return 'form_structure'; } if ( 'form' === $tab ) { return 'form_structure'; } if ( 'calendar_settings' === $tab ) { if ( 'changeover_times' === $wpbc_calendar_section ) { return 'changeover_days'; } return 'date_selection'; } if ( 'wpbc_general_settings_calendar_tab' === $scroll_to_section ) { return 'date_selection'; } if ( 'themes' === $tab ) { return 'color_theme'; } } if ( 'wpbc-availability' === $page ) { if ( 'time_slots_availability' === $tab ) { return 'time_slots_availability'; } if ( 'working_time' === $wpbc_ag_open ) { return 'working_time'; } return 'date_availability'; } if ( 'wpbc-resources' === $page ) { return 'wizard_publish'; } if ( 'wpbc-services' === $page && 'appointment_services' === $tab ) { return 'service_provider'; } return ''; } /** * Detect an external setup step from an admin URL. * * @param string $url URL. * * @return string */ function wpbc_setup_wizard__detect_step_from_admin_url( $url ) { if ( empty( $url ) ) { return ''; } $url_parts = wp_parse_url( $url ); if ( empty( $url_parts['query'] ) ) { return ''; } $query_args = array(); wp_parse_str( $url_parts['query'], $query_args ); $page = isset( $query_args['page'] ) ? sanitize_key( $query_args['page'] ) : ''; $tab = isset( $query_args['tab'] ) ? sanitize_key( $query_args['tab'] ) : ''; $scroll_to_section = isset( $query_args['scroll_to_section'] ) ? sanitize_key( $query_args['scroll_to_section'] ) : ''; $wpbc_ag_open = isset( $query_args['wpbc_ag_open'] ) ? sanitize_key( $query_args['wpbc_ag_open'] ) : ''; $wpbc_calendar_section = isset( $query_args['wpbc_calendar_section'] ) ? sanitize_key( $query_args['wpbc_calendar_section'] ) : ''; $wpbc_setup_step = isset( $query_args['wpbc_setup_step'] ) ? sanitize_key( $query_args['wpbc_setup_step'] ) : ''; if ( '' !== $wpbc_setup_step ) { return $wpbc_setup_step; } if ( 'wpbc-settings' === $page ) { if ( 'builder_booking_form' === $tab ) { return 'form_structure'; } if ( 'form' === $tab ) { return 'form_structure'; } if ( 'calendar_settings' === $tab ) { if ( 'changeover_times' === $wpbc_calendar_section ) { return 'changeover_days'; } return 'date_selection'; } if ( 'wpbc_general_settings_calendar_tab' === $scroll_to_section ) { return 'date_selection'; } if ( 'themes' === $tab ) { return 'color_theme'; } } if ( 'wpbc-availability' === $page ) { if ( 'time_slots_availability' === $tab ) { return 'time_slots_availability'; } if ( 'working_time' === $wpbc_ag_open ) { return 'working_time'; } return 'date_availability'; } if ( 'wpbc-resources' === $page ) { return 'wizard_publish'; } if ( 'wpbc-services' === $page && 'appointment_services' === $tab ) { return 'service_provider'; } return ''; } /** * Get human-readable setup step title. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_title( $step_name ) { $mode_id = wpbc_setup_wizard__get_selected_mode_id(); if ( 'wizard_publish' === $step_name && 'appointment' === $mode_id ) { return __( 'Appointment Page', 'booking' ); } if ( 'wizard_publish' === $step_name && 'rental' === $mode_id ) { return __( 'Rental Page', 'booking' ); } if ( 'date_availability' === $step_name && 'appointment' === $mode_id ) { return __( 'Days Off', 'booking' ); } $step_titles = array( 'welcome' => __( 'Welcome', 'booking' ), 'general_info' => __( 'General Info', 'booking' ), 'date_time_formats' => __( 'Dates and Times', 'booking' ), 'bookings_types' => __( 'Booking Type', 'booking' ), 'service_provider' => __( 'Service and Provider', 'booking' ), 'date_selection' => __( 'Date Selection', 'booking' ), 'changeover_days' => __( 'Changeover Days', 'booking' ), 'working_time' => __( 'Working Time', 'booking' ), 'time_slots_availability' => __( 'Time Slots', 'booking' ), 'date_availability' => __( 'Date Availability', 'booking' ), 'form_structure' => __( 'Booking Form', 'booking' ), 'color_theme' => __( 'Color Theme', 'booking' ), 'wizard_publish' => __( 'Publish', 'booking' ), 'get_started' => __( 'Get Started', 'booking' ), ); return isset( $step_titles[ $step_name ] ) ? $step_titles[ $step_name ] : $step_name; } /** * Get action-oriented setup step heading shown in the floating setup bar. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_heading( $step_name ) { $mode_id = wpbc_setup_wizard__get_selected_mode_id(); if ( 'wizard_publish' === $step_name && 'appointment' === $mode_id ) { return __( 'Review and test the Appointment page', 'booking' ); } if ( 'wizard_publish' === $step_name && 'rental' === $mode_id ) { return __( 'Review and test the Rental page', 'booking' ); } if ( 'date_availability' === $step_name && 'appointment' === $mode_id ) { return __( 'Block Provider days off and exceptions', 'booking' ); } $step_headings = array( 'welcome' => __( 'Start the initial setup', 'booking' ), 'general_info' => __( 'Confirm your business details', 'booking' ), 'date_time_formats' => __( 'Choose date and time formats', 'booking' ), 'bookings_types' => __( 'Choose the main booking workflow', 'booking' ), 'service_provider' => __( 'Review your first Service and Provider', 'booking' ), 'form_structure' => __( 'Select and save the booking form template', 'booking' ), 'date_selection' => __( 'Set how visitors select dates', 'booking' ), 'changeover_days' => __( 'Configure changeover days', 'booking' ), 'working_time' => __( 'Define when bookings can start and end', 'booking' ), 'time_slots_availability' => __( 'Review availability for appointment slots', 'booking' ), 'date_availability' => __( 'Set date availability rules', 'booking' ), 'color_theme' => __( 'Choose the calendar appearance', 'booking' ), 'wizard_publish' => __( 'Publish the booking form on your site', 'booking' ), 'get_started' => __( 'Complete setup and manage bookings', 'booking' ), ); return isset( $step_headings[ $step_name ] ) ? $step_headings[ $step_name ] : wpbc_setup_wizard__get_step_title( $step_name ); } /** * Get setup step guidance shown in the floating setup bar. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_description( $step_name ) { $mode_id = wpbc_setup_wizard__get_selected_mode_id(); if ( 'wizard_publish' === $step_name && 'appointment' === $mode_id ) { return __( 'Open a published Appointment page to test the Service, Provider, date, and time flow. If no page is listed, run Appointment QuickStart or publish the shortcode first.', 'booking' ); } if ( 'wizard_publish' === $step_name && 'rental' === $mode_id ) { return __( 'Open a published Property booking page to test the Rental flow. If no page is listed, run Rental QuickStart or publish the booking form first.', 'booking' ); } if ( 'date_availability' === $step_name && 'appointment' === $mode_id ) { return __( 'Use the canonical Days Availability calendar to block holidays, leave, and other Provider-specific exceptions.', 'booking' ); } $step_descriptions = array( 'welcome' => __( 'Begin the guided setup. You can leave the wizard at any time and continue later from the setup bar.', 'booking' ), 'general_info' => __( 'Review the business name, contact details, and basic information used while preparing the booking configuration.', 'booking' ), 'date_time_formats' => __( 'Pick the date and time display formats that should be used in the admin panel, booking form, and customer-facing messages.', 'booking' ), 'bookings_types' => __( 'Select whether bookings use full days, appointment time slots, or changeover-style date ranges. This choice controls the next configuration steps.', 'booking' ), 'service_provider' => __( 'QuickStart created a starter Service assigned to an existing booking resource. Review its duration, buffers, price, Booking Form, and Provider assignment.', 'booking' ), 'form_structure' => __( 'Open the form template chooser, select the template that matches your booking type, and save the Booking Form page before continuing.', 'booking' ), 'date_selection' => __( 'Choose whether visitors can select one day, multiple individual days, or an available date range in the calendar. Save the General Settings page after changing this option.', 'booking' ), 'changeover_days' => __( 'Enable and review check-in/check-out changeover days, including diagonal or vertical markings and related changeover options.', 'booking' ), 'working_time' => __( 'Set the daily working hours that should be available for bookings. These hours are used as the base schedule for time-based availability.', 'booking' ), 'time_slots_availability' => __( 'Check the generated appointment slots and adjust any slot-specific availability rules before choosing the calendar appearance.', 'booking' ), 'date_availability' => __( 'Set which weekdays, dates, booking windows, and buffer rules should be available or unavailable for visitors.', 'booking' ), 'color_theme' => __( 'Choose the calendar skin and visual style so the booking form fits your site before you publish it.', 'booking' ), 'wizard_publish' => __( 'Use the Publish buttons for each booking resource to insert the booking form into an existing page, create a new page, or copy the shortcode.', 'booking' ), 'get_started' => __( 'Finish the setup wizard. After this, the setup bar will disappear and you can start managing bookings normally.', 'booking' ), ); return isset( $step_descriptions[ $step_name ] ) ? $step_descriptions[ $step_name ] : ''; } /** * Get external setup step UI integration matrix. * * Each entry describes how the floating setup bar should connect a wizard step * to an existing WPBC admin page: what to open, what to scroll/highlight, which * form/save control belongs to this step, and which JS events mean the external * page has saved successfully. * * @return array */ function wpbc_setup_wizard__get_step_ui_matrix() { $publish_step_ui = wpbc_setup_wizard__get_publish_step_ui(); $time_slots_step_ui = array( 'save_behavior' => 'link_only', 'target_selector' => '.wpbc_admin_page__tab__time_slots_availability, .wpbc_ts_page', 'scroll_selector' => '.wpbc_admin_page__tab__time_slots_availability:visible, .wpbc_ts_page:visible', 'highlight_disabled' => '1', ); $date_availability_step_ui = array( 'save_behavior' => 'link_only', 'target_selector' => '.wpbc_admin_page__tab__availability, .wpbc_ajx_availability_container', 'scroll_selector' => '.wpbc_ajx_availability_container:visible, .wpbc_admin_page__tab__availability:visible', 'highlight_disabled' => '1', ); if ( 'rental' === wpbc_setup_wizard__get_selected_mode_id() ) { $date_availability_step_ui = array( 'save_behavior' => 'manual_save_required', 'target_selector' => '.wpbc_admin_page__tab__general_availability, [data-wpbc-ag-settings-form="1"]', 'scroll_selector' => '[data-wpbc-ag-settings-form="1"]:visible, .wpbc_admin_page__tab__general_availability:visible', 'highlight_disabled' => '1', 'form_selector' => '[data-wpbc-ag-settings-form="1"]', 'save_selector' => '[data-wpbc-ag-save="1"]', 'save_ajax_action' => 'WPBC_AJX_AVAILABILITY_GENERAL_SAVE', 'save_events' => 'wpbc:availability-general:settings-saved,wpbc:setup-wizard:step-saved', ); } if ( 'appointment' === wpbc_setup_wizard__get_selected_mode_id() ) { $form_builder_url = wpbc_get_settings_url() . '&tab=builder_booking_form'; $form_builder_url = add_query_arg( array( 'wpbc_bfb_panel' => 'add_fields', 'wpbc_bfb_group' => 'fields-times', 'wpbc_bfb_focus' => 'weekday_starttime', ), $form_builder_url ); $time_slots_step_ui['secondary_action_url'] = wpbc_setup_wizard__add_setup_context_to_url( $form_builder_url, 'time_slots_availability' ); $time_slots_step_ui['secondary_action_label'] = __( 'Adjust slot-specific availability rules', 'booking' ); } return array( 'service_provider' => array( 'save_behavior' => 'link_only', 'target_selector' => '.wpbc_appointment_services_page', 'scroll_selector' => '.wpbc_appointment_services_page:visible', 'highlight_disabled' => '1', ), 'form_structure' => array( 'save_behavior' => 'manual_save_required', 'target_selector' => '.wpbc_admin_page__tab__builder_booking_form, #wpbc_form_field_free', 'scroll_selector' => '.wpbc_bfb_popup_modal__forms_loading_section:visible, #wpbc_form_field_free:visible, .wpbc_admin_page__tab__builder_booking_form:visible', 'highlight_disabled' => '1', 'form_selector' => '#wpbc_form_field_free', 'save_selector' => '#wpbc_form_field_free .wpbc_submit_button_trigger, #wpbc_form_field_free .wpbc_submit_button, [onclick*="wpbc_bfb__ajax_save_current_form"], [data-wpbc-bfb-save-source]', 'save_events' => 'wpbc:bfb:form:ajax_saved,wpbc:bfb:form:saved,wpbc:bfb:save:done,wpbc:bfb:ajax_saved,wpbc:setup-wizard:step-saved', ), 'date_selection' => array( 'save_behavior' => 'manual_save_required', 'target_selector' => '.wpbc_admin_page__tab__calendar_settings, [data-wpbc-calendar-page="1"], [data-group="settings-calendar-days-selection"], [data-wpbc-calendar-settings-form="1"]', 'scroll_selector' => '[data-group="settings-calendar-days-selection"]:visible, .wpbc_calendar__inspector_settings:visible, [data-wpbc-calendar-settings-form="1"]:visible', 'highlight_selector' => '[data-group="settings-calendar-days-selection"]:visible, .wpbc_calendar__inspector_settings:visible, [data-wpbc-calendar-settings-form="1"]:visible', 'form_selector' => '[data-wpbc-calendar-settings-form="1"], #wpbc_general_settings_form', 'save_selector' => '[data-wpbc-calendar-save="1"], #wpbc_general_settings_form .wpbc_submit_button_trigger, #wpbc_general_settings_form .wpbc_submit_button', 'save_ajax_action' => 'WPBC_AJX_SETTINGS_CALENDAR_SAVE', 'save_events' => 'wpbc:setup-wizard:step-saved', ), 'changeover_days' => array( 'save_behavior' => 'manual_save_required', 'target_selector' => '[data-group="settings-calendar-time"], [data-wpbc-calendar-changeover-settings="1"], [name="booking_range_selection_time_is_active"], [data-wpbc-calendar-page="1"]', 'scroll_selector' => '[data-group="settings-calendar-time"]:visible, [data-wpbc-calendar-changeover-settings="1"]:visible, [name="booking_range_selection_time_is_active"]:visible, [data-wpbc-calendar-page="1"]:visible', 'highlight_selector' => '[data-group="settings-calendar-time"]:visible, [data-wpbc-calendar-changeover-settings="1"]:visible, [name="booking_range_selection_time_is_active"]:visible', 'form_selector' => '[data-wpbc-calendar-settings-form="1"]', 'save_selector' => '[data-wpbc-calendar-save="1"]', 'save_ajax_action' => 'WPBC_AJX_SETTINGS_CALENDAR_SAVE', 'save_events' => 'wpbc:setup-wizard:step-saved', ), 'working_time' => array( 'save_behavior' => 'manual_save_required', 'target_selector' => '[data-group="general-availability-working-time"], .wpbc_ag_working_time_block, .wpbc_admin_page__tab__general_availability', 'scroll_selector' => '[data-group="general-availability-working-time"]:visible, .wpbc_ag_working_time_block:visible, [data-wpbc-ag-settings-form="1"]:visible', 'highlight_selector' => '[data-group="general-availability-working-time"]:visible, .wpbc_ag_working_time_block:visible', 'form_selector' => '[data-wpbc-ag-settings-form="1"]', 'save_selector' => '[data-wpbc-ag-save="1"]', 'save_ajax_action' => 'WPBC_AJX_AVAILABILITY_GENERAL_SAVE', 'save_events' => 'wpbc:availability-general:settings-saved,wpbc:setup-wizard:step-saved', 'open_action' => 'availability_section', ), 'time_slots_availability' => $time_slots_step_ui, 'date_availability' => $date_availability_step_ui, 'color_theme' => array( 'save_behavior' => 'manual_save_required', 'target_selector' => '.wpbc_admin_page__tab__themes, [data-wpbc-theme-page="1"]', 'scroll_selector' => '.wpbc_theme__inspector_settings:visible, [data-wpbc-theme-settings-form="1"]:visible, .wpbc_theme_rightbar_panels:visible', 'highlight_selector' => '.wpbc_theme__inspector_settings:visible, [data-wpbc-theme-settings-form="1"]:visible, .wpbc_theme_rightbar_panels:visible', 'form_selector' => '[data-wpbc-theme-settings-form="1"]', 'save_selector' => '[data-wpbc-theme-save="1"]', 'save_ajax_action' => 'WPBC_AJX_SETTINGS_THEMES_SAVE', 'save_events' => 'wpbc:setup-wizard:step-saved', ), 'wizard_publish' => $publish_step_ui, 'get_started' => array( 'save_behavior' => 'complete', 'target_selector' => '.wpbc_admin_page', 'scroll_selector' => '.wpbc_admin_page:visible', 'highlight_disabled' => '1', ), ); } /** * Determine whether Setup Wizard publishing should use the shared Resource catalog. * * The compatibility resolver remains authoritative so an older paid edition, * the temporary legacy preference, or the support rollback constant keeps the * wizard connected to the released legacy Resources renderer. * * @return bool True when the new Resource catalog is the effective renderer. */ function wpbc_setup_wizard__uses_new_resources_catalog() { return function_exists( 'wpbc_booking_resources_catalog_should_use_new_renderer' ) && wpbc_booking_resources_catalog_should_use_new_renderer(); } /** * Get the renderer-aware Setup Wizard publishing integration. * * The new catalog is opened through its domain action event after the initial * AJAX response. The legacy selectors are intentionally retained unchanged * while the compatibility renderer remains available in Booking Calendar 11.6. * * @param bool|null $use_new_catalog Optional renderer override for deterministic tests. Null uses the effective compatibility resolver. * * @return array Setup bar selector and action configuration. */ function wpbc_setup_wizard__get_publish_step_ui( $use_new_catalog = null ) { if ( null === $use_new_catalog ) { $use_new_catalog = wpbc_setup_wizard__uses_new_resources_catalog(); } if ( true === $use_new_catalog ) { return array( 'save_behavior' => 'link_only', 'target_selector' => '#wpbc_catalog_booking_resources', 'scroll_selector' => '#wpbc_catalog_booking_resources:visible', 'highlight_disabled' => '1', 'open_action' => 'catalog_publish', ); } return array( 'save_behavior' => 'link_only', 'target_selector' => '.ui_group__publish_btn, .wpbc_resource_field__publish, .wpbc_resource_publish, .wpbc_publish_resources, [data-wpbc-resource-publish]', 'scroll_selector' => '.ui_group__publish_btn:visible, .wpbc_resource_field__publish:visible, .wpbc_resource_publish:visible, .wpbc_publish_resources:visible, [data-wpbc-resource-publish]:visible', 'highlight_selector' => '.ui_group__publish_btn:visible, .wpbc_resource_field__publish:visible, .wpbc_resource_publish:visible, .wpbc_publish_resources:visible, [data-wpbc-resource-publish]:visible', 'highlight_all' => '1', 'open_action' => 'publish_area', ); } /** * Get one UI integration value from the external setup step matrix. * * @param string $step_name Step name. * @param string $key Matrix key. * * @return string */ function wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, $key ) { $ui_matrix = wpbc_setup_wizard__get_step_ui_matrix(); return ( isset( $ui_matrix[ $step_name ][ $key ] ) ) ? (string) $ui_matrix[ $step_name ][ $key ] : ''; } /** * Get save behavior for external setup step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_save_behavior( $step_name ) { $save_behavior = wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'save_behavior' ); return ( ! empty( $save_behavior ) ) ? $save_behavior : 'link_only'; } /** * Get DOM selector to highlight/scroll on external setup pages. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_target_selector( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'target_selector' ); } /** * Get DOM selector to scroll on external setup pages. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_scroll_selector( $step_name ) { $scroll_selector = wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'scroll_selector' ); return ( ! empty( $scroll_selector ) ) ? $scroll_selector : wpbc_setup_wizard__get_step_target_selector( $step_name ); } /** * Get DOM selector to highlight on external setup pages. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_highlight_selector( $step_name ) { if ( wpbc_setup_wizard__is_step_highlight_disabled( $step_name ) ) { return ''; } $highlight_selector = wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'highlight_selector' ); return ( ! empty( $highlight_selector ) ) ? $highlight_selector : wpbc_setup_wizard__get_step_target_selector( $step_name ); } /** * Check if external setup page highlighting is disabled for a step. * * @param string $step_name Step name. * * @return bool */ function wpbc_setup_wizard__is_step_highlight_disabled( $step_name ) { return '1' === wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'highlight_disabled' ); } /** * Get page open action for external setup pages. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_open_action( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'open_action' ); } /** * Get browser events that confirm an external setup page save. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_save_events( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'save_events' ); } /** * Get AJAX action name used by the existing page save button related to the setup step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_save_ajax_action( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'save_ajax_action' ); } /** * Get DOM selector for the existing page form related to the setup step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_form_selector( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'form_selector' ); } /** * Get selector for the existing page save button related to the setup step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_save_selector( $step_name ) { return wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'save_selector' ); } /** * Add setup guide context to a target URL. * * @param string $url URL. * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__add_setup_context_to_url( $url, $step_name ) { return add_query_arg( array( 'wpbc_setup' => '1', 'wpbc_setup_step' => $step_name, ), $url ); } /** * Get the Form Builder template search key for the selected booking type. * * @return string */ function wpbc_setup_wizard__get_bfb_template_search_key() { $booking_wizard_data_arr = get_bk_option( 'booking_wizard_data' ); if ( empty( $booking_wizard_data_arr ) || ! is_array( $booking_wizard_data_arr ) || empty( $booking_wizard_data_arr['save_and_continue__bookings_types'] ) || ! is_array( $booking_wizard_data_arr['save_and_continue__bookings_types'] ) ) { return ''; } $booking_type = isset( $booking_wizard_data_arr['save_and_continue__bookings_types']['wpbc_swp_booking_types'] ) ? $booking_wizard_data_arr['save_and_continue__bookings_types']['wpbc_swp_booking_types'] : ''; $url_sep = defined( 'WPBC_BFB_TEMPLATE_SEARCH_OR_SEPARATOR_URL' ) ? WPBC_BFB_TEMPLATE_SEARCH_OR_SEPARATOR_URL : '^'; if ( 'full_days_bookings' === $booking_type ) { return 'full-days' . $url_sep . 'dates_form'; } if ( 'time_slots_appointments' === $booking_type ) { if ( 'rangetime' === wpbc_setup_wizard__get_selected_appointments_type( $booking_wizard_data_arr ) ) { return 'times' . $url_sep . 'time slots'; } return 'appointments' . $url_sep . 'service'; } if ( 'changeover_multi_dates_bookings' === $booking_type ) { return 'changeover' . $url_sep . 'triangles'; } return ''; } /** * Get the existing admin page URL that should handle a setup step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_target_url( $step_name ) { $target_fragment = ''; if ( wpbc_setup_wizard__is_internal_step( $step_name ) ) { return add_query_arg( 'current_step', $step_name, wpbc_get_setup_wizard_page_url() ); } switch ( $step_name ) { case 'service_provider': $url = function_exists( 'wpbc_booking_modes_get_canonical_page_url' ) ? wpbc_booking_modes_get_canonical_page_url( 'wpbc-services__appointment_services' ) : admin_url( 'admin.php?page=wpbc-services&tab=appointment_services' ); break; case 'date_selection': $url = function_exists( 'wpbc_get_settings_calendar_url' ) ? wpbc_get_settings_calendar_url() : wpbc_get_settings_url() . '&tab=calendar_settings'; $url = add_query_arg( 'wpbc_calendar_section', 'days_selection', $url ); break; case 'changeover_days': $url = function_exists( 'wpbc_get_settings_calendar_url' ) ? wpbc_get_settings_calendar_url() : wpbc_get_settings_url() . '&tab=calendar_settings'; $url = add_query_arg( 'wpbc_calendar_section', 'changeover_times', $url ); break; case 'working_time': $url = function_exists( 'wpbc_get_general_availability_url' ) ? wpbc_get_general_availability_url() : admin_url( 'admin.php?page=wpbc-availability&tab=general_availability' ); $url = add_query_arg( 'wpbc_ag_open', 'working_time', $url ); break; case 'time_slots_availability': $url = function_exists( 'wpbc_get_time_slots_availability_url' ) ? wpbc_get_time_slots_availability_url() : admin_url( 'admin.php?page=wpbc-availability&tab=time_slots_availability' ); break; case 'date_availability': if ( 'rental' === wpbc_setup_wizard__get_selected_mode_id() ) { $url = function_exists( 'wpbc_get_general_availability_url' ) ? wpbc_get_general_availability_url() : admin_url( 'admin.php?page=wpbc-availability&tab=general_availability' ); break; } $canonical_url = function_exists( 'wpbc_booking_modes_get_canonical_page_url' ) ? wpbc_booking_modes_get_canonical_page_url( 'wpbc-availability__availability' ) : ''; $url = ! empty( $canonical_url ) ? $canonical_url : admin_url( 'admin.php?page=wpbc-availability&tab=availability' ); break; case 'form_structure': $url = wpbc_get_settings_url() . '&tab=builder_booking_form'; $template_search_key = wpbc_setup_wizard__get_bfb_template_search_key(); if ( ! empty( $template_search_key ) ) { $url = add_query_arg( 'auto_open_template', $template_search_key, $url ); } break; case 'color_theme': $url = function_exists( 'wpbc_get_settings_themes_url' ) ? wpbc_get_settings_themes_url() : wpbc_get_settings_url() . '&tab=themes'; break; case 'wizard_publish': $url = function_exists( 'wpbc_booking_modes_get_canonical_page_url' ) ? wpbc_booking_modes_get_canonical_page_url( 'wpbc-resources__resources' ) : ''; if ( empty( $url ) ) { $url = wpbc_get_resources_url(); } $url = add_query_arg( 'tab', 'resources', $url ); $target_fragment = wpbc_setup_wizard__uses_new_resources_catalog() ? 'wpbc_catalog_booking_resources' : 'wpbc_booking_resource_table'; break; case 'get_started': $url = wpbc_get_bookings_url() . '&tab=vm_booking_listing'; break; default: $url = wpbc_get_setup_wizard_page_url(); break; } $url = wpbc_setup_wizard__add_setup_context_to_url( $url, $step_name ); if ( ! empty( $target_fragment ) ) { $url .= '#' . $target_fragment; } return $url; } /** * Get URL that completes an external setup step and redirects to the next mapped step. * * @param string $step_name Step name. * * @return string */ function wpbc_setup_wizard__get_step_continue_url( $step_name ) { if ( wpbc_setup_wizard__is_internal_step( $step_name ) ) { return wpbc_setup_wizard__get_step_target_url( $step_name ); } return add_query_arg( array( 'wpbc_setup_wizard' => 'continue', 'wpbc_setup_step' => $step_name, 'wpbc_setup_from_page_step' => $step_name, '_wpnonce' => wp_create_nonce( 'wpbc_settings_url_nonce' ), ), wpbc_get_setup_wizard_page_url() ); } /** * Get setup step metadata for routing and UI. * * @param string $step_name Step name. * * @return array */ function wpbc_setup_wizard__get_step_route_metadata( $step_name ) { return array( 'is_external' => ! wpbc_setup_wizard__is_internal_step( $step_name ), 'target_url' => wpbc_setup_wizard__get_step_target_url( $step_name ), 'title' => wpbc_setup_wizard__get_step_title( $step_name ), 'heading' => wpbc_setup_wizard__get_step_heading( $step_name ), 'description' => wpbc_setup_wizard__get_step_description( $step_name ), 'save_behavior' => wpbc_setup_wizard__get_step_save_behavior( $step_name ), 'target_selector' => wpbc_setup_wizard__get_step_target_selector( $step_name ), 'scroll_selector' => wpbc_setup_wizard__get_step_scroll_selector( $step_name ), 'highlight_selector' => wpbc_setup_wizard__get_step_highlight_selector( $step_name ), 'highlight_disabled' => wpbc_setup_wizard__is_step_highlight_disabled( $step_name ) ? '1' : '0', 'form_selector' => wpbc_setup_wizard__get_step_form_selector( $step_name ), 'save_selector' => wpbc_setup_wizard__get_step_save_selector( $step_name ), 'save_ajax_action' => wpbc_setup_wizard__get_step_save_ajax_action( $step_name ), 'save_events' => wpbc_setup_wizard__get_step_save_events( $step_name ), 'open_action' => wpbc_setup_wizard__get_step_open_action( $step_name ), 'secondary_action_url' => wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'secondary_action_url' ), 'secondary_action_label' => wpbc_setup_wizard__get_step_ui_matrix_value( $step_name, 'secondary_action_label' ), ); } /** * Get first step after "Booking Type" for current profile. * * @return string */ function wpbc_setup_wizard__get_first_profile_step() { $route = wpbc_setup_wizard__get_profile_route(); return ( empty( $route ) ) ? 'color_theme' : $route[0]; }