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

639 lines 16.9 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 $is_style_editor_page = FrmAppHelper::is_style_editor_page();
364
365 switch ( $active_step ) {
366 case 'create-form':
367 return $is_form_templates_page;
368 case 'add-fields':
369 return $is_form_builder_page;
370 case 'style-form':
371 case 'embed-form':
372 case 'completed':
373 return $is_form_builder_page || $is_style_editor_page;
374 default:
375 return false;
376 }
377 }
378
379 /**
380 * AJAX callback to mark a checklist step as completed.
381 *
382 * @return void
383 */
384 public static function ajax_mark_checklist_step_as_completed() {
385 check_ajax_referer( 'frm_ajax', 'nonce' );
386 FrmAppHelper::permission_check( 'frm_edit_forms' );
387
388 $step_key = FrmAppHelper::get_post_param( 'step_key' );
389
390 if ( ! $step_key ) {
391 wp_send_json_error( __( 'Invalid step', 'formidable' ) );
392 }
393
394 self::$checklist = self::get_checklist();
395 self::$checklist['completed_steps'][ $step_key ] = true;
396 self::save_checklist();
397
398 wp_send_json_success();
399 }
400
401 /**
402 * AJAX callback to dismiss the welcome tour.
403 *
404 * @return void
405 */
406 public static function ajax_dismiss_welcome_tour() {
407 check_ajax_referer( 'frm_ajax', 'nonce' );
408 FrmAppHelper::permission_check( 'frm_edit_forms' );
409
410 self::$checklist = self::get_checklist();
411 self::$checklist['dismissed'] = true;
412 self::save_checklist();
413
414 wp_send_json_success();
415 }
416
417 /**
418 * Checks if more than the default form exists.
419 *
420 * @return bool True if more than the default form exists, false otherwise.
421 */
422 private static function more_than_the_default_form_exists() {
423 $form_keys = FrmDb::get_col( 'frm_forms', array(), 'form_key' );
424
425 if ( count( $form_keys ) > 1 ) {
426 return true;
427 }
428
429 return $form_keys && ! in_array( 'contact-form', $form_keys, true );
430 }
431
432 /**
433 * Checks if there are form embeds.
434 *
435 * @return bool True if there are form embeds, false otherwise.
436 */
437 private static function check_for_form_embeds() {
438 global $wpdb;
439 $result = $wpdb->get_var( "SELECT 1 FROM {$wpdb->posts} WHERE post_content LIKE '%[formidable %' LIMIT 1" );
440 return '1' === $result;
441 }
442
443 /**
444 * Get the active step.
445 *
446 * @return int
447 */
448 private static function get_active_step() {
449 return self::$checklist['active_step'] ?? 0;
450 }
451
452 /**
453 * Mark the styler step as completed.
454 *
455 * @return void
456 */
457 public static function mark_styler_step_as_completed() {
458 if ( isset( self::$checklist['completed_steps']['style-form'] ) ) {
459 return;
460 }
461
462 self::$checklist['completed_steps']['style-form'] = true;
463 self::save_checklist();
464 }
465
466 /**
467 * Adds custom classes to the existing string of admin body classes.
468 *
469 * @param string $classes Existing body classes.
470 *
471 * @return string Updated list of body classes, including the newly added classes.
472 */
473 public static function add_admin_body_classes( $classes ) {
474 return $classes . ' frm-admin-welcome-tour';
475 }
476
477 /**
478 * Enqueues the Welcome Tour page scripts and styles.
479 *
480 * @return void
481 */
482 public static function enqueue_assets() {
483 $plugin_url = FrmAppHelper::plugin_url();
484 $version = FrmAppHelper::plugin_version();
485
486 wp_enqueue_style( self::SCRIPT_HANDLE, $plugin_url . '/css/admin/welcome-tour.css', array(), $version );
487
488 wp_register_script( self::SCRIPT_HANDLE, $plugin_url . '/js/welcome-tour.js', array( 'wp-i18n' ), $version, true );
489 wp_localize_script( self::SCRIPT_HANDLE, 'frmWelcomeTourVars', self::get_js_variables() );
490 wp_enqueue_script( self::SCRIPT_HANDLE );
491 wp_set_script_translations( self::SCRIPT_HANDLE, 'formidable' );
492
493 FrmAppHelper::dequeue_extra_global_scripts();
494 }
495
496 /**
497 * Get the Welcome Tour JS variables as an array.
498 *
499 * @return array
500 */
501 private static function get_js_variables() {
502 return array(
503 'IS_DASHBOARD_PAGE' => self::$is_dashboard_page,
504 'IS_WELCOME_TOUR_SEEN' => ! empty( self::$checklist['seen'] ),
505 'PROGRESS_BAR_PERCENT' => self::get_welcome_tour_progress_bar_percent(),
506 'TOUR_URL' => admin_url( 'admin.php?page=formidable-form-templates' ),
507 );
508 }
509
510 /**
511 * Get the Welcome Tour progress bar percentage.
512 *
513 * @return int
514 */
515 private static function get_welcome_tour_progress_bar_percent() {
516 if ( ! self::$steps ) {
517 return 0;
518 }
519
520 $percent = self::get_active_step() / count( self::$steps['keys'] ) * 100;
521
522 return (int) $percent;
523 }
524
525 /**
526 * Saves the checklist data.
527 *
528 * @param array|null $checklist The checklist data to set.
529 *
530 * @return void
531 */
532 public static function save_checklist( $checklist = null ) {
533 update_option( self::CHECKLIST_OPTION, $checklist ?? self::$checklist, false );
534 }
535
536 /**
537 * Gets the checklist data.
538 *
539 * @return array The checklist data.
540 */
541 public static function get_checklist() {
542 return get_option(
543 self::CHECKLIST_OPTION,
544 array(
545 'completed_steps' => array(),
546 'active_step_key' => 'create-form',
547 )
548 );
549 }
550
551 /**
552 * Build a tracked URL with UTM parameters and affiliate tracking.
553 *
554 * @param string $url The base URL to process.
555 *
556 * @return string The processed URL with UTM parameters and affiliate tracking.
557 */
558 public static function make_tracked_url( $url ) {
559 $url = FrmAppHelper::maybe_add_missing_utm(
560 $url,
561 array(
562 'campaign' => 'welcome-tour',
563 )
564 );
565 return FrmAppHelper::make_affiliate_url( $url );
566 }
567
568 /**
569 * Get the current form ID.
570 *
571 * @return int The current form ID.
572 */
573 public static function get_current_form_id() {
574 if ( self::$current_form_id ) {
575 return self::$current_form_id;
576 }
577
578 self::$current_form_id = FrmAppHelper::simple_get( 'form', 'absint', 0 );
579
580 if ( ! self::$current_form_id ) {
581 self::$current_form_id = FrmAppHelper::simple_get( 'id', 'absint', 0 );
582 }
583
584 return self::$current_form_id;
585 }
586
587 /**
588 * Checks if the tour is completed.
589 *
590 * @return bool True if the tour is completed, false otherwise.
591 */
592 private static function is_tour_completed() {
593 return ! empty( self::$checklist['done'] );
594 }
595
596 /**
597 * Marks the completed state as seen.
598 *
599 * @return void
600 */
601 private static function mark_completed_as_seen() {
602 if ( ! empty( self::$checklist['completed_seen'] ) ) {
603 return;
604 }
605
606 self::$checklist['completed_seen'] = true;
607 self::save_checklist();
608 }
609
610 /**
611 * Gets usage tracking data.
612 *
613 * @return array
614 */
615 public static function get_usage_data() {
616 // Do not use the get_checklist() method to prevent adding default value.
617 $option = get_option( self::CHECKLIST_OPTION );
618
619 if ( ! $option ) {
620 // Welcome tour doesn't show on this site.
621 return array();
622 }
623
624 $usage_data = array();
625 $steps = self::get_steps();
626
627 foreach ( $steps as $key => $step ) {
628 $usage_data[ 'completed_step_' . $key ] = empty( $option['completed_steps'][ $key ] ) ? 0 : 1;
629 }
630
631 $usage_data['done'] = empty( $option['done'] ) ? 0 : 1;
632
633 // If dismissed, the dismissed step is the active step.
634 $usage_data['dismissed'] = empty( $option['dismissed'] ) ? 0 : $option['active_step_key'];
635
636 return $usage_data;
637 }
638 }
639