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

1,437 lines 39.5 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 }
892
893 /**
894 * Enqueue required assets for the Legacy Views editor (Views v4.x).
895 *
896 * @since 6.1.2
897 *
898 * @return void
899 */
900 private static function enqueue_legacy_views_assets() {
901 wp_enqueue_style( 'formidable-grids' );
902 wp_enqueue_script( 'jquery-ui-draggable' );
903 self::maybe_deregister_popper2();
904 wp_enqueue_script( 'formidable_admin' );
905 wp_add_inline_style(
906 'formidable-admin',
907 '
908 .frm-white-body.post-type-frm_display .columns-2 {
909 display: block;
910 overflow: visible;
911 }
912
913 .frm-white-body.post-type-frm_display .columns-2 > div {
914 overflow-y: hidden;
915 }
916
917 .frm-white-body.post-type-frm_display #titlediv #title-prompt-text {
918 padding: 3px 0 0 10px;
919 }
920
921 .post-type-frm_display #field-search-input,
922 .post-type-frm_display #advanced-search-input {
923 flex: 1;
924 }
925 '
926 );
927 wp_enqueue_style( 'formidable-admin' );
928 FrmAppHelper::localize_script( 'admin' );
929 self::include_info_overlay();
930 }
931
932 /**
933 * Fix a Duplicator Pro conflict because it uses Popper 2. See issue #3459.
934 *
935 * @since 5.2.02.01
936 *
937 * @return void
938 */
939 private static function maybe_deregister_popper2() {
940 global $wp_scripts;
941
942 if ( ! array_key_exists( 'popper', $wp_scripts->registered ) ) {
943 return;
944 }
945
946 $popper = $wp_scripts->registered['popper'];
947 if ( version_compare( $popper->ver, '2.0', '>=' ) ) {
948 wp_deregister_script( 'popper' );
949 self::register_popper1();
950 }
951 }
952
953 /**
954 * Register Popper required for Bootstrap 4.
955 *
956 * @since 5.2.02.01
957 *
958 * @return void
959 */
960 private static function register_popper1() {
961 if ( ! self::should_register_popper() ) {
962 return;
963 }
964 wp_register_script( 'popper', FrmAppHelper::plugin_url() . '/js/popper.min.js', array( 'jquery' ), '1.16.0', true );
965 }
966
967 /**
968 * Only register popper on Formidable pages.
969 * This helps to avoid popper conflicts on other plugin pages, including the WP Bakery page editor.
970 *
971 * @since 6.11.1
972 *
973 * @return bool
974 */
975 private static function should_register_popper() {
976 global $pagenow;
977
978 $post_id = FrmAppHelper::simple_get( 'post', 'absint' );
979 if ( 'post.php' === $pagenow && $post_id && 'frm_display' === get_post_type( $post_id ) ) {
980 return true;
981 }
982
983 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
984 $is_views_post_type = 'frm_display' === $post_type;
985 if ( in_array( $pagenow, array( 'post-new.php', 'edit.php' ), true ) && $is_views_post_type ) {
986 return true;
987 }
988
989 $page = FrmAppHelper::simple_get( 'page', 'sanitize_title' );
990 if ( strpos( $page, 'formidable' ) === 0 ) {
991 return true;
992 }
993
994 return in_array( $pagenow, array( 'term.php', 'edit-tags.php' ), true ) && FrmAppHelper::simple_get( 'taxonomy' ) === 'frm_application';
995 }
996
997 /**
998 * Automatically insert a Formidable block when loading Gutenberg when $_GET['frmForm' is set.
999 *
1000 * @since 5.2
1001 *
1002 * @return void
1003 */
1004 private static function maybe_force_formidable_block_on_gutenberg_page() {
1005 global $pagenow;
1006 if ( 'post.php' !== $pagenow ) {
1007 return;
1008 }
1009
1010 $form_id = FrmAppHelper::simple_get( 'frmForm', 'absint' );
1011 if ( ! $form_id ) {
1012 return;
1013 }
1014
1015 self::add_js_to_inject_gutenberg_block( 'formidable/simple-form', 'formId', $form_id );
1016 }
1017
1018 /**
1019 * @since 5.3
1020 *
1021 * @param string $block_name
1022 * @param string $object_key
1023 * @param array|string $object_id
1024 *
1025 * @return void
1026 */
1027 public static function add_js_to_inject_gutenberg_block( $block_name, $object_key, $object_id ) {
1028 require FrmAppHelper::plugin_path() . '/classes/views/shared/edit-page-js.php';
1029 }
1030
1031 /**
1032 * @return void
1033 */
1034 public static function load_lang() {
1035 load_plugin_textdomain( 'formidable', false, FrmAppHelper::plugin_folder() . '/languages/' );
1036 }
1037
1038 /**
1039 * Check if the styles are updated when a form is loaded on the front-end
1040 *
1041 * @since 3.0.1
1042 *
1043 * @return void
1044 */
1045 public static function maybe_update_styles() {
1046 if ( self::needs_update() ) {
1047 self::network_upgrade_site();
1048 }
1049 }
1050
1051 /**
1052 * @since 3.0
1053 *
1054 * @return void
1055 */
1056 public static function create_rest_routes() {
1057 $args = array(
1058 'methods' => 'GET',
1059 'callback' => 'FrmAppController::api_install',
1060 'permission_callback' => __CLASS__ . '::can_update_db',
1061 );
1062
1063 register_rest_route( 'frm-admin/v1', '/install', $args );
1064
1065 $args = array(
1066 'methods' => 'GET',
1067 'callback' => 'FrmAddonsController::install_addon_api',
1068 'permission_callback' => 'FrmAddonsController::can_install_addon_api',
1069 );
1070
1071 register_rest_route( 'frm-admin/v1', '/install-addon', $args );
1072 }
1073
1074 /**
1075 * Make sure the install is only being run when we tell it to.
1076 * We don't want to run manually by people calling the API.
1077 *
1078 * @since 4.06.02
1079 */
1080 public static function can_update_db() {
1081 return get_transient( 'frm_updating_api' );
1082 }
1083
1084 /**
1085 * Run silent upgrade on each site in the network during a network upgrade.
1086 * Update database settings for all sites in a network during network upgrade process.
1087 *
1088 * @since 2.0.1
1089 *
1090 * @param int $blog_id Blog ID.
1091 *
1092 * @return void
1093 */
1094 public static function network_upgrade_site( $blog_id = 0 ) {
1095 // Flag to check if install is happening as intended.
1096 set_transient( 'frm_updating_api', true, MINUTE_IN_SECONDS );
1097 $request = new WP_REST_Request( 'GET', '/frm-admin/v1/install' );
1098
1099 self::maybe_add_wp_site_health();
1100
1101 if ( $blog_id ) {
1102 switch_to_blog( $blog_id );
1103 $response = rest_do_request( $request );
1104 restore_current_blog();
1105 } else {
1106 $response = rest_do_request( $request );
1107 }
1108
1109 if ( $response->is_error() ) {
1110 // if the remove post fails, use javascript instead
1111 add_action( 'admin_notices', 'FrmAppController::install_js_fallback' );
1112 }
1113 }
1114
1115 /**
1116 * Make sure WP_Site_Health has been included because it is required when calling rest_do_request.
1117 * Check first that the file exists because WP_Site_Health was only introduced in WordPress 5.2.
1118 *
1119 * @return void
1120 */
1121 private static function maybe_add_wp_site_health() {
1122 if ( ! class_exists( 'WP_Site_Health' ) ) {
1123 $wp_site_health_path = ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
1124 if ( file_exists( $wp_site_health_path ) ) {
1125 require_once $wp_site_health_path;
1126 }
1127 }
1128 }
1129
1130 /**
1131 * @since 3.0
1132 *
1133 * @return true
1134 */
1135 public static function api_install() {
1136 delete_transient( 'frm_updating_api' );
1137 if ( self::needs_update() ) {
1138 $running = get_option( 'frm_install_running' );
1139 if ( false === $running || $running < strtotime( '-5 minutes' ) ) {
1140 update_option( 'frm_install_running', time(), 'no' );
1141 self::install();
1142 delete_option( 'frm_install_running' );
1143 }
1144 }
1145
1146 return true;
1147 }
1148
1149 /**
1150 * Silent database upgrade (no redirect).
1151 * Called via ajax request during network upgrade process.
1152 *
1153 * @since 2.0.1
1154 *
1155 * @return void
1156 */
1157 public static function ajax_install() {
1158 FrmAppHelper::permission_check( 'frm_change_settings' );
1159 check_ajax_referer( 'frm_ajax', 'nonce' );
1160 self::api_install();
1161 wp_die();
1162 }
1163
1164 /**
1165 * @return void
1166 */
1167 public static function install() {
1168 $frmdb = new FrmMigrate();
1169 $frmdb->upgrade();
1170 }
1171
1172 /**
1173 * @return void
1174 */
1175 public static function uninstall() {
1176 FrmAppHelper::permission_check( 'administrator' );
1177 check_ajax_referer( 'frm_ajax', 'nonce' );
1178
1179 $frmdb = new FrmMigrate();
1180 $frmdb->uninstall();
1181
1182 // Disable the plugin and redirect after uninstall so the tables don't get added right back.
1183 $plugins = array( FrmAppHelper::plugin_folder() . '/formidable.php', 'formidable-pro/formidable-pro.php' );
1184 deactivate_plugins( $plugins, false, false );
1185 echo esc_url_raw( admin_url( 'plugins.php?deactivate=true' ) );
1186
1187 wp_die();
1188 }
1189
1190 public static function drop_tables( $tables ) {
1191 global $wpdb;
1192 $tables[] = $wpdb->prefix . 'frm_fields';
1193 $tables[] = $wpdb->prefix . 'frm_forms';
1194 $tables[] = $wpdb->prefix . 'frm_items';
1195 $tables[] = $wpdb->prefix . 'frm_item_metas';
1196
1197 return $tables;
1198 }
1199
1200 /**
1201 * @return void
1202 */
1203 public static function deauthorize() {
1204 FrmAppHelper::permission_check( 'frm_change_settings' );
1205 check_ajax_referer( 'frm_ajax', 'nonce' );
1206
1207 delete_option( 'frmpro-credentials' );
1208 delete_option( 'frmpro-authorized' );
1209 delete_site_option( 'frmpro-credentials' );
1210 delete_site_option( 'frmpro-authorized' );
1211 wp_die();
1212 }
1213
1214 /**
1215 * This is triggered when Formidable is activated.
1216 *
1217 * @return void
1218 */
1219 public static function handle_activation() {
1220 self::maybe_activate_payment_cron();
1221 }
1222
1223 /**
1224 * The payment cron is unscheduled when Formidable is deactivated.
1225 * We need to add it back again on activation if Stripe is configured.
1226 *
1227 * @since 6.5
1228 *
1229 * @return void
1230 */
1231 private static function maybe_activate_payment_cron() {
1232 if ( ! FrmStrpLiteConnectHelper::stripe_connect_is_setup() ) {
1233 return;
1234 }
1235
1236 FrmTransLiteAppController::maybe_schedule_cron();
1237 }
1238
1239 public static function set_footer_text( $text ) {
1240 if ( FrmAppHelper::is_formidable_admin() ) {
1241 $text = '';
1242 }
1243
1244 return $text;
1245 }
1246
1247 /**
1248 * Add admin footer links.
1249 *
1250 * @since 6.4.1
1251 *
1252 * @return void
1253 */
1254 public static function add_admin_footer_links() {
1255 if ( self::should_show_footer_links() ) {
1256 include FrmAppHelper::plugin_path() . '/classes/views/shared/admin-footer-links.php';
1257 }
1258 }
1259
1260 /**
1261 * Check if the footer links should be shown.
1262 *
1263 * @since 6.7
1264 *
1265 * @return bool
1266 */
1267 private static function should_show_footer_links() {
1268 $post_type = FrmAppHelper::simple_get( 'post_type', 'sanitize_title' );
1269 $is_formidable_page = FrmAppHelper::is_formidable_admin() || 'frm_logs' === $post_type;
1270 $show_footer_links = $is_formidable_page;
1271 if ( FrmAppHelper::is_full_screen() || ! FrmAppHelper::is_formidable_branding() ) {
1272 $show_footer_links = false;
1273 }
1274
1275 /**
1276 * Filter whether to show the Formidable footer links.
1277 *
1278 * @since 6.7
1279 *
1280 * @param bool $show_footer_links
1281 * @return bool
1282 */
1283 return apply_filters( 'frm_show_footer_links', $show_footer_links );
1284 }
1285
1286 /**
1287 * Show an error modal and terminate the script execution.
1288 *
1289 * @since 6.7
1290 *
1291 * @param array $error_args Arguments that control the behavior of the error modal.
1292 *
1293 * @return void
1294 */
1295 public static function show_error_modal( $error_args ) {
1296 $defaults = array(
1297 'title' => '',
1298 'body' => '',
1299 'cancel_url' => '',
1300 'cancel_classes' => '',
1301 'continue_url' => '',
1302 'continue_classes' => '',
1303 'icon' => 'frm_lock_simple',
1304 );
1305
1306 $error_args = wp_parse_args( $error_args, $defaults );
1307 if ( ! isset( $error_args['cancel_text'] ) && ! empty( $error_args['cancel_url'] ) ) {
1308 $error_args['cancel_text'] = __( 'Cancel', 'formidable' );
1309 }
1310
1311 if ( ! isset( $error_args['continue_text'] ) && ! empty( $error_args['continue_url'] ) ) {
1312 $error_args['continue_text'] = __( 'Continue', 'formidable' );
1313 }
1314
1315 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/error-modal.php';
1316 }
1317
1318 /**
1319 * Handles Floating Links' scripts and styles enqueueing.
1320 *
1321 * @since 6.4
1322 *
1323 * @param string $plugin_url URL of the plugin.
1324 * @param string $version Current version of the plugin.
1325 * @return void
1326 */
1327 private static function enqueue_floating_links( $plugin_url, $version ) {
1328 if ( ! $plugin_url || ! $version ) {
1329 // If any required parameters are missing, exit early.
1330 return;
1331 }
1332
1333 // Enqueue the Floating Links styles.
1334 wp_enqueue_style( 's11-floating-links', $plugin_url . '/css/packages/s11-floating-links.css', array(), $version );
1335
1336 // Enqueue the Floating Links script.
1337 wp_enqueue_script( 's11-floating-links', $plugin_url . '/js/packages/floating-links/s11-floating-links.js', array( 'formidable_admin' ), $version, true );
1338
1339 // Enqueue the config script.
1340 wp_enqueue_script( 's11-floating-links-config', $plugin_url . '/js/packages/floating-links/config.js', array( 'wp-i18n' ), $version, true );
1341
1342 if ( function_exists( 'wp_set_script_translations' ) ) {
1343 wp_set_script_translations( 's11-floating-links-config', 's11-' );
1344 }
1345
1346 $floating_links_data = array(
1347 'proIsInstalled' => FrmAppHelper::pro_is_installed(),
1348 );
1349 wp_localize_script( 's11-floating-links-config', 's11FloatingLinksData', $floating_links_data );
1350
1351 /**
1352 * Prompt Pro to load additional floating links scripts.
1353 * This is used to include images in the Inbox SlideIn when Pro is active.
1354 *
1355 * @since 6.8.4
1356 */
1357 do_action( 'frm_enqueue_floating_links' );
1358 }
1359
1360 /**
1361 * @deprecated 3.0 This is still referenced in https://formidableforms.com/knowledgebase/php-examples/ as of May 8, 2024.
1362 * @codeCoverageIgnore
1363 */
1364 public static function page_route( $content ) {
1365 return FrmDeprecated::page_route( $content );
1366 }
1367
1368 /**
1369 * Check if we are in our admin pages.
1370 *
1371 * @return bool
1372 */
1373 private static function in_our_pages() {
1374 global $current_screen;
1375 return FrmAppHelper::is_formidable_admin() || ( ! empty( $current_screen->post_type ) && 'frm_logs' === $current_screen->post_type );
1376 }
1377
1378 /**
1379 * Hide all third-parties admin notices only in our admin pages.
1380 *
1381 * @return void
1382 */
1383 public static function filter_admin_notices() {
1384 if ( ! self::in_our_pages() ) {
1385 return;
1386 }
1387
1388 $actions = array(
1389 'admin_notices',
1390 'network_admin_notices',
1391 'user_admin_notices',
1392 'all_admin_notices',
1393 );
1394
1395 global $wp_filter;
1396
1397 foreach ( $actions as $action ) {
1398 if ( empty( $wp_filter[ $action ]->callbacks ) ) {
1399 continue;
1400 }
1401 foreach ( $wp_filter[ $action ]->callbacks as $priority => $callbacks ) {
1402 foreach ( $callbacks as $callback_name => $callback ) {
1403 if ( self::is_our_callback_string( $callback_name ) || self::is_our_callback_array( $callback ) ) {
1404 continue;
1405 }
1406 unset( $wp_filter[ $action ]->callbacks[ $priority ][ $callback_name ] );
1407 }
1408 }
1409 }
1410 }
1411
1412 /**
1413 * Validate that the callback name is ours not from third-party.
1414 *
1415 * @param string $callback_name WordPress callback name.
1416 *
1417 * @return bool
1418 */
1419 private static function is_our_callback_string( $callback_name ) {
1420 return 0 === stripos( $callback_name, 'frm' );
1421 }
1422
1423 /**
1424 * Validate that the callback array is ours not from third-party.
1425 *
1426 * @param array $callback WordPress callback array.
1427 *
1428 * @return bool
1429 */
1430 private static function is_our_callback_array( $callback ) {
1431 return ! empty( $callback['function'] ) &&
1432 is_array( $callback['function'] ) &&
1433 ! empty( $callback['function'][0] ) &&
1434 self::is_our_callback_string( is_object( $callback['function'][0] ) ? get_class( $callback['function'][0] ) : $callback['function'][0] );
1435 }
1436 }
1437