Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
forbidals
/
formidable
/
classes
/
controllers
:
FrmWelcomeTourController.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * Welcome Tour Controller class. * * @package Formidable */ if ( ! defined( 'ABSPATH' ) ) { die( 'You are not allowed to call this page directly.' ); } /** * Handles the Welcome Tour page in the admin area. * * @since 6.25.1 */ class FrmWelcomeTourController { /** * Option name to store Welcome Tour data. * * @var string */ const CHECKLIST_OPTION = 'frm-welcome-tour'; /** * The script handle. * * @var string */ const SCRIPT_HANDLE = 'frm-welcome-tour'; /** * Checklist data to pass to the view. * * @var array */ private static $checklist = array(); /** * Steps data to pass to the view. * * @var array */ private static $steps = array(); /** * Whether the current page is the dashboard page. * * @var bool */ private static $is_dashboard_page = false; /** * The current form ID. * * @var int */ private static $current_form_id = 0; /** * Initializes the Welcome Tour. * * @return void */ public static function admin_init() { if ( ! self::should_show_welcome_tour() ) { return; } add_filter( 'frm_should_show_floating_links', '__return_false' ); add_filter( 'admin_body_class', self::class . '::add_admin_body_classes', 999 ); add_action( 'admin_enqueue_scripts', self::class . '::enqueue_assets', 15 ); if ( self::$is_dashboard_page ) { add_action( 'admin_footer', self::class . '::maybe_mark_welcome_tour_as_seen', 999 ); return; } add_action( 'admin_footer', self::class . '::render', 999 ); add_action( 'frm_after_changed_form_style', self::class . '::mark_styler_step_as_completed' ); add_action( 'frm_after_saved_style', self::class . '::mark_styler_step_as_completed' ); } /** * Determines if the welcome tour should be shown based on current page context. * * @return bool True if welcome tour should be shown, false otherwise. */ private static function should_show_welcome_tour() { // Only show welcome tour for new installs on Formidable admin pages. if ( ! FrmAppHelper::is_formidable_admin() || empty( FrmAppHelper::get_settings()->installed_after_welcome_tour_update ) ) { return false; } self::$checklist = self::get_checklist(); if ( self::is_tour_completed() && ( ! empty( self::$checklist['completed_seen'] ) || ! self::get_current_form_id() ) || ! empty( self::$checklist['dismissed'] ) ) { return false; } self::$is_dashboard_page = FrmDashboardController::is_dashboard_page(); if ( self::$is_dashboard_page ) { return empty( self::$checklist['seen'] ); } self::setup_checklist_progress(); return self::should_show_checklist(); } /** * Sets up the checklist progress. * * @return void */ public static function setup_checklist_progress() { self::$steps = self::get_steps(); $step_keys = self::$steps['keys']; $active_step = 0; foreach ( $step_keys as $index => $step_key ) { $completed_step = isset( self::$checklist['completed_steps'][ $step_key ] ); if ( false === $completed_step ) { switch ( $step_key ) { case 'create-form': $completed_step = self::more_than_the_default_form_exists(); break; case 'embed-form': $completed_step = self::check_for_form_embeds(); break; } } if ( $completed_step ) { self::$checklist['completed_steps'][ $step_key ] = true; } // Count completed steps from start until gap found. if ( $completed_step && $index === $active_step ) { $active_step++; } self::$steps['steps'][ $index ]['completed'] = $completed_step; }//end foreach self::$checklist['active_step'] = $active_step; if ( $active_step === count( $step_keys ) ) { self::$checklist['done'] = true; self::$checklist['active_step_key'] = 'completed'; } else { self::$checklist['active_step_key'] = $step_keys[ $active_step ]; } self::save_checklist(); } /** * Gets the checklist steps. * * @return array */ private static function get_steps() { $steps = array( 'create-form' => array( 'title' => __( 'Create your first form', 'formidable' ), 'description' => __( 'Start from scratch or jump in with one of our ready-to-use templates.', 'formidable' ), ), 'add-fields' => array( 'title' => __( 'Add fields to your form', 'formidable' ), 'description' => __( 'Click or drag fields from the left to add them to your form. Edit and/or delete them as needed.', 'formidable' ), ), 'style-form' => array( 'title' => __( 'Style your form', 'formidable' ), 'description' => __( 'Our default style looks great, but feel free to modify it! Change the color, font size, spacing, or whatever else you\'d like.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong 'link' => FrmStylesHelper::get_list_url( self::get_current_form_id() ), ), 'embed-form' => array( 'title' => __( 'Embed in a page', 'formidable' ), 'description' => __( 'Time to get some responses! Add your brand new form to a current page, or embed it on a new one.', 'formidable' ), 'link' => FrmStylesHelper::get_list_url( self::get_current_form_id() ), ), ); $steps_keys = array_keys( $steps ); return array( 'keys' => $steps_keys, 'steps' => self::fill_step_completed_data( $steps, $steps_keys ), ); } /** * Fills the steps with the completed data. * * @param array $steps The steps to fill. * @param array $steps_keys The steps keys. * * @return array The steps with the completed data. */ private static function fill_step_completed_data( $steps, $steps_keys ) { return array_map( function ( $step, $step_key ) { $step['completed'] = isset( self::$checklist['completed_steps'][ $step_key ] ); return $step; }, $steps, $steps_keys ); } /** * Get spotlight data for the current active step. * * @return array The spotlight data. */ private static function get_spotlight_data() { $spotlight_data = array(); switch ( self::$checklist['active_step_key'] ) { case 'create-form': $spotlight_data = array( 'target' => '#frm-form-templates-create-form-divider', 'left-position' => 'middle', ); break; case 'add-fields': $spotlight_data = array( 'target' => '.frm-settings-panel .frm-tabs-navs li.frm-active', 'left-position' => '140px', ); break; case 'style-form': $spotlight_data = array( 'target' => '#frm_style_sidebar .frm-style-card > div', 'left-position' => 'end', 'offset' => array( 'top' => -22, 'left' => 16, ), ); break; case 'embed-form': $spotlight_data = array( 'target' => '#frm-embed-action', 'left-position' => 'middle', 'placement' => 'bottom', ); break; default: break; }//end switch return array_merge( self::$steps['steps'][ self::$checklist['active_step'] ], $spotlight_data ); } /** * Marks the welcome tour as seen if it hasn't been seen yet. * * @return void */ public static function maybe_mark_welcome_tour_as_seen() { if ( ! empty( self::$checklist['seen'] ) ) { return; } self::$checklist['seen'] = true; self::save_checklist(); } /** * Render the welcome tour elements. * * @return void */ public static function render() { $view_path = FrmAppHelper::plugin_path() . '/classes/views/welcome-tour/'; $is_tour_completed = self::is_tour_completed(); $current_form_id = self::get_current_form_id(); if ( $is_tour_completed ) { if ( ! $current_form_id ) { return; } self::mark_completed_as_seen(); $steps_path = $view_path . 'steps/step-completed.php'; } else { $steps_path = $view_path . 'steps/list.php'; $steps = array_combine( self::$steps['keys'], self::$steps['steps'] ); $active_step = self::$checklist['active_step_key']; $spotlight = self::get_spotlight_data(); } include $view_path . 'index.php'; } /** * Shows links after completing the Welcome tour. * * @param int $current_form_id Current form ID. * * @return void */ public static function show_completed_links( $current_form_id ) { $links = array( 'setup-email-notification' => array( 'url' => admin_url( 'admin.php?page=formidable&frm_action=settings&id=' . $current_form_id . '&t=email_settings' ), 'text' => __( 'Setup email notifications', 'formidable' ), ), 'customize-success-message' => array( 'url' => admin_url( 'admin.php?page=formidable&frm_action=settings&id=' . $current_form_id . '&t=email_settings' ), 'text' => __( 'Customize success message', 'formidable' ), ), 'manage-entries' => array( 'url' => admin_url( 'admin.php?page=formidable-entries' ), 'text' => __( 'Manage form entries', 'formidable' ), ), 'explore-addon' => array( 'url' => admin_url( 'admin.php?page=formidable-addons' ), 'text' => __( 'Explore integrations', 'formidable' ), ), ); $button_attrs = array( 'class' => 'frm-usage-tracking-flow-click button frm-button-secondary frm-button-sm frm-mb-2xs', 'target' => '_blank', 'rel' => 'noopener', 'data-tracking-key' => 'welcome_tour_completed_link_click', ); foreach ( $links as $key => $link ) { $attrs = $button_attrs + array( 'data-tracking-value' => $key ); ?> <a href="<?php echo esc_url( $link['url'] ); ?>" <?php FrmAppHelper::array_to_html_params( $attrs, true ); ?>> <?php echo esc_html( $link['text'] ); ?> </a> <?php } } /** * Checks if the checklist should be shown. * * @return bool True if the checklist should be shown, false otherwise. */ private static function should_show_checklist() { if ( self::is_tour_completed() ) { // Show the completed step only if user hasn't seen it yet. return empty( self::$checklist['completed_seen'] ); } $active_step = self::$checklist['active_step_key']; $page = FrmAppHelper::simple_get( 'page' ); $is_form_templates_page = FrmFormTemplatesController::PAGE_SLUG === $page; $is_form_builder_page = FrmAppHelper::is_form_builder_page(); $is_style_editor_page = FrmAppHelper::is_style_editor_page(); switch ( $active_step ) { case 'create-form': return $is_form_templates_page; case 'add-fields': return $is_form_builder_page; case 'style-form': case 'embed-form': case 'completed': return $is_form_builder_page || $is_style_editor_page; default: return false; } } /** * AJAX callback to mark a checklist step as completed. * * @return void */ public static function ajax_mark_checklist_step_as_completed() { check_ajax_referer( 'frm_ajax', 'nonce' ); FrmAppHelper::permission_check( 'frm_edit_forms' ); $step_key = FrmAppHelper::get_post_param( 'step_key' ); if ( ! $step_key ) { wp_send_json_error( __( 'Invalid step', 'formidable' ) ); } self::$checklist = self::get_checklist(); self::$checklist['completed_steps'][ $step_key ] = true; self::save_checklist(); wp_send_json_success(); } /** * AJAX callback to dismiss the welcome tour. * * @return void */ public static function ajax_dismiss_welcome_tour() { check_ajax_referer( 'frm_ajax', 'nonce' ); FrmAppHelper::permission_check( 'frm_edit_forms' ); self::$checklist = self::get_checklist(); self::$checklist['dismissed'] = true; self::save_checklist(); wp_send_json_success(); } /** * Checks if more than the default form exists. * * @return bool True if more than the default form exists, false otherwise. */ private static function more_than_the_default_form_exists() { $form_keys = FrmDb::get_col( 'frm_forms', array(), 'form_key' ); if ( count( $form_keys ) > 1 ) { return true; } return $form_keys && ! in_array( 'contact-form', $form_keys, true ); } /** * Checks if there are form embeds. * * @return bool True if there are form embeds, false otherwise. */ private static function check_for_form_embeds() { global $wpdb; $result = $wpdb->get_var( "SELECT 1 FROM {$wpdb->posts} WHERE post_content LIKE '%[formidable %' LIMIT 1" ); return '1' === $result; } /** * Get the active step. * * @return int */ private static function get_active_step() { return self::$checklist['active_step'] ?? 0; } /** * Mark the styler step as completed. * * @return void */ public static function mark_styler_step_as_completed() { if ( isset( self::$checklist['completed_steps']['style-form'] ) ) { return; } self::$checklist['completed_steps']['style-form'] = true; self::save_checklist(); } /** * Adds custom classes to the existing string of admin body classes. * * @param string $classes Existing body classes. * * @return string Updated list of body classes, including the newly added classes. */ public static function add_admin_body_classes( $classes ) { return $classes . ' frm-admin-welcome-tour'; } /** * Enqueues the Welcome Tour page scripts and styles. * * @return void */ public static function enqueue_assets() { $plugin_url = FrmAppHelper::plugin_url(); $version = FrmAppHelper::plugin_version(); wp_enqueue_style( self::SCRIPT_HANDLE, $plugin_url . '/css/admin/welcome-tour.css', array(), $version ); wp_register_script( self::SCRIPT_HANDLE, $plugin_url . '/js/welcome-tour.js', array( 'wp-i18n' ), $version, true ); wp_localize_script( self::SCRIPT_HANDLE, 'frmWelcomeTourVars', self::get_js_variables() ); wp_enqueue_script( self::SCRIPT_HANDLE ); wp_set_script_translations( self::SCRIPT_HANDLE, 'formidable' ); FrmAppHelper::dequeue_extra_global_scripts(); } /** * Get the Welcome Tour JS variables as an array. * * @return array */ private static function get_js_variables() { return array( 'IS_DASHBOARD_PAGE' => self::$is_dashboard_page, 'IS_WELCOME_TOUR_SEEN' => ! empty( self::$checklist['seen'] ), 'PROGRESS_BAR_PERCENT' => self::get_welcome_tour_progress_bar_percent(), 'TOUR_URL' => admin_url( 'admin.php?page=formidable-form-templates' ), ); } /** * Get the Welcome Tour progress bar percentage. * * @return int */ private static function get_welcome_tour_progress_bar_percent() { if ( ! self::$steps ) { return 0; } $percent = self::get_active_step() / count( self::$steps['keys'] ) * 100; return (int) $percent; } /** * Saves the checklist data. * * @param array|null $checklist The checklist data to set. * * @return void */ public static function save_checklist( $checklist = null ) { update_option( self::CHECKLIST_OPTION, $checklist ?? self::$checklist, false ); } /** * Gets the checklist data. * * @return array The checklist data. */ public static function get_checklist() { return get_option( self::CHECKLIST_OPTION, array( 'completed_steps' => array(), 'active_step_key' => 'create-form', ) ); } /** * Build a tracked URL with UTM parameters and affiliate tracking. * * @param string $url The base URL to process. * * @return string The processed URL with UTM parameters and affiliate tracking. */ public static function make_tracked_url( $url ) { $url = FrmAppHelper::maybe_add_missing_utm( $url, array( 'campaign' => 'welcome-tour', ) ); return FrmAppHelper::make_affiliate_url( $url ); } /** * Get the current form ID. * * @return int The current form ID. */ public static function get_current_form_id() { if ( self::$current_form_id ) { return self::$current_form_id; } self::$current_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 ); if ( ! self::$current_form_id ) { self::$current_form_id = FrmAppHelper::simple_get( 'id', 'absint', 0 ); } return self::$current_form_id; } /** * Checks if the tour is completed. * * @return bool True if the tour is completed, false otherwise. */ private static function is_tour_completed() { return ! empty( self::$checklist['done'] ); } /** * Marks the completed state as seen. * * @return void */ private static function mark_completed_as_seen() { if ( ! empty( self::$checklist['completed_seen'] ) ) { return; } self::$checklist['completed_seen'] = true; self::save_checklist(); } /** * Gets usage tracking data. * * @return array */ public static function get_usage_data() { // Do not use the get_checklist() method to prevent adding default value. $option = get_option( self::CHECKLIST_OPTION ); if ( ! $option ) { // Welcome tour doesn't show on this site. return array(); } $usage_data = array(); $steps = self::get_steps(); foreach ( $steps as $key => $step ) { $usage_data[ 'completed_step_' . $key ] = empty( $option['completed_steps'][ $key ] ) ? 0 : 1; } $usage_data['done'] = empty( $option['done'] ) ? 0 : 1; // If dismissed, the dismissed step is the active step. $usage_data['dismissed'] = empty( $option['dismissed'] ) ? 0 : $option['active_step_key']; return $usage_data; } }