PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.25.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.25.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / controllers / FrmWelcomeTourController.php

FrmWelcomeTourController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.25.1, at classes/controllers/FrmWelcomeTourController.php

624 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Welcome Tour Controller class.
4 *
5 * @package Formidable
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 die( 'You are not allowed to call this page directly.' );
10 }
11
12 /**
13 * Handles the Welcome Tour page in the admin area.
14 *
15 * @since 6.25.1
16 */
17 class FrmWelcomeTourController {
18
19 /**
20 * Option name to store Welcome Tour data.
21 *
22 * @var string
23 */
24 const CHECKLIST_OPTION = 'frm-welcome-tour';
25
26 /**
27 * The script handle.
28 *
29 * @var string
30 */
31 const SCRIPT_HANDLE = 'frm-welcome-tour';
32
33 /**
34 * Checklist data to pass to the view.
35 *
36 * @var array
37 */
38 private static $checklist = array();
39
40 /**
41 * Steps data to pass to the view.
42 *
43 * @var array
44 */
45 private static $steps = array();
46
47 /**
48 * Whether the current page is the dashboard page.
49 *
50 * @var bool
51 */
52 private static $is_dashboard_page = false;
53
54 /**
55 * The current form ID.
56 *
57 * @var int
58 */
59 private static $current_form_id = 0;
60
61 /**
62 * Initializes the Welcome Tour.
63 *
64 * @return void
65 */
66 public static function admin_init() {
67 if ( ! self::should_show_welcome_tour() ) {
68 return;
69 }
70
71 add_filter( 'frm_should_show_floating_links', '__return_false' );
72 add_filter( 'admin_body_class', self::class . '::add_admin_body_classes', 999 );
73 add_action( 'admin_enqueue_scripts', self::class . '::enqueue_assets', 15 );
74
75 if ( self::$is_dashboard_page ) {
76 add_action( 'admin_footer', self::class . '::maybe_mark_welcome_tour_as_seen', 999 );
77 return;
78 }
79
80 add_action( 'admin_footer', self::class . '::render', 999 );
81 add_action( 'frm_after_changed_form_style', self::class . '::mark_styler_step_as_completed' );
82 add_action( 'frm_after_saved_style', self::class . '::mark_styler_step_as_completed' );
83 }
84
85 /**
86 * Determines if the welcome tour should be shown based on current page context.
87 *
88 * @return bool True if welcome tour should be shown, false otherwise.
89 */
90 private static function should_show_welcome_tour() {
91 // Only show welcome tour for new installs on Formidable admin pages.
92 if ( ! FrmAppHelper::is_formidable_admin() || empty( FrmAppHelper::get_settings()->installed_after_welcome_tour_update ) ) {
93 return false;
94 }
95
96 self::$checklist = self::get_checklist();
97 if (
98 self::is_tour_completed() && ( ! empty( self::$checklist['completed_seen'] ) || ! self::get_current_form_id() )
99 || ! empty( self::$checklist['dismissed'] )
100 ) {
101 return false;
102 }
103
104 self::$is_dashboard_page = FrmDashboardController::is_dashboard_page();
105 if ( self::$is_dashboard_page ) {
106 return empty( self::$checklist['seen'] );
107 }
108
109 self::setup_checklist_progress();
110 return self::should_show_checklist();
111 }
112
113 /**
114 * Sets up the checklist progress.
115 *
116 * @return void
117 */
118 public static function setup_checklist_progress() {
119 self::$steps = self::get_steps();
120 $step_keys = self::$steps['keys'];
121 $active_step = 0;
122
123 foreach ( $step_keys as $index => $step_key ) {
124 $completed_step = isset( self::$checklist['completed_steps'][ $step_key ] );
125
126 if ( false === $completed_step ) {
127 switch ( $step_key ) {
128 case 'create-form':
129 $completed_step = self::more_than_the_default_form_exists();
130 break;
131 case 'embed-form':
132 $completed_step = self::check_for_form_embeds();
133 break;
134 }
135 }
136
137 if ( $completed_step ) {
138 self::$checklist['completed_steps'][ $step_key ] = true;
139 }
140
141 // Count completed steps from start until gap found.
142 if ( $completed_step && $index === $active_step ) {
143 $active_step++;
144 }
145
146 self::$steps['steps'][ $index ]['completed'] = $completed_step;
147 }//end foreach
148
149 self::$checklist['active_step'] = $active_step;
150
151 if ( $active_step === count( $step_keys ) ) {
152 self::$checklist['done'] = true;
153 self::$checklist['active_step_key'] = 'completed';
154 } else {
155 self::$checklist['active_step_key'] = $step_keys[ $active_step ];
156 }
157
158 self::save_checklist();
159 }
160
161 /**
162 * Gets the checklist steps.
163 *
164 * @return array
165 */
166 private static function get_steps() {
167 $steps = array(
168 'create-form' => array(
169 'title' => __( 'Create your first form', 'formidable' ),
170 'description' => __( 'Start from scratch or jump in with one of our ready-to-use templates.', 'formidable' ),
171 ),
172 'add-fields' => array(
173 'title' => __( 'Add fields to your form', 'formidable' ),
174 'description' => __( 'Click or drag fields from the left to add them to your form. Edit and/or delete them as needed.', 'formidable' ),
175 ),
176 'style-form' => array(
177 'title' => __( 'Style your form', 'formidable' ),
178 '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
179 'link' => FrmStylesHelper::get_list_url( self::get_current_form_id() ),
180 ),
181 'embed-form' => array(
182 'title' => __( 'Embed in a page', 'formidable' ),
183 'description' => __( 'Time to get some responses! Add your brand new form to a current page, or embed it on a new one.', 'formidable' ),
184 'link' => FrmStylesHelper::get_list_url( self::get_current_form_id() ),
185 ),
186 );
187
188 $steps_keys = array_keys( $steps );
189
190 return array(
191 'keys' => $steps_keys,
192 'steps' => self::fill_step_completed_data( $steps, $steps_keys ),
193 );
194 }
195
196 /**
197 * Fills the steps with the completed data.
198 *
199 * @param array $steps The steps to fill.
200 * @param array $steps_keys The steps keys.
201 * @return array The steps with the completed data.
202 */
203 private static function fill_step_completed_data( $steps, $steps_keys ) {
204 return array_map(
205 function ( $step, $step_key ) {
206 $step['completed'] = isset( self::$checklist['completed_steps'][ $step_key ] );
207 return $step;
208 },
209 $steps,
210 $steps_keys
211 );
212 }
213
214 /**
215 * Get spotlight data for the current active step.
216 *
217 * @return array The spotlight data.
218 */
219 private static function get_spotlight_data() {
220 $spotlight_data = array();
221
222 switch ( self::$checklist['active_step_key'] ) {
223 case 'create-form':
224 $spotlight_data = array(
225 'target' => '#frm-form-templates-create-form-divider',
226 'left-position' => 'middle',
227 );
228 break;
229 case 'add-fields':
230 $spotlight_data = array(
231 'target' => '.frm-settings-panel .frm-tabs-navs li.frm-active',
232 'left-position' => '140px',
233 );
234 break;
235 case 'style-form':
236 $spotlight_data = array(
237 'target' => '#frm_style_sidebar .frm-style-card > div',
238 'left-position' => 'end',
239 'offset' => array(
240 'top' => -22,
241 'left' => 16,
242 ),
243 );
244 break;
245 case 'embed-form':
246 $spotlight_data = array(
247 'target' => '#frm-embed-action',
248 'left-position' => 'middle',
249 'placement' => 'bottom',
250 );
251 break;
252 default:
253 break;
254 }//end switch
255
256 return array_merge( self::$steps['steps'][ self::$checklist['active_step'] ], $spotlight_data );
257 }
258
259 /**
260 * Marks the welcome tour as seen if it hasn't been seen yet.
261 *
262 * @return void
263 */
264 public static function maybe_mark_welcome_tour_as_seen() {
265 if ( ! empty( self::$checklist['seen'] ) ) {
266 return;
267 }
268
269 self::$checklist['seen'] = true;
270 self::save_checklist();
271 }
272
273 /**
274 * Render the welcome tour elements.
275 *
276 * @return void
277 */
278 public static function render() {
279 $view_path = FrmAppHelper::plugin_path() . '/classes/views/welcome-tour/';
280 $is_tour_completed = self::is_tour_completed();
281 $current_form_id = self::get_current_form_id();
282
283 if ( $is_tour_completed ) {
284 if ( ! $current_form_id ) {
285 return;
286 }
287 self::mark_completed_as_seen();
288 $steps_path = $view_path . 'steps/step-completed.php';
289 } else {
290 $steps_path = $view_path . 'steps/list.php';
291 $steps = array_combine( self::$steps['keys'], self::$steps['steps'] );
292 $active_step = self::$checklist['active_step_key'];
293 $spotlight = self::get_spotlight_data();
294 }
295
296 include $view_path . 'index.php';
297 }
298
299 /**
300 * Shows links after completing the Welcome tour.
301 *
302 * @param int $current_form_id Current form ID.
303 */
304 public static function show_completed_links( $current_form_id ) {
305 $links = array(
306 'setup-email-notification' => array(
307 'url' => admin_url( 'admin.php?page=formidable&frm_action=settings&id=' . $current_form_id . '&t=email_settings' ),
308 'text' => __( 'Setup email notifications', 'formidable' ),
309 ),
310 'customize-success-message' => array(
311 'url' => admin_url( 'admin.php?page=formidable&frm_action=settings&id=' . $current_form_id . '&t=email_settings' ),
312 'text' => __( 'Customize success message', 'formidable' ),
313 ),
314 'manage-entries' => array(
315 'url' => admin_url( 'admin.php?page=formidable-entries' ),
316 'text' => __( 'Manage form entries', 'formidable' ),
317 ),
318 'explore-addon' => array(
319 'url' => admin_url( 'admin.php?page=formidable-addons' ),
320 'text' => __( 'Explore integrations', 'formidable' ),
321 ),
322 );
323
324 $button_attrs = array(
325 'class' => 'frm-usage-tracking-flow-click button frm-button-secondary frm-button-sm frm-mb-2xs',
326 'target' => '_blank',
327 'rel' => 'noopener',
328 'data-tracking-key' => 'welcome_tour_completed_link_click',
329 );
330
331 foreach ( $links as $key => $link ) {
332 $attrs = $button_attrs + array( 'data-tracking-value' => $key );
333 ?>
334 <a href="<?php echo esc_url( $link['url'] ); ?>" <?php FrmAppHelper::array_to_html_params( $attrs, true ); ?>>
335 <?php echo esc_html( $link['text'] ); ?>
336 </a>
337 <?php
338 }
339 }
340
341 /**
342 * Checks if the checklist should be shown.
343 *
344 * @return bool True if the checklist should be shown, false otherwise.
345 */
346 private static function should_show_checklist() {
347 if ( self::is_tour_completed() ) {
348 // Show the completed step only if user hasn't seen it yet.
349 return empty( self::$checklist['completed_seen'] );
350 }
351
352 $active_step = self::$checklist['active_step_key'];
353 $page = FrmAppHelper::simple_get( 'page' );
354 $is_form_templates_page = FrmFormTemplatesController::PAGE_SLUG === $page;
355 $is_form_builder_page = FrmAppHelper::is_form_builder_page();
356 $is_style_editor_page = FrmAppHelper::is_style_editor_page();
357
358 switch ( $active_step ) {
359 case 'create-form':
360 return $is_form_templates_page;
361 case 'add-fields':
362 return $is_form_builder_page;
363 case 'style-form':
364 case 'embed-form':
365 case 'completed':
366 return $is_form_builder_page || $is_style_editor_page;
367 default:
368 return false;
369 }
370 }
371
372 /**
373 * AJAX callback to mark a checklist step as completed.
374 *
375 * @return void
376 */
377 public static function ajax_mark_checklist_step_as_completed() {
378 check_ajax_referer( 'frm_ajax', 'nonce' );
379 FrmAppHelper::permission_check( 'frm_edit_forms' );
380
381 $step_key = FrmAppHelper::get_post_param( 'step_key' );
382 if ( ! $step_key ) {
383 wp_send_json_error( __( 'Invalid step', 'formidable' ) );
384 }
385
386 self::$checklist = self::get_checklist();
387 self::$checklist['completed_steps'][ $step_key ] = true;
388 self::save_checklist();
389
390 wp_send_json_success();
391 }
392
393 /**
394 * AJAX callback to dismiss the welcome tour.
395 *
396 * @return void
397 */
398 public static function ajax_dismiss_welcome_tour() {
399 check_ajax_referer( 'frm_ajax', 'nonce' );
400 FrmAppHelper::permission_check( 'frm_edit_forms' );
401
402 self::$checklist = self::get_checklist();
403 self::$checklist['dismissed'] = true;
404 self::save_checklist();
405
406 wp_send_json_success();
407 }
408
409 /**
410 * Checks if more than the default form exists.
411 *
412 * @return bool True if more than the default form exists, false otherwise.
413 */
414 private static function more_than_the_default_form_exists() {
415 $form_keys = FrmDb::get_col( 'frm_forms', array(), 'form_key' );
416 if ( count( $form_keys ) > 1 ) {
417 return true;
418 }
419
420 return $form_keys && ! in_array( 'contact-form', $form_keys, true );
421 }
422
423 /**
424 * Checks if there are form embeds.
425 *
426 * @return bool True if there are form embeds, false otherwise.
427 */
428 private static function check_for_form_embeds() {
429 global $wpdb;
430 $result = $wpdb->get_var( "SELECT 1 FROM {$wpdb->posts} WHERE post_content LIKE '%[formidable %' LIMIT 1" );
431 return '1' === $result;
432 }
433
434 /**
435 * Get the active step.
436 *
437 * @return int
438 */
439 private static function get_active_step() {
440 return self::$checklist['active_step'] ?? 0;
441 }
442
443 /**
444 * Mark the styler step as completed.
445 *
446 * @return void
447 */
448 public static function mark_styler_step_as_completed() {
449 if ( isset( self::$checklist['completed_steps']['style-form'] ) ) {
450 return;
451 }
452
453 self::$checklist['completed_steps']['style-form'] = true;
454 self::save_checklist();
455 }
456
457 /**
458 * Adds custom classes to the existing string of admin body classes.
459 *
460 * @param string $classes Existing body classes.
461 * @return string Updated list of body classes, including the newly added classes.
462 */
463 public static function add_admin_body_classes( $classes ) {
464 return $classes . ' frm-admin-welcome-tour';
465 }
466
467 /**
468 * Enqueues the Welcome Tour page scripts and styles.
469 *
470 * @return void
471 */
472 public static function enqueue_assets() {
473 $plugin_url = FrmAppHelper::plugin_url();
474 $version = FrmAppHelper::plugin_version();
475
476 wp_enqueue_style( self::SCRIPT_HANDLE, $plugin_url . '/css/admin/welcome-tour.css', array(), $version );
477
478 wp_register_script( self::SCRIPT_HANDLE, $plugin_url . '/js/welcome-tour.js', array( 'wp-i18n' ), $version, true );
479 wp_localize_script( self::SCRIPT_HANDLE, 'frmWelcomeTourVars', self::get_js_variables() );
480 wp_enqueue_script( self::SCRIPT_HANDLE );
481 wp_set_script_translations( self::SCRIPT_HANDLE, 'formidable' );
482
483 FrmAppHelper::dequeue_extra_global_scripts();
484 }
485
486 /**
487 * Get the Welcome Tour JS variables as an array.
488 *
489 * @return array
490 */
491 private static function get_js_variables() {
492 return array(
493 'IS_DASHBOARD_PAGE' => self::$is_dashboard_page,
494 'IS_WELCOME_TOUR_SEEN' => ! empty( self::$checklist['seen'] ),
495 'PROGRESS_BAR_PERCENT' => self::get_welcome_tour_progress_bar_percent(),
496 'TOUR_URL' => admin_url( 'admin.php?page=formidable-form-templates' ),
497 );
498 }
499
500 /**
501 * Get the Welcome Tour progress bar percentage.
502 *
503 * @return int
504 */
505 private static function get_welcome_tour_progress_bar_percent() {
506 if ( ! self::$steps ) {
507 return 0;
508 }
509
510 $percent = self::get_active_step() / count( self::$steps['keys'] ) * 100;
511
512 return (int) $percent;
513 }
514
515 /**
516 * Saves the checklist data.
517 *
518 * @param array|null $checklist The checklist data to set.
519 */
520 public static function save_checklist( $checklist = null ) {
521 update_option( self::CHECKLIST_OPTION, $checklist ?? self::$checklist, false );
522 }
523
524 /**
525 * Gets the checklist data.
526 *
527 * @return array The checklist data.
528 */
529 public static function get_checklist() {
530 return get_option(
531 self::CHECKLIST_OPTION,
532 array(
533 'completed_steps' => array(),
534 'active_step_key' => 'create-form',
535 )
536 );
537 }
538
539 /**
540 * Build a tracked URL with UTM parameters and affiliate tracking.
541 *
542 * @param string $url The base URL to process.
543 * @return string The processed URL with UTM parameters and affiliate tracking.
544 */
545 public static function make_tracked_url( $url ) {
546 $utm_params = array(
547 'utm_source' => 'WordPress',
548 'utm_medium' => 'welcome-tour',
549 'utm_campaign' => 'liteplugin',
550 );
551
552 return FrmAppHelper::make_affiliate_url( add_query_arg( $utm_params, $url ) );
553 }
554
555 /**
556 * Get the current form ID.
557 *
558 * @return int The current form ID.
559 */
560 public static function get_current_form_id() {
561 if ( self::$current_form_id ) {
562 return self::$current_form_id;
563 }
564
565 self::$current_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
566 if ( ! self::$current_form_id ) {
567 self::$current_form_id = FrmAppHelper::simple_get( 'id', 'absint', 0 );
568 }
569
570 return self::$current_form_id;
571 }
572
573 /**
574 * Checks if the tour is completed.
575 *
576 * @return bool True if the tour is completed, false otherwise.
577 */
578 private static function is_tour_completed() {
579 return ! empty( self::$checklist['done'] );
580 }
581
582 /**
583 * Marks the completed state as seen.
584 *
585 * @return void
586 */
587 private static function mark_completed_as_seen() {
588 if ( ! empty( self::$checklist['completed_seen'] ) ) {
589 return;
590 }
591
592 self::$checklist['completed_seen'] = true;
593 self::save_checklist();
594 }
595
596 /**
597 * Gets usage tracking data.
598 *
599 * @return array
600 */
601 public static function get_usage_data() {
602 // Do not use the get_checklist() method to prevent adding default value.
603 $option = get_option( self::CHECKLIST_OPTION );
604 if ( ! $option ) {
605 // Welcome tour doesn't show on this site.
606 return array();
607 }
608
609 $usage_data = array();
610 $steps = self::get_steps();
611
612 foreach ( $steps as $key => $step ) {
613 $usage_data[ 'completed_step_' . $key ] = empty( $option['completed_steps'][ $key ] ) ? 0 : 1;
614 }
615
616 $usage_data['done'] = empty( $option['done'] ) ? 0 : 1;
617
618 // If dismissed, the dismissed step is the active step.
619 $usage_data['dismissed'] = empty( $option['dismissed'] ) ? 0 : $option['active_step_key'];
620
621 return $usage_data;
622 }
623 }
624