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