PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.33
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.33
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.33, at classes/controllers/FrmWelcomeTourController.php

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