PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.16.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.16.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 / FrmAppController.php

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

1,430 lines 39.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmAppController {
7
8 /**
9 * @return void
10 */
11 public static function menu() {
12 FrmAppHelper::maybe_add_permissions();
13 if ( ! current_user_can( 'frm_view_forms' ) ) {
14 return;
15 }
16
17 $menu_name = FrmAppHelper::get_menu_name();
18 add_menu_page( 'Formidable', $menu_name, 'frm_view_forms', 'formidable', 'FrmFormsController::route', self::menu_icon(), self::get_menu_position() );
19
20 self::maybe_add_black_friday_submenu_item();
21 }
22
23 /**
24 * @return int
25 */
26 private static function get_menu_position() {
27 return (int) apply_filters( 'frm_menu_position', 29 );
28 }
29
30 /**
31 * @since 3.05
32 */
33 private static function menu_icon() {
34 $icon = FrmAppHelper::svg_logo(
35 array(
36 'fill' => '#a0a5aa',
37 'orange' => '#a0a5aa',
38 )
39 );
40 $icon = 'data:image/svg+xml;base64,' . base64_encode( $icon );
41
42 return apply_filters( 'frm_icon', $icon );
43 }
44
45 /**
46 * @since 6.16
47 *
48 * @return void
49 */
50 private static function maybe_add_black_friday_submenu_item() {
51 if ( ! current_user_can( 'frm_change_settings' ) ) {
52 return;
53 }
54
55 $is_black_friday = self::is_black_friday();
56 $is_cyber_monday = self::is_cyber_monday();
57
58 if ( ! $is_black_friday && ! $is_cyber_monday ) {
59 return;
60 }
61
62 $black_friday_menu_label = $is_black_friday ? __( 'Black Friday!', 'formidable' ) : __( 'Cyber Monday!', 'formidable' );
63 $black_friday_menu_label = '<span class="frm-orange-text">' . esc_html( $black_friday_menu_label ) . '</span>';
64
65 add_action(
66 'admin_menu',
67 function () use ( $black_friday_menu_label ) {
68 add_submenu_page(
69 'formidable',
70 'Formidable',
71 $black_friday_menu_label,
72 'frm_change_settings',
73 'formidable-black-friday',
74 function () {
75 // This function should do nothing. The redirect is handled earlier to avoid header conflicts.
76 }
77 );
78 },
79 1000
80 );
81 }
82
83 /**
84 * Black Friday sale is from November 25 to 29.
85 *
86 * @since 6.16
87 *
88 * @return bool
89 */
90 private static function is_black_friday() {
91 return self::within_sale_date_range( '2024-11-25', '2024-11-29' );
92 }
93
94 /**
95 * Cyber Monday sale rules from November 30 to December 4.
96 *
97 * @since 6.16
98 *
99 * @return bool
100 */
101 private static function is_cyber_monday() {
102 return self::within_sale_date_range( '2024-11-30', '2024-12-04' );
103 }
104
105 /**
106 * Check if the current time is within a sale date range.
107 * Our sales are based on Eastern Time, so we use New York's timezone.
108 *
109 * @since 6.16
110 *
111 * @param string $from The beginning of the date range. Y-m-d format is expected.
112 * @param string $to The end of the date range. Y-m-d format is expected.
113 * @return bool
114 */
115 private static function within_sale_date_range( $from, $to ) {
116 $date = new DateTime( 'now', new DateTimeZone( 'America/New_York' ) );
117 $today = $date->format( 'Y-m-d' );
118 return $today >= $from && $today <= $to;
119 }
120
121 /**
122 * @since 3.0
123 */
124 public static function add_admin_class( $classes ) {
125 if ( self::is_white_page() ) {
126 $classes .= ' frm-white-body ';
127 $classes .= self::get_os();
128
129 $page = str_replace( 'formidable-', '', FrmAppHelper::simple_get( 'page', 'sanitize_title' ) );
130 if ( empty( $page ) || $page === 'formidable' ) {
131 $action = FrmAppHelper::simple_get( 'frm_action', 'sanitize_title' );
132 if ( in_array( $action, array( 'settings', 'edit', 'list' ) ) ) {
133 $page .= $action;
134 } else {
135 $page = $action;
136 }
137 }
138 if ( ! empty( $page ) ) {
139 $classes .= ' frm-admin-page-' . $page;
140 }
141 }
142
143 if ( self::is_grey_page() ) {
144 $classes .= ' frm-grey-body ';
145 }
146
147 if ( FrmAppHelper::is_full_screen() ) {
148 $full_screen_on = self::get_full_screen_setting();
149 $add_class = '';
150 if ( $full_screen_on ) {
151 $add_class = ' frm-full-screen is-fullscreen-mode';
152
153 // Load the CSS for .is-fullscreen-mode.
154 wp_enqueue_style( 'wp-edit-post' );
155 }
156 $classes .= apply_filters( 'frm_admin_full_screen_class', $add_class );
157 }
158
159 if ( ! FrmAppHelper::pro_is_installed() ) {
160 $classes .= ' frm-lite ';
161 }
162
163 if ( get_user_setting( 'unfold' ) && 'f' !== get_user_setting( 'mfold' ) ) {
164 $classes .= ' frm-unfold ';
165 }
166
167 return $classes;
168 }
169
170 /**
171 * Get the full screen mode setting from the block editor.
172 *
173 * @since 6.1
174 *
175 * @return bool
176 */
177 private static function get_full_screen_setting() {
178 global $wpdb;
179 $meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences';
180
181 $prefs = get_user_meta( get_current_user_id(), $meta_key, true );
182 if ( $prefs && isset( $prefs['core/edit-post']['fullscreenMode'] ) ) {
183 return $prefs['core/edit-post']['fullscreenMode'];
184 }
185
186 return true;
187 }
188
189 /**
190 * @since 4.0
191 *
192 * @return string
193 */
194 private static function get_os() {
195 $agent = strtolower( FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' ) );
196 $os = '';
197 if ( strpos( $agent, 'mac' ) !== false ) {
198 $os = ' osx';
199 } elseif ( strpos( $agent, 'linux' ) !== false ) {
200 $os = ' linux';
201 } elseif ( strpos( $agent, 'windows' ) !== false ) {
202 $os = ' windows';
203 }
204 return $os;
205 }
206
207 /**
208 * @since 3.0
209 */
210 private static function is_white_page() {
211 $white_pages = array(
212 'formidable',
213 'formidable-entries',
214 'formidable-views',
215 'formidable-views-editor',
216 'formidable-addons',
217 'formidable-import',
218 'formidable-settings',
219 'formidable-styles',
220 'formidable-styles2',
221 'formidable-inbox',
222 FrmFormTemplatesController::PAGE_SLUG,
223 FrmOnboardingWizardController::PAGE_SLUG,
224 );
225
226 if ( ! class_exists( 'FrmTransHooksController', false ) && ! FrmTransLiteAppHelper::should_fallback_to_paypal() ) {
227 // Only consider the payments page as a "white page" when the Payments submodule is off.
228 // Otherwise this causes a lot of styling issues when the Stripe add-on (or Authorize.Net) is active.
229
230 // Add an extra check to avoid white page styling on the PayPal "edit" action.
231 // We fallback to the PayPal add on for the "edit" action since Stripe Lite does not have an edit view.
232 if ( ! in_array( FrmAppHelper::simple_get( 'action' ), array( 'edit', 'new' ), true ) || ! is_callable( 'FrmPaymentsController::route' ) ) {
233 $white_pages[] = 'formidable-payments';
234 }
235 }
236
237 $is_white_page = self::is_page_in_list( $white_pages ) || self::is_grey_page() || FrmAppHelper::is_view_builder_page();
238
239 /**
240 * Allow another add on to style a page as a Formidable "white page", which adds a white background color.
241 *
242 * @since 5.3
243 *
244 * @param bool $is_white_page
245 */
246 $is_white_page = apply_filters( 'frm_is_white_page', $is_white_page );
247
248 return $is_white_page;
249 }
250
251 /**
252 * Add a grey bg instead of white.
253 *
254 * @since 6.8
255 *
256 * @return bool
257 */
258 private static function is_grey_page() {
259 $grey_pages = array(
260 'formidable-applications',
261 'formidable-dashboard',
262 );
263
264 $is_grey_page = self::is_page_in_list( $grey_pages );
265
266 /**
267 * Filter to change FF wrapper background to grey.
268 *
269 * @since 6.8
270 *
271 * @param bool $is_grey_page
272 * @return bool
273 */
274 return apply_filters( 'frm_is_grey_page', $is_grey_page );
275 }
276
277 /**
278 * @since 6.8
279 *
280 * @param array $pages A list of page names to check.
281 * @return bool
282 */
283 private static function is_page_in_list( $pages ) {
284 $get_page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
285 return in_array( $get_page, $pages, true );
286 }
287
288 /**
289 * @return void
290 */
291 public static function load_wp_admin_style() {
292 FrmAppHelper::load_font_style();
293 }
294
295 /**
296 * @param int|object $form
297 * @param bool $show_nav
298 * @param string $title
299 *
300 * @psalm-param 'hide'|'show' $title
301 *
302 * @return void
303 */
304 public static function get_form_nav( $form, $show_nav = false, $title = 'show' ) {
305 $show_nav = FrmAppHelper::get_param( 'show_nav', $show_nav, 'get', 'absint' );
306 if ( empty( $show_nav ) || ! $form ) {
307 return;
308 }
309
310 FrmForm::maybe_get_form( $form );
311 if ( ! is_object( $form ) ) {
312 return;
313 }
314
315 $id = $form->id;
316 $current_page = self::get_current_page();
317 $nav_items = self::get_form_nav_items( $form );
318
319 include FrmAppHelper::plugin_path() . '/classes/views/shared/form-nav.php';
320 }
321
322 /**
323 * @return string
324 */
325 private static function get_current_page() {
326 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
327 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title', 'None' );
328 $current_page = isset( $_GET['page'] ) ? $page : $post_type;
329
330 if ( FrmAppHelper::is_view_builder_page() ) {
331 $current_page = 'frm_display';
332 }
333
334 return $current_page;
335 }
336
337 /**
338 * @param object $form
339 * @return array
340 */
341 private static function get_form_nav_items( $form ) {
342 $id = $form->parent_form_id ? $form->parent_form_id : $form->id;
343
344 $nav_items = array(
345 array(
346 'link' => FrmForm::get_edit_link( $id ),
347 'label' => __( 'Build', 'formidable' ),
348 'current' => array( 'edit', 'new', 'duplicate' ),
349 'page' => 'formidable',
350 'permission' => 'frm_edit_forms',
351 ),
352 array(
353 'link' => FrmStylesHelper::get_list_url( $id ),
354 'label' => __( 'Style', 'formidable' ),
355 'current' => array(),
356 'page' => 'formidable-styles',
357 'permission' => 'frm_edit_forms',
358 ),
359 array(
360 'link' => admin_url( 'admin.php?page=formidable&frm_action=settings&id=' . absint( $id ) ),
361 'label' => __( 'Settings', 'formidable' ),
362 'current' => array( 'settings' ),
363 'page' => 'formidable',
364 'permission' => 'frm_edit_forms',
365 ),
366 array(
367 'link' => admin_url( 'admin.php?page=formidable-entries&frm_action=list&form=' . absint( $id ) ),
368 'label' => __( 'Entries', 'formidable' ),
369 'current' => array(),
370 'page' => 'formidable-entries',
371 'permission' => 'frm_view_entries',
372 ),
373 );
374
375 $views_installed = is_callable( 'FrmProAppHelper::views_is_installed' ) && FrmProAppHelper::views_is_installed();
376
377 if ( ! $views_installed ) {
378 $nav_items[] = array(
379 'link' => admin_url( 'admin.php?page=formidable-views&form=' . absint( $id ) ),
380 'label' => __( 'Views', 'formidable' ),
381 'current' => array(),
382 'page' => 'formidable-views',
383 'permission' => 'frm_view_entries',
384 'atts' => array(
385 'class' => 'frm_noallow',
386 ),
387 );
388 }
389
390 // Let people know reports and views exist.
391 if ( ! FrmAppHelper::pro_is_installed() ) {
392 $nav_items[] = array(
393 'link' => admin_url( 'admin.php?page=formidable&frm_action=lite-reports&form=' . absint( $id ) ),
394 'label' => __( 'Reports', 'formidable' ),
395 'current' => array( 'reports' ),
396 'page' => 'formidable',
397 'permission' => 'frm_view_entries',
398 'atts' => array(
399 'class' => 'frm_noallow',
400 ),
401 );
402 }
403
404 $nav_args = array(
405 'form_id' => $id,
406 'form' => $form,
407 );
408
409 return (array) apply_filters( 'frm_form_nav_list', $nav_items, $nav_args );
410 }
411
412 // Adds a settings link to the plugins page
413 /**
414 * @return array
415 */
416 public static function settings_link( $links ) {
417 $settings = array();
418
419 if ( ! FrmAppHelper::pro_is_installed() ) {
420 $label = FrmAddonsController::is_license_expired() ? __( 'Renew', 'formidable' ) : __( 'Upgrade to Pro', 'formidable' );
421 $settings[] = '<a href="' . esc_url( FrmAppHelper::admin_upgrade_link( 'plugin-row' ) ) . '" target="_blank" rel="noopener"><b style="color:#1da867;font-weight:700;">' . esc_html( $label ) . '</b></a>';
422 }
423
424 $settings[] = '<a href="' . esc_url( admin_url( 'admin.php?page=formidable' ) ) . '">' . __( 'Build a Form', 'formidable' ) . '</a>';
425
426 return array_merge( $settings, $links );
427 }
428
429 /**
430 * @return void
431 */
432 public static function pro_get_started_headline() {
433 self::review_request();
434 FrmAppHelper::min_pro_version_notice( '6.0' );
435 }
436
437 /**
438 * Add admin notices as needed for reviews
439 *
440 * @since 3.04.03
441 *
442 * @return void
443 */
444 private static function review_request() {
445 $reviews = new FrmReviews();
446 $reviews->review_request();
447 }
448
449 /**
450 * Save the request to hide the review
451 *
452 * @since 3.04.03
453 *
454 * @return void
455 */
456 public static function dismiss_review() {
457 FrmAppHelper::permission_check( 'frm_change_settings' );
458 check_ajax_referer( 'frm_ajax', 'nonce' );
459
460 $reviews = new FrmReviews();
461 $reviews->dismiss_review();
462 }
463
464 /**
465 * @since 3.04.02
466 *
467 * @return void
468 */
469 public static function include_upgrade_overlay() {
470 self::enqueue_dialog_assets();
471 add_action( 'admin_footer', 'FrmAppController::upgrade_overlay_html' );
472 }
473
474 /**
475 * Enqueue scripts and styles required for modals.
476 *
477 * @since 5.3
478 *
479 * @return void
480 */
481 public static function enqueue_dialog_assets() {
482 wp_enqueue_script( 'jquery-ui-dialog' );
483 wp_enqueue_style( 'jquery-ui-dialog' );
484 }
485
486 /**
487 * @since 3.06.03
488 *
489 * @return void
490 */
491 public static function upgrade_overlay_html() {
492 $is_pro = FrmAppHelper::pro_is_installed();
493 $upgrade_link = array(
494 'medium' => 'builder',
495 'content' => 'upgrade',
496 );
497 $default_link = FrmAppHelper::admin_upgrade_link( $upgrade_link );
498 $plugin_path = FrmAppHelper::plugin_path();
499 $shared_path = $plugin_path . '/classes/views/shared/';
500
501 include $shared_path . 'upgrade_overlay.php';
502 include $shared_path . 'confirm-overlay.php';
503 }
504
505 /**
506 * Create a basic form with an email field.
507 *
508 * @param string $form_key
509 * @param string $title
510 * @param string $description
511 * @return void
512 */
513 public static function api_email_form( $form_key, $title = '', $description = '' ) {
514 $user = wp_get_current_user();
515 $args = array(
516 'api_url' => 'https://sandbox.formidableforms.com/api/wp-json/frm/v2/forms/' . $form_key . '?return=html&exclude_script=jquery&exclude_style=formidable-css',
517 'title' => $title,
518 'description' => $description,
519 );
520 require FrmAppHelper::plugin_path() . '/classes/views/form-templates/modals/leave-email-modal.php';
521 }
522
523 /**
524 * @return void
525 */
526 public static function include_info_overlay() {
527 self::enqueue_dialog_assets();
528 add_action( 'admin_footer', 'FrmAppController::info_overlay_html' );
529 }
530
531 /**
532 * @return void
533 */
534 public static function info_overlay_html() {
535 include FrmAppHelper::plugin_path() . '/classes/views/shared/info-overlay.php';
536 }
537
538 /**
539 * @since 3.04.02
540 *
541 * @return void
542 */
543 public static function remove_upsells() {
544 remove_action( 'frm_before_settings', 'FrmSettingsController::license_box' );
545 remove_action( 'frm_after_settings', 'FrmSettingsController::settings_cta' );
546 remove_action( 'frm_add_form_style_tab_options', 'FrmFormsController::add_form_style_tab_options' );
547 remove_action( 'frm_after_field_options', 'FrmFormsController::logic_tip' );
548 }
549
550 /**
551 * If there are CURL problems on this server, wp_remote_post won't work for installing
552 * Use a javascript fallback instead.
553 *
554 * @since 2.0.3
555 *
556 * @return void
557 */
558 public static function install_js_fallback() {
559 FrmAppHelper::load_admin_wide_js();
560 ?>
561 <div id="frm_install_message"></div>
562 <script>jQuery(document).ready( frm_install_now );</script>
563 <?php
564 }
565
566 /**
567 * Check if the database is outdated
568 *
569 * @since 2.0.1
570 * @return bool
571 */
572 public static function needs_update() {
573 $needs_upgrade = self::compare_for_update(
574 array(
575 'option' => 'frm_db_version',
576 'new_db_version' => FrmAppHelper::$db_version,
577 'new_plugin_version' => FrmAppHelper::plugin_version(),
578 )
579 );
580
581 if ( ! $needs_upgrade ) {
582 $needs_upgrade = apply_filters( 'frm_db_needs_upgrade', $needs_upgrade );
583 }
584
585 return $needs_upgrade;
586 }
587
588 /**
589 * Check both version number and DB number for changes
590 *
591 * @since 3.0.04
592 *
593 * @param array $atts
594 * @return bool
595 */
596 public static function compare_for_update( $atts ) {
597 $db_version = get_option( $atts['option'] );
598
599 if ( strpos( $db_version, '-' ) === false ) {
600 $needs_upgrade = true;
601 } else {
602 $last_upgrade = explode( '-', $db_version );
603 $needs_db_upgrade = (int) $last_upgrade[1] < (int) $atts['new_db_version'];
604 $new_version = version_compare( $last_upgrade[0], $atts['new_plugin_version'], '<' );
605 $needs_upgrade = $needs_db_upgrade || $new_version;
606 }
607
608 return $needs_upgrade;
609 }
610
611 /**
612 * Check for database update and trigger js loading
613 *
614 * @since 2.0.1
615 *
616 * @return void
617 */
618 public static function admin_init() {
619 if ( FrmAppHelper::get_param( 'delete_all' ) && FrmAppHelper::is_admin_page( 'formidable' ) && 'trash' === FrmAppHelper::get_param( 'form_type' ) ) {
620 FrmFormsController::delete_all();
621 }
622
623 if ( FrmAppHelper::is_admin_page( 'formidable' ) && 'duplicate' === FrmAppHelper::get_param( 'frm_action' ) ) {
624 FrmFormsController::duplicate();
625 }
626
627 if ( FrmAppHelper::is_style_editor_page() && 'save' === FrmAppHelper::get_param( 'frm_action' ) ) {
628 // Hook in earlier than FrmStylesController::route so we can redirect before the headers have been sent.
629 FrmStylesController::save_style();
630 }
631
632 if ( 'formidable-pro-upgrade' === FrmAppHelper::get_param( 'page' ) && ! FrmAppHelper::pro_is_installed() && current_user_can( 'frm_view_forms' ) ) {
633 wp_redirect(
634 FrmAppHelper::admin_upgrade_link(
635 array(
636 'medium' => 'upgrade',
637 'content' => 'submenu-upgrade',
638 )
639 )
640 );
641 die();
642 }
643
644 if ( 'formidable-black-friday' === FrmAppHelper::get_param( 'page' ) && current_user_can( 'frm_change_settings' ) ) {
645 wp_redirect(
646 FrmAppHelper::admin_upgrade_link(
647 array(
648 'medium' => 'black-friday-submenu',
649 'content' => self::is_cyber_monday() ? 'cyber-monday-submenu' : 'black-friday-submenu',
650 ),
651 'black-friday'
652 )
653 );
654 die();
655 }
656
657 // Register personal data hooks.
658 new FrmPersonalData();
659
660 if ( ! FrmAppHelper::doing_ajax() && self::needs_update() ) {
661 self::network_upgrade_site();
662 }
663
664 if ( ! FrmAppHelper::doing_ajax() ) {
665 // don't continue during ajax calls
666 self::admin_js();
667 }
668
669 self::trigger_page_load_hooks();
670
671 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
672 // Redirect to the "Form Templates" page if the 'frm_action' parameter matches specific actions.
673 // This provides backward compatibility for old addons that use legacy modal templates.
674 $action = FrmAppHelper::get_param( 'frm_action' );
675 $trigger_name_modal = FrmAppHelper::get_param( 'triggerNewFormModal' );
676 if ( $trigger_name_modal || in_array( $action, array( 'add_new', 'list_templates' ), true ) ) {
677 $application_id = FrmAppHelper::simple_get( 'applicationId', 'absint' );
678 $url_param = $application_id ? '&applicationId=' . $application_id : '';
679
680 wp_safe_redirect( admin_url( 'admin.php?page=' . FrmFormTemplatesController::PAGE_SLUG . $url_param ) );
681 exit;
682 }
683
684 FrmInbox::maybe_disable_screen_options();
685 }
686 }
687
688 /**
689 * Get the current page and check for a possible function to trigger.
690 * If a class name matches the page name, and the class has a load_page() method, trigger it.
691 *
692 * @since 6.8
693 *
694 * @return void
695 */
696 private static function trigger_page_load_hooks() {
697 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
698 if ( strpos( $page, 'formidable-' ) !== 0 ) {
699 // Only trigger hooks on Formidable pages.
700 return;
701 }
702
703 $page = str_replace( array( 'formidable-', '-' ), array( '', ' ' ), $page );
704 $page = str_replace( ' ', '', ucwords( $page ) );
705 $class = 'Frm' . $page . 'Controller';
706 if ( class_exists( $class ) && method_exists( $class, 'load_page' ) ) {
707 call_user_func( array( $class, 'load_page' ) );
708 }
709 }
710
711 /**
712 * @return void
713 */
714 public static function admin_js() {
715 $plugin_url = FrmAppHelper::plugin_url();
716 $version = FrmAppHelper::plugin_version();
717
718 FrmAppHelper::load_admin_wide_js();
719
720 // Register component assets early to ensure they can be enqueued later in controllers.
721 wp_register_style( 'formidable-animations', $plugin_url . '/css/admin/animations.css', array(), $version );
722
723 if ( class_exists( 'FrmOverlayController' ) ) {
724 // This should always exist.
725 // But it may not have loaded properly when updating the plugin.
726 FrmOverlayController::register_assets();
727 }
728
729 wp_register_style( 'formidable_admin_global', $plugin_url . '/css/admin/frm_admin_global.css', array(), $version );
730 wp_enqueue_style( 'formidable_admin_global' );
731
732 wp_register_style( 'formidable-admin', $plugin_url . '/css/frm_admin.css', array(), $version );
733 wp_register_style( 'formidable-grids', $plugin_url . '/css/frm_grids.css', array(), $version );
734
735 wp_register_script( 'formidable_dom', $plugin_url . '/js/admin/dom.js', array( 'jquery', 'jquery-ui-dialog', 'wp-i18n' ), $version, true );
736 wp_register_script( 'formidable_embed', $plugin_url . '/js/admin/embed.js', array( 'formidable_dom', 'jquery-ui-autocomplete' ), $version, true );
737 self::register_popper1();
738 wp_register_script( 'bootstrap_tooltip', $plugin_url . '/js/bootstrap.min.js', array( 'jquery', 'popper' ), '4.6.1', true );
739 wp_register_script( 'formidable_settings', $plugin_url . '/js/admin/settings.js', array(), $version, true );
740
741 if ( self::should_show_floating_links() ) {
742 self::enqueue_floating_links( $plugin_url, $version );
743 }
744
745 $dependencies = array(
746 'formidable_admin_global',
747 'jquery',
748 'jquery-ui-core',
749 'jquery-ui-draggable',
750 'jquery-ui-sortable',
751 'bootstrap_tooltip',
752 'bootstrap-multiselect',
753 'wp-i18n',
754 // Required in WP versions older than 5.7
755 'wp-hooks',
756 'formidable_dom',
757 'formidable_embed',
758 );
759
760 if ( FrmAppHelper::is_style_editor_page( 'edit' ) ) {
761 // We only need to load the color picker when editing styles.
762 $dependencies[] = 'wp-color-picker';
763 }
764
765 wp_register_script( 'formidable_admin', $plugin_url . '/js/formidable_admin.js', $dependencies, $version, true );
766
767 if ( FrmAppHelper::on_form_listing_page() ) {
768 // For the existing page dropdown in the Form embed modal.
769 wp_enqueue_script( 'jquery-ui-autocomplete' );
770 }
771
772 wp_register_script( 'bootstrap-multiselect', $plugin_url . '/js/bootstrap-multiselect.js', array( 'jquery', 'bootstrap_tooltip', 'popper' ), '1.1.1', true );
773
774 if ( ! class_exists( 'FrmTransHooksController', false ) ) {
775 /**
776 * Gateway fields are included for add-on compatibility but we do not want it to be visible.
777 * They do however need to be visible when the payments submodule is active.
778 */
779 wp_add_inline_style(
780 'formidable-admin',
781 '#frm_builder_page li[data-ftype="gateway"] { display: none; }'
782 );
783 }
784
785 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
786 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
787
788 global $pagenow;
789 if ( strpos( $page, 'formidable' ) === 0 || ( $pagenow === 'edit.php' && $post_type === 'frm_display' ) ) {
790
791 wp_enqueue_script( 'admin-widgets' );
792 wp_enqueue_style( 'widgets' );
793 self::maybe_deregister_popper2();
794 wp_enqueue_script( 'formidable_admin' );
795 wp_set_script_translations( 'formidable_admin', 'formidable' );
796 wp_enqueue_script( 'formidable_embed' );
797 wp_set_script_translations( 'formidable_embed', 'formidable' );
798 FrmAppHelper::localize_script( 'admin' );
799
800 wp_enqueue_style( 'formidable-animations' );
801 wp_enqueue_style( 'formidable-admin' );
802 if ( 'formidable-styles' !== $page && 'formidable-styles2' !== $page ) {
803 wp_enqueue_style( 'formidable-grids' );
804 self::maybe_enqueue_dropzone_css( $page );
805 } else {
806 wp_enqueue_style( 'formidable-grids' );
807 }
808
809 if ( 'formidable-entries' === $page ) {
810 // Load front end js for entries.
811 wp_enqueue_script( 'formidable' );
812
813 // Registers and enqueues the entries page scripts.
814 wp_register_script( 'formidable_entries', $plugin_url . '/js/admin/entries.js', array( 'formidable_admin', 'wp-dom-ready' ), $version, true );
815 wp_enqueue_script( 'formidable_entries' );
816 }
817
818 do_action( 'frm_enqueue_builder_scripts' );
819 self::include_upgrade_overlay();
820 self::include_info_overlay();
821 } elseif ( FrmAppHelper::is_view_builder_page() ) {
822 if ( isset( $_REQUEST['post_type'] ) ) {
823 $post_type = sanitize_title( wp_unslash( $_REQUEST['post_type'] ) );
824 } elseif ( isset( $_REQUEST['post'] ) && absint( $_REQUEST['post'] ) ) {
825 $post = get_post( absint( wp_unslash( $_REQUEST['post'] ) ) );
826 if ( ! $post ) {
827 return;
828 }
829 $post_type = $post->post_type;
830 } else {
831 return;
832 }
833
834 if ( $post_type === 'frm_display' ) {
835 self::enqueue_legacy_views_assets();
836 }
837 }//end if
838
839 if ( 'formidable-addons' === $page ) {
840 wp_register_script( 'formidable_addons', $plugin_url . '/js/admin/addons.js', array( 'formidable_admin', 'wp-dom-ready' ), $version, true );
841 wp_enqueue_script( 'formidable_addons' );
842 }
843 }
844
845 /**
846 * Avoid loading dropzone CSS on the form list page. It isn't required there.
847 *
848 * @since 6.11
849 *
850 * @param string $page
851 * @return void
852 */
853 private static function maybe_enqueue_dropzone_css( $page ) {
854 if ( ! FrmAppHelper::pro_is_installed() ) {
855 return;
856 }
857
858 $should_avoid_loading_dropzone = 'formidable' === $page && ! FrmAppHelper::simple_get( 'frm_action', 'sanitize_title' );
859 if ( ! $should_avoid_loading_dropzone ) {
860 wp_enqueue_style( 'formidable-dropzone' );
861 }
862 }
863
864 /**
865 * The floating links are not shown on every page.
866 * They are also not shown if white labeling is being used.
867 *
868 * @since 6.8.4
869 *
870 * @return bool
871 */
872 private static function should_show_floating_links() {
873 if ( ! FrmAppHelper::is_formidable_branding() ) {
874 return false;
875 }
876
877 return FrmAppHelper::is_formidable_admin() &&
878 ! FrmAppHelper::is_style_editor_page() &&
879 ! FrmAppHelper::is_admin_page( 'formidable-views-editor' ) &&
880 ! FrmAppHelper::simple_get( 'frm_action', 'sanitize_title' );
881 }
882
883 /**
884 * @since 6.3.2
885 *
886 * @return void
887 */
888 public static function admin_enqueue_scripts() {
889 self::load_wp_admin_style();
890 self::maybe_force_formidable_block_on_gutenberg_page();
891 FrmUsageController::load_scripts();
892 }
893
894 /**
895 * Enqueue required assets for the Legacy Views editor (Views v4.x).
896 *
897 * @since 6.1.2
898 *
899 * @return void
900 */
901 private static function enqueue_legacy_views_assets() {
902 wp_enqueue_style( 'formidable-grids' );
903 wp_enqueue_script( 'jquery-ui-draggable' );
904 self::maybe_deregister_popper2();
905 wp_enqueue_script( 'formidable_admin' );
906 wp_add_inline_style(
907 'formidable-admin',
908 '
909 .frm-white-body.post-type-frm_display .columns-2 {
910 display: block;
911 overflow: visible;
912 }
913
914 .frm-white-body.post-type-frm_display .columns-2 > div {
915 overflow-y: hidden;
916 }
917
918 .frm-white-body.post-type-frm_display #titlediv #title-prompt-text {
919 padding: 3px 0 0 10px;
920 }
921
922 .post-type-frm_display #field-search-input,
923 .post-type-frm_display #advanced-search-input {
924 flex: 1;
925 }
926 '
927 );
928 wp_enqueue_style( 'formidable-admin' );
929 FrmAppHelper::localize_script( 'admin' );
930 self::include_info_overlay();
931 }
932
933 /**
934 * Fix a Duplicator Pro conflict because it uses Popper 2. See issue #3459.
935 *
936 * @since 5.2.02.01
937 *
938 * @return void
939 */
940 private static function maybe_deregister_popper2() {
941 global $wp_scripts;
942
943 if ( ! array_key_exists( 'popper', $wp_scripts->registered ) ) {
944 return;
945 }
946
947 $popper = $wp_scripts->registered['popper'];
948 if ( version_compare( $popper->ver, '2.0', '>=' ) ) {
949 wp_deregister_script( 'popper' );
950 self::register_popper1();
951 }
952 }
953
954 /**
955 * Register Popper required for Bootstrap 4.
956 *
957 * @since 5.2.02.01
958 *
959 * @return void
960 */
961 private static function register_popper1() {
962 if ( ! self::should_register_popper() ) {
963 return;
964 }
965 wp_register_script( 'popper', FrmAppHelper::plugin_url() . '/js/popper.min.js', array( 'jquery' ), '1.16.0', true );
966 }
967
968 /**
969 * Only register popper on Formidable pages.
970 * This helps to avoid popper conflicts on other plugin pages, including the WP Bakery page editor.
971 *
972 * @since 6.11.1
973 *
974 * @return bool
975 */
976 private static function should_register_popper() {
977 global $pagenow;
978
979 $post_id = FrmAppHelper::simple_get( 'post', 'absint' );
980 if ( 'post.php' === $pagenow && $post_id && 'frm_display' === get_post_type( $post_id ) ) {
981 return true;
982 }
983
984 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
985 $is_views_post_type = 'frm_display' === $post_type;
986 if ( in_array( $pagenow, array( 'post-new.php', 'edit.php' ), true ) && $is_views_post_type ) {
987 return true;
988 }
989
990 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
991 if ( strpos( $page, 'formidable' ) === 0 ) {
992 return true;
993 }
994
995 return in_array( $pagenow, array( 'term.php', 'edit-tags.php' ), true ) && FrmAppHelper::simple_get( 'taxonomy' ) === 'frm_application';
996 }
997
998 /**
999 * Automatically insert a Formidable block when loading Gutenberg when $_GET['frmForm' is set.
1000 *
1001 * @since 5.2
1002 *
1003 * @return void
1004 */
1005 private static function maybe_force_formidable_block_on_gutenberg_page() {
1006 global $pagenow;
1007 if ( 'post.php' !== $pagenow ) {
1008 return;
1009 }
1010
1011 $form_id = FrmAppHelper::simple_get( 'frmForm', 'absint' );
1012 if ( ! $form_id ) {
1013 return;
1014 }
1015
1016 self::add_js_to_inject_gutenberg_block( 'formidable/simple-form', 'formId', $form_id );
1017 }
1018
1019 /**
1020 * @since 5.3
1021 *
1022 * @param string $block_name
1023 * @param string $object_key
1024 * @param array|string $object_id
1025 *
1026 * @return void
1027 */
1028 public static function add_js_to_inject_gutenberg_block( $block_name, $object_key, $object_id ) {
1029 require FrmAppHelper::plugin_path() . '/classes/views/shared/edit-page-js.php';
1030 }
1031
1032 /**
1033 * @return void
1034 */
1035 public static function load_lang() {
1036 load_plugin_textdomain( 'formidable', false, FrmAppHelper::plugin_folder() . '/languages/' );
1037 }
1038
1039 /**
1040 * Check if the styles are updated when a form is loaded on the front-end
1041 *
1042 * @since 3.0.1
1043 *
1044 * @return void
1045 */
1046 public static function maybe_update_styles() {
1047 if ( self::needs_update() ) {
1048 self::network_upgrade_site();
1049 }
1050 }
1051
1052 /**
1053 * @since 3.0
1054 *
1055 * @return void
1056 */
1057 public static function create_rest_routes() {
1058 $args = array(
1059 'methods' => 'GET',
1060 'callback' => 'FrmAppController::api_install',
1061 'permission_callback' => __CLASS__ . '::can_update_db',
1062 );
1063
1064 register_rest_route( 'frm-admin/v1', '/install', $args );
1065
1066 $args = array(
1067 'methods' => 'GET',
1068 'callback' => 'FrmAddonsController::install_addon_api',
1069 'permission_callback' => 'FrmAddonsController::can_install_addon_api',
1070 );
1071
1072 register_rest_route( 'frm-admin/v1', '/install-addon', $args );
1073 }
1074
1075 /**
1076 * Make sure the install is only being run when we tell it to.
1077 * We don't want to run manually by people calling the API.
1078 *
1079 * @since 4.06.02
1080 */
1081 public static function can_update_db() {
1082 return get_transient( 'frm_updating_api' );
1083 }
1084
1085 /**
1086 * Run silent upgrade on each site in the network during a network upgrade.
1087 * Update database settings for all sites in a network during network upgrade process.
1088 *
1089 * @since 2.0.1
1090 *
1091 * @param int $blog_id Blog ID.
1092 *
1093 * @return void
1094 */
1095 public static function network_upgrade_site( $blog_id = 0 ) {
1096 // Flag to check if install is happening as intended.
1097 set_transient( 'frm_updating_api', true, MINUTE_IN_SECONDS );
1098 $request = new WP_REST_Request( 'GET', '/frm-admin/v1/install' );
1099
1100 self::maybe_add_wp_site_health();
1101
1102 if ( $blog_id ) {
1103 switch_to_blog( $blog_id );
1104 $response = rest_do_request( $request );
1105 restore_current_blog();
1106 } else {
1107 $response = rest_do_request( $request );
1108 }
1109
1110 if ( $response->is_error() ) {
1111 // if the remove post fails, use javascript instead
1112 add_action( 'admin_notices', 'FrmAppController::install_js_fallback' );
1113 }
1114 }
1115
1116 /**
1117 * Make sure WP_Site_Health has been included because it is required when calling rest_do_request.
1118 * Check first that the file exists because WP_Site_Health was only introduced in WordPress 5.2.
1119 *
1120 * @return void
1121 */
1122 private static function maybe_add_wp_site_health() {
1123 if ( ! class_exists( 'WP_Site_Health' ) ) {
1124 $wp_site_health_path = ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
1125 if ( file_exists( $wp_site_health_path ) ) {
1126 require_once $wp_site_health_path;
1127 }
1128 }
1129 }
1130
1131 /**
1132 * @since 3.0
1133 *
1134 * @return true
1135 */
1136 public static function api_install() {
1137 delete_transient( 'frm_updating_api' );
1138 if ( self::needs_update() ) {
1139 $running = get_option( 'frm_install_running' );
1140 if ( false === $running || $running < strtotime( '-5 minutes' ) ) {
1141 update_option( 'frm_install_running', time(), 'no' );
1142 self::install();
1143 delete_option( 'frm_install_running' );
1144 }
1145 }
1146
1147 return true;
1148 }
1149
1150 /**
1151 * Silent database upgrade (no redirect).
1152 * Called via ajax request during network upgrade process.
1153 *
1154 * @since 2.0.1
1155 *
1156 * @return void
1157 */
1158 public static function ajax_install() {
1159 FrmAppHelper::permission_check( 'frm_change_settings' );
1160 check_ajax_referer( 'frm_ajax', 'nonce' );
1161 self::api_install();
1162 wp_die();
1163 }
1164
1165 /**
1166 * @return void
1167 */
1168 public static function install() {
1169 $frmdb = new FrmMigrate();
1170 $frmdb->upgrade();
1171 }
1172
1173 /**
1174 * @return void
1175 */
1176 public static function uninstall() {
1177 FrmAppHelper::permission_check( 'administrator' );
1178 check_ajax_referer( 'frm_ajax', 'nonce' );
1179
1180 $frmdb = new FrmMigrate();
1181 $frmdb->uninstall();
1182
1183 // Disable the plugin and redirect after uninstall so the tables don't get added right back.
1184 $plugins = array( FrmAppHelper::plugin_folder() . '/formidable.php', 'formidable-pro/formidable-pro.php' );
1185 deactivate_plugins( $plugins, false, false );
1186 echo esc_url_raw( admin_url( 'plugins.php?deactivate=true' ) );
1187
1188 wp_die();
1189 }
1190
1191 public static function drop_tables( $tables ) {
1192 global $wpdb;
1193 $tables[] = $wpdb->prefix . 'frm_fields';
1194 $tables[] = $wpdb->prefix . 'frm_forms';
1195 $tables[] = $wpdb->prefix . 'frm_items';
1196 $tables[] = $wpdb->prefix . 'frm_item_metas';
1197
1198 return $tables;
1199 }
1200
1201 /**
1202 * @return void
1203 */
1204 public static function deauthorize() {
1205 FrmAppHelper::permission_check( 'frm_change_settings' );
1206 check_ajax_referer( 'frm_ajax', 'nonce' );
1207
1208 delete_option( 'frmpro-credentials' );
1209 delete_option( 'frmpro-authorized' );
1210 delete_site_option( 'frmpro-credentials' );
1211 delete_site_option( 'frmpro-authorized' );
1212 wp_die();
1213 }
1214
1215 /**
1216 * This is triggered when Formidable is activated.
1217 *
1218 * @return void
1219 */
1220 public static function handle_activation() {
1221 self::maybe_activate_payment_cron();
1222 }
1223
1224 /**
1225 * The payment cron is unscheduled when Formidable is deactivated.
1226 * We need to add it back again on activation if Stripe is configured.
1227 *
1228 * @since 6.5
1229 *
1230 * @return void
1231 */
1232 private static function maybe_activate_payment_cron() {
1233 if ( ! FrmStrpLiteConnectHelper::stripe_connect_is_setup() ) {
1234 return;
1235 }
1236
1237 FrmTransLiteAppController::maybe_schedule_cron();
1238 }
1239
1240 public static function set_footer_text( $text ) {
1241 if ( FrmAppHelper::is_formidable_admin() ) {
1242 $text = '';
1243 }
1244
1245 return $text;
1246 }
1247
1248 /**
1249 * Add admin footer links.
1250 *
1251 * @since 6.4.1
1252 *
1253 * @return void
1254 */
1255 public static function add_admin_footer_links() {
1256 if ( self::should_show_footer_links() ) {
1257 include FrmAppHelper::plugin_path() . '/classes/views/shared/admin-footer-links.php';
1258 }
1259 }
1260
1261 /**
1262 * Check if the footer links should be shown.
1263 *
1264 * @since 6.7
1265 *
1266 * @return bool
1267 */
1268 private static function should_show_footer_links() {
1269 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
1270 $is_formidable_page = FrmAppHelper::is_formidable_admin() || 'frm_logs' === $post_type;
1271 $show_footer_links = $is_formidable_page;
1272 if ( FrmAppHelper::is_full_screen() || ! FrmAppHelper::is_formidable_branding() ) {
1273 $show_footer_links = false;
1274 }
1275
1276 /**
1277 * Filter whether to show the Formidable footer links.
1278 *
1279 * @since 6.7
1280 *
1281 * @param bool $show_footer_links
1282 * @return bool
1283 */
1284 return apply_filters( 'frm_show_footer_links', $show_footer_links );
1285 }
1286
1287 /**
1288 * Show an error modal and terminate the script execution.
1289 *
1290 * @since 6.7
1291 *
1292 * @param array $error_args Arguments that control the behavior of the error modal.
1293 *
1294 * @return void
1295 */
1296 public static function show_error_modal( $error_args ) {
1297 $defaults = array(
1298 'title' => '',
1299 'body' => '',
1300 'cancel_url' => '',
1301 'cancel_classes' => '',
1302 'continue_url' => '',
1303 'continue_classes' => '',
1304 'icon' => 'frm_lock_simple',
1305 );
1306
1307 $error_args = wp_parse_args( $error_args, $defaults );
1308 if ( ! isset( $error_args['cancel_text'] ) && ! empty( $error_args['cancel_url'] ) ) {
1309 $error_args['cancel_text'] = __( 'Cancel', 'formidable' );
1310 }
1311
1312 if ( ! isset( $error_args['continue_text'] ) && ! empty( $error_args['continue_url'] ) ) {
1313 $error_args['continue_text'] = __( 'Continue', 'formidable' );
1314 }
1315
1316 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/error-modal.php';
1317 }
1318
1319 /**
1320 * Handles Floating Links' scripts and styles enqueueing.
1321 *
1322 * @since 6.4
1323 *
1324 * @param string $plugin_url URL of the plugin.
1325 * @param string $version Current version of the plugin.
1326 * @return void
1327 */
1328 private static function enqueue_floating_links( $plugin_url, $version ) {
1329 if ( ! $plugin_url || ! $version ) {
1330 // If any required parameters are missing, exit early.
1331 return;
1332 }
1333
1334 // Enqueue the Floating Links styles.
1335 wp_enqueue_style( 's11-floating-links', $plugin_url . '/css/packages/s11-floating-links.css', array(), $version );
1336
1337 // Enqueue the Floating Links script.
1338 wp_enqueue_script( 's11-floating-links', $plugin_url . '/js/packages/floating-links/s11-floating-links.js', array( 'formidable_admin' ), $version, true );
1339
1340 // Enqueue the config script.
1341 wp_enqueue_script( 's11-floating-links-config', $plugin_url . '/js/packages/floating-links/config.js', array( 'wp-i18n' ), $version, true );
1342
1343 if ( function_exists( 'wp_set_script_translations' ) ) {
1344 wp_set_script_translations( 's11-floating-links-config', 's11-' );
1345 }
1346
1347 $floating_links_data = array(
1348 'proIsInstalled' => FrmAppHelper::pro_is_installed(),
1349 );
1350 wp_localize_script( 's11-floating-links-config', 's11FloatingLinksData', $floating_links_data );
1351
1352 /**
1353 * Prompt Pro to load additional floating links scripts.
1354 * This is used to include images in the Inbox SlideIn when Pro is active.
1355 *
1356 * @since 6.8.4
1357 */
1358 do_action( 'frm_enqueue_floating_links' );
1359 }
1360
1361 /**
1362 * Check if we are in our admin pages.
1363 *
1364 * @return bool
1365 */
1366 private static function in_our_pages() {
1367 global $current_screen;
1368 return FrmAppHelper::is_formidable_admin() || ( ! empty( $current_screen->post_type ) && 'frm_logs' === $current_screen->post_type );
1369 }
1370
1371 /**
1372 * Hide all third-parties admin notices only in our admin pages.
1373 *
1374 * @return void
1375 */
1376 public static function filter_admin_notices() {
1377 if ( ! self::in_our_pages() ) {
1378 return;
1379 }
1380
1381 $actions = array(
1382 'admin_notices',
1383 'network_admin_notices',
1384 'user_admin_notices',
1385 'all_admin_notices',
1386 );
1387
1388 global $wp_filter;
1389
1390 foreach ( $actions as $action ) {
1391 if ( empty( $wp_filter[ $action ]->callbacks ) ) {
1392 continue;
1393 }
1394 foreach ( $wp_filter[ $action ]->callbacks as $priority => $callbacks ) {
1395 foreach ( $callbacks as $callback_name => $callback ) {
1396 if ( self::is_our_callback_string( $callback_name ) || self::is_our_callback_array( $callback ) ) {
1397 continue;
1398 }
1399 unset( $wp_filter[ $action ]->callbacks[ $priority ][ $callback_name ] );
1400 }
1401 }
1402 }
1403 }
1404
1405 /**
1406 * Validate that the callback name is ours not from third-party.
1407 *
1408 * @param string $callback_name WordPress callback name.
1409 *
1410 * @return bool
1411 */
1412 private static function is_our_callback_string( $callback_name ) {
1413 return 0 === stripos( $callback_name, 'frm' );
1414 }
1415
1416 /**
1417 * Validate that the callback array is ours not from third-party.
1418 *
1419 * @param array $callback WordPress callback array.
1420 *
1421 * @return bool
1422 */
1423 private static function is_our_callback_array( $callback ) {
1424 return ! empty( $callback['function'] ) &&
1425 is_array( $callback['function'] ) &&
1426 ! empty( $callback['function'][0] ) &&
1427 self::is_our_callback_string( is_object( $callback['function'][0] ) ? get_class( $callback['function'][0] ) : $callback['function'][0] );
1428 }
1429 }
1430