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

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

3,428 lines 98.2 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 FrmFormsController {
7
8 /**
9 * Track the form that opened the redirect URL in a new tab. This is used to check if we should show the default
10 * message in the current tab.
11 *
12 * @since 6.2
13 *
14 * @var array Keys are form IDs and values are 1.
15 */
16 private static $redirected_in_new_tab = array();
17
18 /**
19 * The HTML for the Formdiable TinyMCE button (That triggers a popup to insert shortcodes)
20 * is stored here and re-used as an optimization.
21 *
22 * @since 6.11
23 *
24 * @var string|null
25 */
26 private static $formidable_tinymce_button;
27
28 public static function menu() {
29 $menu_label = __( 'Forms', 'formidable' );
30 if ( ! FrmAppHelper::pro_is_installed() ) {
31 $menu_label .= ' (Lite)';
32 }
33 add_submenu_page( 'formidable', 'Formidable | ' . $menu_label, $menu_label, 'frm_view_forms', 'formidable', 'FrmFormsController::route' );
34
35 self::maybe_load_listing_hooks();
36 }
37
38 public static function maybe_load_listing_hooks() {
39 if ( ! FrmAppHelper::on_form_listing_page() ) {
40 return;
41 }
42
43 add_filter( 'get_user_option_managetoplevel_page_formidablecolumnshidden', 'FrmFormsController::hidden_columns' );
44
45 add_filter( 'manage_toplevel_page_formidable_columns', 'FrmFormsController::get_columns', 0 );
46 add_filter( 'manage_toplevel_page_formidable_sortable_columns', 'FrmFormsController::get_sortable_columns' );
47 }
48
49 public static function head() {
50 if ( wp_is_mobile() ) {
51 wp_enqueue_script( 'jquery-touch-punch' );
52 }
53 }
54
55 public static function register_widgets() {
56 require_once FrmAppHelper::plugin_path() . '/classes/widgets/FrmShowForm.php';
57 register_widget( 'FrmShowForm' );
58 }
59
60 /**
61 * Show a message about conditional logic
62 *
63 * @since 4.06.03
64 */
65 public static function logic_tip() {
66 $images_url = FrmAppHelper::plugin_url() . '/images/';
67 $data_message = __( 'Only show the fields you need and create branching forms. Upgrade to get conditional logic and question branching.', 'formidable' );
68 $data_message .= ' <img src="' . esc_url( $images_url ) . '/survey-logic.png" srcset="' . esc_url( $images_url ) . 'survey-logic@2x.png 2x" alt="' . esc_attr__( 'Conditional Logic options', 'formidable' ) . '"/>';
69 echo '<a href="javascript:void(0)" class="frm_noallow frm_show_upgrade frm_add_logic_link frm-collapsed frm-flex-justify" data-upgrade="' . esc_attr__( 'Conditional Logic options', 'formidable' ) . '" data-message="' . esc_attr( $data_message ) . '" data-medium="builder" data-content="logic">';
70 esc_html_e( 'Conditional Logic', 'formidable' );
71 FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown8_icon', array( 'aria-hidden' => 'true' ) );
72 echo '</a>';
73 }
74
75 /**
76 * By default, Divi processes form shortcodes on the edit post page.
77 * Now that won't do.
78 *
79 * @since 3.01
80 */
81 public static function prevent_divi_conflict( $shortcodes ) {
82 $shortcodes[] = 'formidable';
83
84 return $shortcodes;
85 }
86
87 /**
88 * @return void
89 */
90 public static function list_form() {
91 FrmAppHelper::permission_check( 'frm_view_forms' );
92
93 $message = '';
94 $params = FrmForm::list_page_params();
95 $errors = self::process_bulk_form_actions( array() );
96 if ( isset( $errors['message'] ) ) {
97 $message = $errors['message'];
98 unset( $errors['message'] );
99 }
100 $errors = apply_filters( 'frm_admin_list_form_action', $errors );
101
102 self::display_forms_list( $params, $message, $errors );
103 }
104
105 /**
106 * Create the default email action
107 *
108 * @since 2.02.11
109 *
110 * @param int|object $form
111 * @return void
112 */
113 private static function create_default_email_action( $form ) {
114 FrmForm::maybe_get_form( $form );
115 if ( ! is_object( $form ) ) {
116 return;
117 }
118
119 $create_email = apply_filters( 'frm_create_default_email_action', true, $form );
120
121 if ( $create_email ) {
122 /**
123 * @var FrmFormAction
124 */
125 $action_control = FrmFormActionsController::get_form_actions( 'email' );
126 $action_control->create( $form->id );
127 }
128 }
129
130 /**
131 * Create the default On submit action
132 *
133 * @since 6.0.0
134 *
135 * @param int|object $form Form object or ID.
136 * @return void
137 */
138 private static function create_default_on_submit_action( $form ) {
139 FrmForm::maybe_get_form( $form );
140 if ( ! is_object( $form ) ) {
141 return;
142 }
143
144 /**
145 * Enable or disable the default On Submit action.
146 *
147 * @since 6.0.0
148 *
149 * @param bool $create Set to `false` if you want to disable.
150 * @param object $form Form object.
151 */
152 $create = apply_filters( 'frm_create_default_on_submit_action', true, $form );
153
154 if ( $create ) {
155 /**
156 * @var FrmFormAction
157 */
158 $action_control = FrmFormActionsController::get_form_actions( FrmOnSubmitAction::$slug );
159 $action_control->create( $form->id );
160 }
161 }
162
163 /**
164 * Creates submit button field.
165 *
166 * @since 6.9
167 *
168 * @param int|object $form Form ID or object.
169 * @return void
170 */
171 private static function create_submit_button_field( $form ) {
172 FrmForm::maybe_get_form( $form );
173 if ( ! is_object( $form ) ) {
174 return;
175 }
176
177 if ( FrmSubmitHelper::get_submit_field( $form->id ) ) {
178 // Do not create submit button field if it exists.
179 return;
180 }
181
182 FrmField::create(
183 array(
184 'type' => FrmSubmitHelper::FIELD_TYPE,
185 'name' => __( 'Submit', 'formidable' ),
186 'field_order' => FrmSubmitHelper::DEFAULT_ORDER,
187 'form_id' => $form->id,
188 'field_options' => FrmFieldsHelper::get_default_field_options( FrmSubmitHelper::FIELD_TYPE ),
189 'description' => '',
190 'default_value' => '',
191 'options' => array(),
192 )
193 );
194 }
195
196 /**
197 * @return void
198 */
199 public static function edit( $values = false ) {
200 FrmAppHelper::permission_check( 'frm_edit_forms' );
201
202 $id = isset( $values['id'] ) ? absint( $values['id'] ) : FrmAppHelper::get_param( 'id', '', 'get', 'absint' );
203
204 self::get_edit_vars( $id );
205 }
206
207 /**
208 * @param mixed $id
209 * @param string $message
210 * @return void
211 */
212 public static function settings( $id = false, $message = '' ) {
213 FrmAppHelper::permission_check( 'frm_edit_forms' );
214
215 if ( ! $id || ! is_numeric( $id ) ) {
216 $id = FrmAppHelper::get_param( 'id', '', 'get', 'absint' );
217 }
218
219 FrmOnSubmitHelper::maybe_migrate_submit_settings_to_action( $id );
220
221 self::get_settings_vars( $id, array(), $message );
222 }
223
224 public static function update_settings() {
225 FrmAppHelper::permission_check( 'frm_edit_forms' );
226 $process_form = FrmAppHelper::get_post_param( 'process_form', '', 'sanitize_text_field' );
227
228 if ( ! wp_verify_nonce( $process_form, 'process_form_nonce' ) ) {
229 $frm_settings = FrmAppHelper::get_settings();
230 $error_args = array(
231 'title' => __( 'Verification failed', 'formidable' ),
232 'body' => $frm_settings->admin_permission,
233 'cancel_text' => __( 'Cancel', 'formidable' ),
234 );
235 FrmAppController::show_error_modal( $error_args );
236 return;
237 }
238
239 $id = FrmAppHelper::get_param( 'id', '', 'get', 'absint' );
240
241 $errors = FrmForm::validate( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
242 $warnings = FrmFormsHelper::check_for_warnings( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
243
244 if ( count( $errors ) > 0 ) {
245 self::get_settings_vars( $id, $errors, compact( 'warnings' ) );
246 return;
247 }
248
249 do_action( 'frm_before_update_form_settings', $id );
250
251 $antispam_was_on = self::antispam_was_on( $id );
252
253 FrmForm::update( $id, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
254
255 $antispam_is_on = ! empty( $_POST['options']['antispam'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
256 if ( $antispam_is_on !== $antispam_was_on ) {
257 FrmAntiSpam::clear_caches();
258 }
259
260 $message = __( 'Settings Successfully Updated', 'formidable' );
261
262 self::get_settings_vars( $id, array(), compact( 'message', 'warnings' ) );
263 }
264
265 /**
266 * @param int $form_id
267 * @return bool
268 */
269 private static function antispam_was_on( $form_id ) {
270 $form = FrmForm::getOne( $form_id );
271 return ! empty( $form->options['antispam'] );
272 }
273
274 /**
275 * @return void
276 */
277 public static function update( $values = array() ) {
278 if ( empty( $values ) ) {
279 $values = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
280 }
281
282 // Set radio button and checkbox meta equal to "other" value.
283 if ( FrmAppHelper::pro_is_installed() ) {
284 $values = FrmProEntry::mod_other_vals( $values, 'back' );
285 }
286
287 $errors = FrmForm::validate( $values );
288 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'frm_save_form', 'frm_save_form_nonce' );
289 if ( $permission_error !== false ) {
290 $errors['form'] = $permission_error;
291 }
292
293 $id = isset( $values['id'] ) ? absint( $values['id'] ) : FrmAppHelper::get_param( 'id', '', 'get', 'absint' );
294
295 if ( count( $errors ) > 0 ) {
296 self::get_edit_vars( $id, $errors );
297 return;
298 }
299
300 self::maybe_remove_draft_option_from_fields( $id );
301
302 FrmForm::update( $id, $values );
303 $message = __( 'Form was successfully updated.', 'formidable' );
304
305 if ( self::is_too_long( $values ) ) {
306 $message .= '<br/> ' . sprintf(
307 /* translators: %1$s: Start link HTML, %2$s: end link HTML */
308 __( 'However, your form is very long and may be %1$sreaching server limits%2$s.', 'formidable' ),
309 '<a href="' . esc_url( self::get_form_too_long_docs_url() ) . '" target="_blank" rel="noopener">',
310 '</a>'
311 );
312 }
313
314 if ( defined( 'DOING_AJAX' ) ) {
315 wp_die( FrmAppHelper::kses( $message, array( 'a' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
316 }
317
318 self::get_edit_vars( $id, array(), $message );
319 }
320
321 /**
322 * @since 6.25.1
323 *
324 * @return string
325 */
326 private static function get_form_too_long_docs_url() {
327 $utm = array(
328 'campaign' => 'builder',
329 'content' => 'form-too-long',
330 );
331 return FrmAppHelper::admin_upgrade_link( $utm, 'knowledgebase/i-have-a-long-form-why-did-the-options-at-the-end-of-the-form-stop-saving/' );
332 }
333
334 /**
335 * Remove the draft flag from any new fields from this current session.
336 *
337 * @since 6.8
338 *
339 * @param int $form_id
340 * @return void
341 */
342 private static function maybe_remove_draft_option_from_fields( $form_id ) {
343 $draft_field_ids_csv = FrmAppHelper::get_post_param( 'draft_fields', '', 'sanitize_text_field' );
344 if ( ! $draft_field_ids_csv ) {
345 // If the draft_fields input is empty there are no new fields in the session.
346 return;
347 }
348
349 $draft_field_ids = array_filter( explode( ',', $draft_field_ids_csv ), 'is_numeric' );
350 if ( ! $draft_field_ids ) {
351 // Exit early if the draft fields input is invalid. It should be a CSV of integer values.
352 return;
353 }
354
355 $draft_field_rows = FrmFieldsHelper::get_draft_field_results( $form_id, $draft_field_ids );
356 foreach ( $draft_field_rows as $row ) {
357 $row->field_options['draft'] = 0;
358 FrmField::update( $row->id, array( 'field_options' => $row->field_options ) );
359 }
360 }
361
362 /**
363 * Check if the value at the end of the form was included.
364 * If it's missing, it means other values at the end of the form
365 * were likely not saved either.
366 *
367 * @since 3.06.01
368 *
369 * @param array $values
370 * @return bool
371 */
372 private static function is_too_long( $values ) {
373 return empty( $values['frm_end'] );
374 }
375
376 public static function duplicate() {
377 FrmAppHelper::permission_check( 'frm_edit_forms' );
378 $nonce = FrmAppHelper::simple_get( '_wpnonce' );
379
380 if ( ! wp_verify_nonce( $nonce ) ) {
381 $frm_settings = FrmAppHelper::get_settings();
382 wp_die( esc_html( $frm_settings->admin_permission ) );
383 }
384
385 $params = FrmForm::list_page_params();
386 $form = FrmForm::duplicate( $params['id'], $params['template'], true );
387 $url = admin_url( 'admin.php?page=formidable' );
388 $message = 'form_duplicate_error';
389
390 if ( $form ) {
391 $new_template = FrmAppHelper::simple_get( 'new_template' ) ? '&new_template=true' : '';
392 $url = admin_url( 'admin.php?page=formidable&frm_action=edit&id=' . absint( $form ) . $new_template );
393 $message = 'form_duplicated';
394 }
395
396 $url .= '&message=' . $message;
397
398 wp_safe_redirect( $url );
399 exit();
400 }
401
402 /**
403 * @return string|null
404 */
405 public static function page_preview() {
406 $params = FrmForm::list_page_params();
407 if ( ! $params['form'] || is_numeric( $params['form'] ) ) {
408 return null;
409 }
410
411 self::maybe_block_preview( $params['form'] );
412
413 $form = FrmForm::getOne( $params['form'] );
414 if ( ! $form ) {
415 return null;
416 }
417
418 return self::show_form( $form->id, '', 'auto', 'auto' );
419 }
420
421 /**
422 * @since 3.0
423 *
424 * @return void
425 */
426 public static function show_page_preview() {
427 $preview = self::page_preview();
428 if ( is_null( $preview ) ) {
429 wp_die(
430 '<h1>' . esc_html__( 'Form key is invalid', 'formidable' ) . '</h1>',
431 '<p>' . esc_html__( 'Form key is invalid', 'formidable' ) . '</p>',
432 404
433 );
434 }
435
436 echo $preview; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
437 }
438
439 /**
440 * @return void
441 */
442 public static function preview() {
443 do_action( 'frm_wp' );
444 FrmAppHelper::set_current_screen_and_hook_suffix();
445
446 global $frm_vars;
447 $frm_vars['preview'] = true;
448
449 // print_emoji_styles is deprecated.
450 remove_action( 'wp_print_styles', 'print_emoji_styles' );
451
452 if ( FrmTestModeController::should_add_test_mode_container() ) {
453 do_action( 'frm_test_mode_init' );
454 add_action( 'wp_enqueue_scripts', 'FrmTestModeController::register_and_enqueue_required_scripts' );
455 add_filter( 'frm_filter_final_form', 'FrmTestModeController::maybe_add_test_mode_container', 99 );
456 }
457
458 $include_theme = FrmAppHelper::get_param( 'theme', '', 'get', 'absint' );
459 if ( $include_theme ) {
460 self::set_preview_query();
461 self::load_theme_preview();
462 } else {
463 self::load_direct_preview();
464 }
465
466 wp_die();
467 }
468
469 /**
470 * Set the page global to any available page (except for the Blog Page).
471 *
472 * @return void
473 */
474 private static function set_preview_query() {
475 $page_query = array(
476 'numberposts' => 1,
477 'orderby' => 'date',
478 'order' => 'ASC',
479 'post_type' => 'page',
480 );
481
482 $page_for_posts = get_option( 'page_for_posts' );
483 if ( is_numeric( $page_for_posts ) ) {
484 // Avoid querying for the "Posts Page" or "Blog Page" so we don't display 10 forms.
485 $page_query['post__not_in'] = array( $page_for_posts );
486 }
487
488 $random_page = get_posts( $page_query );
489 if ( ! $random_page ) {
490 return;
491 }
492
493 $random_page = reset( $random_page );
494 if ( ! is_a( $random_page, 'WP_Post' ) ) {
495 // The return type can also be int.
496 return;
497 }
498
499 query_posts(
500 array(
501 'post_type' => 'page',
502 'page_id' => $random_page->ID,
503 )
504 );
505
506 // Fixes Pro issue #3004. Prevent an undefined $post object.
507 // Otherwise WordPress themes will trigger a warning "Attempt to read property "comment_count" on null".
508 self::set_post_global( $random_page );
509 }
510
511 /**
512 * Set the WP $post global object. Used for in-theme preview when defining a page.
513 *
514 * @since 5.5.2
515 *
516 * @param WP_Post $page The page object.
517 * @return void
518 */
519 private static function set_post_global( $page ) {
520 global $post;
521 $post = $page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
522 }
523
524 /**
525 * @since 3.0
526 *
527 * @return void
528 */
529 private static function load_theme_preview() {
530 add_filter( 'wp_title', 'FrmFormsController::preview_title', 9999 );
531 add_filter( 'the_title', 'FrmFormsController::preview_page_title', 9999 );
532 add_filter( 'the_content', 'FrmFormsController::preview_content', 9999 );
533 add_action( 'loop_no_results', 'FrmFormsController::show_page_preview' );
534 add_filter( 'is_active_sidebar', '__return_false' );
535 FrmStylesController::enqueue_css( 'enqueue', true );
536
537 if ( false === get_template_part( 'page' ) ) {
538 if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
539 add_filter( 'body_class', 'FrmFormsController::preview_block_theme_body_classnames' );
540 }
541 self::fallback_when_page_template_part_is_not_supported_by_theme();
542 }
543 }
544
545 /**
546 * Add padding to the body for block themes.
547 *
548 * @since 6.5.2
549 *
550 * @param array $classes The body classes list.
551 * @return array
552 */
553 public static function preview_block_theme_body_classnames( $classes ) {
554 $classes[] = 'has-global-padding';
555 return $classes;
556 }
557
558 /**
559 * Not every theme supports get_template_part( 'page' ).
560 * When this is not supported, false is returned, and we can handle a fallback.
561 *
562 * @return void
563 */
564 private static function fallback_when_page_template_part_is_not_supported_by_theme() {
565 if ( have_posts() ) {
566 the_post();
567 self::get_template( 'header' );
568
569 // add some generic class names to the container to add some natural padding to the content.
570 // .entry-content catches the WordPress TwentyTwenty theme.
571 // .container catches Customizr content.
572 echo '<div class="container entry-content">';
573 the_content();
574 echo '</div>';
575
576 // Prevent extra unexpected forms in the footer.
577 // For some reason this happens in the Twenty Twenty Five theme.
578 add_filter( 'frm_filter_final_form', '__return_empty_string' );
579
580 self::get_template( 'footer' );
581 }
582 }
583
584 /**
585 * Calls core function to get a template part if it doesn't cause deprecation warnings. Otherwise skips the deprecation function call
586 * and renders required html fragments calling required functions.
587 *
588 * @since 6.7.1
589 * @param string $template
590 * @return void
591 */
592 private static function get_template( $template ) {
593 if ( self::should_try_getting_template( $template ) ) {
594 call_user_func( 'get_' . $template );
595 return;
596 }
597
598 if ( 'header' === $template ) {
599 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/preview/header.php';
600 return;
601 }
602
603 if ( 'footer' === $template ) {
604 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/preview/footer.php';
605 }
606 }
607
608 /**
609 * Returns true if calling a template function doesn't trigger deprecation warnings.
610 *
611 * @since 6.7.1
612 * @param string $template
613 * @return bool
614 */
615 private static function should_try_getting_template( $template ) {
616 $stylesheet_path = get_stylesheet_directory();
617 $template_path = get_template_directory();
618 $is_child_theme = $stylesheet_path !== $template_path;
619 $template_name = $template . '.php';
620
621 return file_exists( $stylesheet_path . '/' . $template_name ) || $is_child_theme && file_exists( $template_path . '/' . $template_name );
622 }
623
624 /**
625 * Set the page title for the theme preview page
626 *
627 * @since 3.0
628 */
629 public static function preview_page_title( $title ) {
630 if ( in_the_loop() ) {
631 $title = self::preview_title( $title );
632 }
633
634 return $title;
635 }
636
637 /**
638 * Set the page title for the theme preview page.
639 *
640 * @since 3.0
641 *
642 * @param string $title
643 * @return string
644 */
645 public static function preview_title( $title ) {
646 return __( 'Form Preview', 'formidable' );
647 }
648
649 /**
650 * Set the page content for the theme preview page.
651 *
652 * @since 3.0
653 *
654 * @param string $content
655 * @return string
656 */
657 public static function preview_content( $content ) {
658 if ( in_the_loop() ) {
659 self::show_page_preview();
660 // Clear the content for the page we're using.
661 $content = '';
662 }
663
664 return $content;
665 }
666
667 /**
668 * @since 3.0
669 */
670 private static function load_direct_preview() {
671 $key = FrmAppHelper::simple_get( 'form', 'sanitize_title' );
672 if ( $key == '' ) {
673 $key = FrmAppHelper::get_post_param( 'form', '', 'sanitize_title' );
674 }
675
676 if ( ! $key ) {
677 $error = __( 'Form key is missing', 'formidable' );
678 wp_die(
679 '<h1>' . esc_html( $error ) . '</h1>',
680 '<p>' . esc_html( $error ) . '</p>',
681 404
682 );
683 }
684
685 self::maybe_block_preview( $key );
686
687 $form = FrmForm::getAll( array( 'form_key' => $key ), '', 1 );
688 if ( ! $form ) {
689 $error = __( 'Form does not exist', 'formidable' );
690 wp_die(
691 '<h1>' . esc_html( $error ) . '</h1>',
692 '<p>' . esc_html( $error ) . '</p>',
693 404
694 );
695 }
696
697 header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
698
699 self::fix_deprecated_null_param_warning();
700
701 require FrmAppHelper::plugin_path() . '/classes/views/frm-entries/direct.php';
702 }
703
704 /**
705 * Block the form preview for the contact form by default if the user cannot view forms.
706 * This is to prevent the contact form being exploited.
707 * Since this form is added by default and every site uses the same key, it is too easy
708 * to guess this form.
709 *
710 * @since 6.20
711 *
712 * @param string $form_key Form key.
713 * @return void
714 */
715 public static function maybe_block_preview( $form_key ) {
716 if ( FrmFormsHelper::should_block_preview( $form_key ) ) {
717 $error = __( 'You do not have permission to view this form', 'formidable' );
718 wp_die(
719 '<h1>' . esc_html( $error ) . '</h1>',
720 '<p>' . esc_html( $error ) . '</p>',
721 403
722 );
723 }
724 }
725
726 /**
727 * Some themes have a null $src value.
728 * This function adds a filter to ensure that $src is not null.
729 * WP will call str_starts_with with the null value triggering a deprecated message otherwise.
730 *
731 * @since 6.10
732 *
733 * @return void
734 */
735 private static function fix_deprecated_null_param_warning() {
736 add_filter(
737 'script_loader_src',
738 /**
739 * @param string|null $src
740 * @return string
741 */
742 function ( $src ) {
743 if ( is_null( $src ) ) {
744 $src = '';
745 }
746 return $src;
747 }
748 );
749 }
750
751 public static function untrash() {
752 self::change_form_status( 'untrash' );
753 }
754
755 /**
756 * @param array $ids
757 * @return string
758 */
759 public static function bulk_untrash( $ids ) {
760 FrmAppHelper::permission_check( 'frm_edit_forms' );
761
762 $count = FrmForm::set_status( $ids, 'published' );
763
764 if ( ! $count ) {
765 // Don't show "0 forms restored" on refresh.
766 return '';
767 }
768
769 /* translators: %1$s: Number of forms */
770 $message = sprintf( _n( '%1$s form restored from the Trash.', '%1$s forms restored from the Trash.', $count, 'formidable' ), $count );
771
772 return $message;
773 }
774
775 /**
776 * @since 3.06
777 */
778 public static function ajax_trash() {
779 FrmAppHelper::permission_check( 'frm_delete_forms' );
780 check_ajax_referer( 'frm_ajax', 'nonce' );
781 $form_id = FrmAppHelper::get_param( 'id', '', 'post', 'absint' );
782 FrmForm::set_status( $form_id, 'trash' );
783 wp_die();
784 }
785
786 public static function trash() {
787 self::change_form_status( 'trash' );
788 }
789
790 /**
791 * @param string $status
792 *
793 * @return void
794 */
795 public static function change_form_status( $status ) {
796 $available_status = array(
797 'untrash' => array(
798 'permission' => 'frm_edit_forms',
799 'new_status' => 'published',
800 ),
801 'trash' => array(
802 'permission' => 'frm_delete_forms',
803 'new_status' => 'trash',
804 ),
805 );
806
807 if ( ! isset( $available_status[ $status ] ) ) {
808 return;
809 }
810
811 FrmAppHelper::permission_check( $available_status[ $status ]['permission'] );
812
813 $params = FrmForm::list_page_params();
814
815 // Check nonce url.
816 check_admin_referer( $status . '_form_' . $params['id'] );
817
818 $count = 0;
819 if ( FrmForm::set_status( $params['id'], $available_status[ $status ]['new_status'] ) ) {
820 ++$count;
821 }
822
823 $form_type = FrmAppHelper::get_simple_request(
824 array(
825 'param' => 'form_type',
826 'type' => 'request',
827 )
828 );
829
830 /* translators: %1$s: Number of forms */
831 $available_status['untrash']['message'] = sprintf( _n( '%1$s form restored from the Trash.', '%1$s forms restored from the Trash.', $count, 'formidable' ), $count );
832
833 /* translators: %1$s: Number of forms, %2$s: Start link HTML, %3$s: End link HTML */
834 $available_status['trash']['message'] = sprintf( _n( '%1$s form moved to the Trash. %2$sUndo%3$s', '%1$s forms moved to the Trash. %2$sUndo%3$s', $count, 'formidable' ), $count, '<a href="' . esc_url( wp_nonce_url( '?page=formidable&frm_action=untrash&form_type=' . $form_type . '&id=' . $params['id'], 'untrash_form_' . $params['id'] ) ) . '">', '</a>' );
835
836 $message = $available_status[ $status ]['message'];
837
838 self::display_forms_list( $params, $message );
839 }
840
841 /**
842 * @param array $ids
843 * @return string
844 */
845 public static function bulk_trash( $ids ) {
846 FrmAppHelper::permission_check( 'frm_delete_forms' );
847
848 $count = 0;
849 foreach ( $ids as $id ) {
850 if ( FrmForm::trash( $id ) ) {
851 ++$count;
852 }
853 }
854
855 if ( ! $count ) {
856 return '';
857 }
858
859 $current_page = FrmAppHelper::get_simple_request(
860 array(
861 'param' => 'form_type',
862 'type' => 'request',
863 )
864 );
865 $message = sprintf(
866 /* translators: %1$s: Number of forms, %2$s: Start link HTML, %3$s: End link HTML */
867 _n( '%1$s form moved to the Trash. %2$sUndo%3$s', '%1$s forms moved to the Trash. %2$sUndo%3$s', $count, 'formidable' ),
868 $count,
869 '<a href="' . esc_url( wp_nonce_url( '?page=formidable&frm_action=list&action=bulk_untrash&form_type=' . $current_page . '&item-action=' . implode( ',', $ids ), 'bulk-toplevel_page_formidable' ) ) . '">',
870 '</a>'
871 );
872
873 return $message;
874 }
875
876 public static function destroy() {
877 FrmAppHelper::permission_check( 'frm_delete_forms' );
878
879 $params = FrmForm::list_page_params();
880
881 // Check nonce url.
882 check_admin_referer( 'destroy_form_' . $params['id'] );
883
884 $count = 0;
885 if ( FrmForm::destroy( $params['id'] ) ) {
886 ++$count;
887 }
888
889 /* translators: %1$s: Number of forms */
890 $message = sprintf( _n( '%1$s Form Permanently Deleted', '%1$s Forms Permanently Deleted', $count, 'formidable' ), $count );
891
892 self::display_forms_list( $params, $message );
893 }
894
895 /**
896 * @param array $ids
897 * @return string
898 */
899 public static function bulk_destroy( $ids ) {
900 FrmAppHelper::permission_check( 'frm_delete_forms' );
901
902 $count = 0;
903 foreach ( $ids as $id ) {
904 $d = FrmForm::destroy( $id );
905 if ( $d ) {
906 ++$count;
907 }
908 }
909
910 if ( $count ) {
911 /* translators: %1$s: Number of forms */
912 return sprintf( _n( '%1$s form permanently deleted.', '%1$s forms permanently deleted.', $count, 'formidable' ), $count );
913 }
914
915 return '';
916 }
917
918 /**
919 * @since 6.7.1 Function went from private to public.
920 */
921 public static function delete_all() {
922 // Check nonce url.
923 $permission_error = FrmAppHelper::permission_nonce_error( 'frm_delete_forms', '_wpnonce', 'bulk-toplevel_page_formidable' );
924 if ( $permission_error !== false ) {
925 self::display_forms_list( array(), '', array( $permission_error ) );
926
927 return;
928 }
929
930 $count = FrmForm::scheduled_delete( time() );
931 $url = remove_query_arg( array( 'delete_all' ) );
932
933 $url .= '&message=forms_permanently_deleted&forms_deleted=' . $count;
934
935 wp_safe_redirect( $url );
936 die();
937 }
938
939 /**
940 * Create a new form from the modal.
941 *
942 * @since 4.0
943 */
944 public static function build_new_form() {
945 FrmAppHelper::permission_check( 'frm_edit_forms' );
946 check_ajax_referer( 'frm_ajax', 'nonce' );
947
948 $new_values = self::get_modal_values();
949 $new_values['form_key'] = $new_values['name'];
950 $new_values['options'] = array(
951 'antispam' => 1,
952 'on_submit_migrated' => 1,
953 );
954
955 /**
956 * Allows changing form values before creating from the modal.
957 *
958 * @since 5.4
959 *
960 * @param array $values Form values.
961 */
962 $new_values = apply_filters( 'frm_new_form_values', $new_values );
963
964 $form_id = FrmForm::create( $new_values );
965 /**
966 * @since 5.3
967 *
968 * @param int $form_id
969 */
970 do_action( 'frm_build_new_form', $form_id );
971
972 self::create_submit_button_field( $form_id );
973 self::create_default_on_submit_action( $form_id );
974 self::create_default_email_action( $form_id );
975
976 $response = array(
977 'redirect' => FrmForm::get_edit_link( $form_id ) . '&new_template=true',
978 );
979
980 echo wp_json_encode( $response );
981 wp_die();
982 }
983
984 /**
985 * Before creating a new form, get the name and description from the modal.
986 *
987 * @since 4.0
988 *
989 * @return array<string,string>
990 */
991 public static function get_modal_values() {
992 $name = FrmAppHelper::get_param( 'name', '', 'post', 'sanitize_text_field' );
993 $desc = FrmAppHelper::get_param( 'desc', '', 'post', 'sanitize_textarea_field' );
994
995 return array(
996 'name' => $name,
997 'description' => $desc,
998 );
999 }
1000
1001 /**
1002 * Inserts Formidable button
1003 * Hook exists since 2.5.0
1004 *
1005 * @since 2.0.15
1006 *
1007 * @return void
1008 */
1009 public static function insert_form_button() {
1010 if ( current_user_can( 'frm_view_forms' ) ) {
1011 // Store the result in memory and re-use it when this function is called multiple times.
1012 // This helps speed up the form builder when there are a lot of HTML fields, where this
1013 // button is inserted once per HTML field.
1014 // In a form with 66 HTML fields, this saves 0.5 seconds on page load time, tested locally.
1015 if ( ! isset( self::$formidable_tinymce_button ) ) {
1016 FrmAppHelper::load_admin_wide_js();
1017 $menu_name = FrmAppHelper::get_menu_name();
1018 $icon = apply_filters( 'frm_media_icon', FrmAppHelper::svg_logo() );
1019 self::$formidable_tinymce_button = '<a href="#TB_inline?width=50&height=50&inlineId=frm_insert_form" class="thickbox button add_media frm_insert_form" title="' . esc_attr__( 'Add forms and content', 'formidable' ) . '">' . FrmAppHelper::kses( $icon, 'all' ) . ' ' . esc_html( $menu_name ) . '</a>';
1020 }
1021 echo self::$formidable_tinymce_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1022 }
1023 }
1024
1025 /**
1026 * @return void
1027 */
1028 public static function insert_form_popup() {
1029 if ( ! self::should_insert_form_popup() ) {
1030 return;
1031 }
1032
1033 FrmAppHelper::load_admin_wide_js();
1034
1035 $shortcodes = array(
1036 'formidable' => array(
1037 'name' => __( 'Form', 'formidable' ),
1038 'label' => __( 'Insert a Form', 'formidable' ),
1039 ),
1040 );
1041 $shortcodes = apply_filters( 'frm_popup_shortcodes', $shortcodes );
1042
1043 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/insert_form_popup.php';
1044
1045 if ( FrmAppHelper::is_form_builder_page() && ! class_exists( '_WP_Editors', false ) ) {
1046 // initialize a wysiwyg so we have usable settings defined in tinyMCEPreInit.mceInit
1047 require ABSPATH . WPINC . '/class-wp-editor.php';
1048 ?>
1049 <div class="frm_hidden">
1050 <?php wp_editor( '', 'frm_description_placeholder', array() ); ?>
1051 </div>
1052 <?php
1053 }
1054 }
1055
1056 /**
1057 * Check the page being loaded, determine if this is a page that should include the form popup.
1058 *
1059 * @since 5.0.14
1060 *
1061 * @return bool
1062 */
1063 private static function should_insert_form_popup() {
1064 if ( FrmAppHelper::is_form_builder_page() ) {
1065 return true;
1066 }
1067 $page = basename( FrmAppHelper::get_server_value( 'PHP_SELF' ) );
1068 return in_array( $page, array( 'post.php', 'page.php', 'page-new.php', 'post-new.php' ), true );
1069 }
1070
1071 public static function get_shortcode_opts() {
1072 FrmAppHelper::permission_check( 'frm_view_forms' );
1073 check_ajax_referer( 'frm_ajax', 'nonce' );
1074
1075 $shortcode = FrmAppHelper::get_post_param( 'shortcode', '', 'sanitize_text_field' );
1076 if ( empty( $shortcode ) ) {
1077 wp_die();
1078 }
1079
1080 echo '<div id="sc-opts-' . esc_attr( $shortcode ) . '" class="frm_shortcode_option">';
1081 echo '<input type="radio" name="frmsc" value="' . esc_attr( $shortcode ) . '" id="sc-' . esc_attr( $shortcode ) . '" class="frm_hidden" />';
1082
1083 $form_id = '';
1084 $opts = array();
1085 switch ( $shortcode ) {
1086 case 'formidable':
1087 $opts = array(
1088 'form_id' => 'id',
1089 'title' => array(
1090 'val' => 1,
1091 'label' => __( 'Display form title', 'formidable' ),
1092 ),
1093 'description' => array(
1094 'val' => 1,
1095 'label' => __( 'Display form description', 'formidable' ),
1096 ),
1097 'minimize' => array(
1098 'val' => 1,
1099 'label' => __( 'Minimize form HTML', 'formidable' ),
1100 ),
1101 );
1102 }
1103 $opts = apply_filters( 'frm_sc_popup_opts', $opts, $shortcode );
1104
1105 if ( isset( $opts['form_id'] ) && is_string( $opts['form_id'] ) ) {
1106 // allow other shortcodes to use the required form id option
1107 $form_id = $opts['form_id'];
1108 unset( $opts['form_id'] );
1109 }
1110
1111 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/shortcode_opts.php';
1112
1113 echo '</div>';
1114
1115 wp_die();
1116 }
1117
1118 /**
1119 * Display list of forms in a table.
1120 *
1121 * @param array $params
1122 * @param string $message
1123 * @param array $errors
1124 * @return void
1125 */
1126 public static function display_forms_list( $params = array(), $message = '', $errors = array() ) {
1127 FrmAppHelper::permission_check( 'frm_view_forms' );
1128
1129 global $wpdb, $frm_vars;
1130
1131 if ( ! $params ) {
1132 $params = FrmForm::list_page_params();
1133 }
1134
1135 /**
1136 * @since 5.3.1
1137 *
1138 * @param string $table_class Class name for List Helper.
1139 */
1140 $table_class = apply_filters( 'frm_forms_list_class', 'FrmFormsListHelper' );
1141 $wp_list_table = new $table_class( compact( 'params' ) );
1142
1143 $pagenum = $wp_list_table->get_pagenum();
1144
1145 $wp_list_table->prepare_items();
1146
1147 $total_pages = $wp_list_table->get_pagination_arg( 'total_pages' );
1148 if ( $pagenum > $total_pages && $total_pages > 0 ) {
1149 wp_redirect( esc_url_raw( add_query_arg( 'paged', $total_pages ) ) );
1150 die();
1151 }
1152
1153 $inbox = new FrmInbox();
1154 $error = $inbox->check_for_error();
1155 if ( $error ) {
1156 $show_messages = array( $error['subject'] . '. ' . $error['message'] );
1157 }
1158
1159 require FrmAppHelper::plugin_path() . '/classes/views/frm-forms/list.php';
1160 }
1161
1162 /**
1163 * @param array<string,string> $columns
1164 * @return array<string,string>
1165 */
1166 public static function get_columns( $columns ) {
1167 $columns['cb'] = '<input type="checkbox" />';
1168 $columns['name'] = esc_html__( 'Form Title', 'formidable' );
1169 $columns['entries'] = esc_html__( 'Entries', 'formidable' );
1170 $columns['id'] = 'ID';
1171 $columns['form_key'] = esc_html__( 'Key', 'formidable' );
1172 if ( 'trash' !== FrmAppHelper::simple_get( 'form_type' ) ) {
1173 $columns['shortcode'] = esc_html__( 'Actions', 'formidable' );
1174 }
1175 $columns['created_at'] = esc_html__( 'Date', 'formidable' );
1176
1177 add_screen_option(
1178 'per_page',
1179 array(
1180 'label' => __( 'Forms', 'formidable' ),
1181 'default' => 20,
1182 'option' => 'formidable_page_formidable_per_page',
1183 )
1184 );
1185
1186 return $columns;
1187 }
1188
1189 /**
1190 * @return array<string,string>
1191 */
1192 public static function get_sortable_columns() {
1193 return array(
1194 'id' => 'id',
1195 'name' => 'name',
1196 'description' => 'description',
1197 'form_key' => 'form_key',
1198 'created_at' => 'created_at',
1199 );
1200 }
1201
1202 /**
1203 * @param mixed $hidden_columns
1204 * @return array
1205 */
1206 public static function hidden_columns( $hidden_columns ) {
1207 if ( ! is_array( $hidden_columns ) ) {
1208 $hidden_columns = array();
1209 }
1210
1211 $type = FrmAppHelper::get_simple_request(
1212 array(
1213 'param' => 'form_type',
1214 'type' => 'request',
1215 )
1216 );
1217
1218 if ( $type === 'template' ) {
1219 $hidden_columns[] = 'id';
1220 $hidden_columns[] = 'form_key';
1221 }
1222
1223 return $hidden_columns;
1224 }
1225
1226 public static function save_per_page( $save, $option, $value ) {
1227 if ( $option === 'formidable_page_formidable_per_page' ) {
1228 $save = (int) $value;
1229 }
1230
1231 return $save;
1232 }
1233
1234 /**
1235 * @param int|string $id
1236 * @param array $errors
1237 * @param string $message
1238 * @param bool $create_link
1239 * @return void
1240 */
1241 private static function get_edit_vars( $id, $errors = array(), $message = '', $create_link = false ) {
1242 global $frm_vars;
1243
1244 $form = FrmForm::getOne( $id );
1245 $error_args = array(
1246 'title' => __( 'You can\'t edit the form', 'formidable' ),
1247 'body' => __( 'You are trying to edit a form that does not exist', 'formidable' ),
1248 'cancel_url' => admin_url( 'admin.php?page=formidable' ),
1249 'continue_url' => add_query_arg(
1250 array(
1251 'page' => 'formidable',
1252 )
1253 ),
1254 );
1255 if ( ! $form ) {
1256 FrmAppController::show_error_modal( $error_args );
1257 return;
1258 }
1259
1260 if ( 'trash' === $form->status ) {
1261 $error_args['body'] = __( 'The form you\'re trying to edit is in trash. You must restore it first before you can make changes', 'formidable' );
1262 $error_args['continue_url'] = add_query_arg(
1263 array(
1264 'page' => 'formidable',
1265 '_wpnonce' => wp_create_nonce( 'untrash_form_' . $id ),
1266 'form_type' => 'trash',
1267 'frm_action' => 'untrash',
1268 'id' => $id,
1269 )
1270 );
1271 $error_args['continue_text'] = __( 'Restore form', 'formidable' );
1272 FrmAppController::show_error_modal( $error_args );
1273 return;
1274 }
1275
1276 if ( $form->parent_form_id ) {
1277 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
1278 wp_die( sprintf( esc_html__( 'You are trying to edit a child form. Please edit from %1$shere%2$s', 'formidable' ), '<a href="' . esc_url( FrmForm::get_edit_link( $form->parent_form_id ) ) . '">', '</a>' ) );
1279 }
1280
1281 $frm_field_selection = FrmField::field_selection();
1282
1283 $fields = FrmField::get_all_for_form( $form->id );
1284
1285 // Automatically add end section fields if they don't exist (2.0 migration).
1286 $reset_fields = false;
1287 FrmFormsHelper::auto_add_end_section_fields( $form, $fields, $reset_fields );
1288 FrmSubmitHelper::maybe_create_submit_field( $form, $fields, $reset_fields );
1289
1290 if ( $reset_fields ) {
1291 $fields = FrmField::get_all_for_form( $form->id, '', 'exclude' );
1292 }
1293
1294 unset( $reset_fields );
1295
1296 $args = array( 'parent_form_id' => $form->id );
1297 $values = FrmAppHelper::setup_edit_vars( $form, 'forms', '', true, array(), $args );
1298
1299 /**
1300 * Allows modifying the list of fields in the form builder.
1301 *
1302 * @since 5.0.04
1303 *
1304 * @param object[] $fields Array of fields.
1305 * @param array $args The arguments. Contains `form`.
1306 */
1307 $values['fields'] = apply_filters( 'frm_fields_in_form_builder', $fields, compact( 'form' ) );
1308
1309 $edit_message = __( 'Form was successfully updated.', 'formidable' );
1310 if ( $form->is_template && $message == $edit_message ) {
1311 $message = __( 'Template was successfully updated.', 'formidable' );
1312 }
1313
1314 self::maybe_update_form_builder_message( $message );
1315
1316 $has_fields = ! empty( $values['fields'] ) && ! FrmSubmitHelper::only_contains_submit_field( $values['fields'] );
1317
1318 if ( defined( 'DOING_AJAX' ) ) {
1319 wp_die();
1320 }
1321
1322 require FrmAppHelper::plugin_path() . '/classes/views/frm-forms/edit.php';
1323 }
1324
1325 /**
1326 * @param array $fields
1327 * @return array
1328 */
1329 public static function update_form_builder_fields( $fields, $form ) {
1330 foreach ( $fields as $field ) {
1331 $field->do_not_include_icons = true;
1332 }
1333 return $fields;
1334 }
1335
1336 public static function maybe_update_form_builder_message( &$message ) {
1337 if ( 'form_duplicated' === FrmAppHelper::simple_get( 'message' ) ) {
1338 $message = __( 'Form was Successfully Copied', 'formidable' );
1339 }
1340 }
1341
1342 /**
1343 * @param int|string $id
1344 * @param array $errors
1345 * @param array|string $args
1346 * @return void
1347 */
1348 public static function get_settings_vars( $id, $errors = array(), $args = array() ) {
1349 FrmAppHelper::permission_check( 'frm_edit_forms' );
1350
1351 global $frm_vars;
1352
1353 self::maybe_print_media_templates();
1354
1355 if ( ! is_array( $args ) ) {
1356 // For reverse compatibility.
1357 $args = array(
1358 'message' => $args,
1359 );
1360 }
1361
1362 $defaults = array(
1363 'message' => '',
1364 'warnings' => array(),
1365 );
1366 $args = array_merge( $defaults, $args );
1367 $message = $args['message'];
1368 $warnings = $args['warnings'];
1369
1370 $form = FrmForm::getOne( $id );
1371 $fields = FrmField::get_all_for_form( $id );
1372 $values = FrmAppHelper::setup_edit_vars( $form, 'forms', $fields, true );
1373
1374 /**
1375 * Allows changing fields in the form settings.
1376 *
1377 * @since 5.0.04
1378 *
1379 * @param array $fields Array of fields.
1380 * @param array $args The arguments. Contains `form`.
1381 */
1382 $values['fields'] = apply_filters( 'frm_fields_in_settings', $values['fields'], compact( 'form' ) );
1383
1384 self::clean_submit_html( $values );
1385
1386 $sections = self::get_settings_tabs( $values );
1387 $current = FrmAppHelper::simple_get( 't', 'sanitize_title', 'advanced_settings' );
1388
1389 require FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings.php';
1390 }
1391
1392 /**
1393 * Print WordPress media templates email actions does not trigger a "Uncaught Error: Template not found: #tmpl-media-selection" error when the media button is clicked.
1394 *
1395 * @since 6.10
1396 *
1397 * @return void
1398 */
1399 private static function maybe_print_media_templates() {
1400 if ( FrmAppHelper::pro_is_included() ) {
1401 // This issue does not exist when Pro is active so exit early.
1402 return;
1403 }
1404
1405 add_action(
1406 'wp_enqueue_editor',
1407 function () {
1408 wp_print_media_templates();
1409 }
1410 );
1411 }
1412
1413 /**
1414 * @since 4.0
1415 */
1416 public static function form_publish_button( $atts ) {
1417 $values = $atts['values'];
1418 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/_publish_box.php';
1419 }
1420
1421 /**
1422 * Get a list of all the settings tabs for the form settings page.
1423 *
1424 * @since 4.0
1425 *
1426 * @param array $values
1427 * @return array
1428 */
1429 private static function get_settings_tabs( $values ) {
1430 $sections = array(
1431 'advanced' => array(
1432 'name' => __( 'General', 'formidable' ),
1433 'title' => __( 'General Form Settings', 'formidable' ),
1434 'function' => array( self::class, 'advanced_settings' ),
1435 'icon' => 'frm_icon_font frm_settings_icon',
1436 ),
1437 'email' => array(
1438 'name' => __( 'Actions & Notifications', 'formidable' ),
1439 'function' => array( 'FrmFormActionsController', 'email_settings' ),
1440 'id' => 'frm_notification_settings',
1441 'icon' => 'frm_icon_font frm_mail_bulk_icon',
1442 ),
1443 'permissions' => array(
1444 'name' => __( 'Form Permissions', 'formidable' ),
1445 'icon' => 'frm_icon_font frm_lock_closed_icon',
1446 'html_class' => 'frm_show_upgrade_tab frm_noallow',
1447 'data' => array(
1448 'medium' => 'permissions',
1449 'upgrade' => __( 'Form Permissions', 'formidable' ),
1450 'message' => __( 'Allow editing, protect forms and files, limit entries, and save drafts. Upgrade to get form and entry permissions.', 'formidable' ),
1451 'screenshot' => 'permissions.png',
1452 ),
1453 ),
1454 'scheduling' => array(
1455 'name' => __( 'Form Scheduling', 'formidable' ),
1456 'icon' => 'frm_icon_font frm_calendar_icon',
1457 'html_class' => 'frm_show_upgrade_tab frm_noallow',
1458 'data' => array(
1459 'medium' => 'scheduling',
1460 'upgrade' => __( 'Form scheduling settings', 'formidable' ),
1461 'screenshot' => 'scheduling.png',
1462 ),
1463 ),
1464 'buttons' => array(
1465 'name' => __( 'Buttons', 'formidable' ),
1466 'class' => self::class,
1467 'function' => 'buttons_settings',
1468 'icon' => 'frm_icon_font frm_button_icon',
1469 ),
1470 'landing' => array(
1471 'name' => __( 'Form Landing Page', 'formidable' ),
1472 'icon' => 'frm_icon_font frm_file_text_icon',
1473 'html_class' => 'frm_show_upgrade_tab frm_noallow',
1474 'data' => FrmAppHelper::get_landing_page_upgrade_data_params(),
1475 ),
1476 'chat' => array(
1477 'name' => __( 'Conversational Forms', 'formidable' ),
1478 'icon' => 'frm_icon_font frm_chat_forms_icon',
1479 'html_class' => 'frm_show_upgrade_tab frm_noallow',
1480 'data' => FrmAppHelper::get_upgrade_data_params(
1481 'chat',
1482 array(
1483 'upgrade' => __( 'Conversational Forms', 'formidable' ),
1484 'message' => __( 'Ask one question at a time for automated conversations.', 'formidable' ),
1485 'screenshot' => 'chat.png',
1486 )
1487 ),
1488 ),
1489 'abandonment' => array(
1490 'name' => __( 'Form Abandonment', 'formidable' ),
1491 'icon' => 'frm_icon_font frm_abandoned_icon',
1492 'html_class' => 'frm_show_upgrade_tab frm_noallow',
1493 'data' => FrmAppHelper::get_upgrade_data_params(
1494 'abandonment',
1495 array(
1496 'upgrade' => __( 'Form abandonment settings', 'formidable' ),
1497 'message' => __( 'Unlock the power of data capture to boost lead generation and master the art of form optimization.', 'formidable' ),
1498 'screenshot' => 'abandonment.png',
1499 )
1500 ),
1501 ),
1502 'html' => array(
1503 'name' => __( 'Customize HTML', 'formidable' ),
1504 'class' => self::class,
1505 'function' => 'html_settings',
1506 'icon' => 'frm_icon_font frm_code_icon',
1507 ),
1508 );
1509
1510 if ( ! has_action( 'frm_add_form_style_tab_options' ) && ! has_action( 'frm_add_form_button_options' ) ) {
1511 unset( $sections['buttons'] );
1512 }
1513
1514 foreach ( array( 'landing', 'chat', 'abandonment' ) as $feature ) {
1515 if ( ! FrmAppHelper::show_new_feature( $feature ) ) {
1516 unset( $sections[ $feature ] );
1517 }
1518 }
1519
1520 $sections = apply_filters( 'frm_add_form_settings_section', $sections, $values );
1521
1522 foreach ( $sections as $key => $section ) {
1523 $defaults = array(
1524 'html_class' => '',
1525 'name' => ucfirst( $key ),
1526 'icon' => 'frm_icon_font frm_settings_icon',
1527 );
1528
1529 $section = array_merge( $defaults, $section );
1530
1531 if ( ! isset( $section['anchor'] ) ) {
1532 $section['anchor'] = $key;
1533 }
1534 $section['anchor'] .= '_settings';
1535
1536 if ( ! isset( $section['title'] ) ) {
1537 $section['title'] = $section['name'];
1538 }
1539
1540 if ( ! isset( $section['id'] ) ) {
1541 $section['id'] = $section['anchor'];
1542 }
1543
1544 $sections[ $key ] = $section;
1545 }//end foreach
1546
1547 return $sections;
1548 }
1549
1550 /**
1551 * @since 4.0
1552 *
1553 * @param array $values
1554 * @return void
1555 */
1556 public static function advanced_settings( $values ) {
1557 $first_h3 = 'frm_first_h3';
1558
1559 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-advanced.php';
1560 }
1561
1562 /**
1563 * @param array $values
1564 * @return void
1565 */
1566 public static function render_spam_settings( $values ) {
1567 if ( function_exists( 'akismet_http_post' ) ) {
1568 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/akismet.php';
1569 }
1570 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/stopforumspam.php';
1571 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/antispam.php';
1572 }
1573
1574 /**
1575 * @since 4.0
1576 *
1577 * @param array $values
1578 * @return void
1579 */
1580 public static function buttons_settings( $values ) {
1581 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-buttons.php';
1582 }
1583
1584 /**
1585 * @since 4.0
1586 *
1587 * @param array $values
1588 * @return void
1589 */
1590 public static function html_settings( $values ) {
1591 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-html.php';
1592 }
1593
1594 /**
1595 * Replace old Submit Button href with new href to avoid errors in Chrome
1596 *
1597 * @since 2.03.08
1598 *
1599 * @param array|bool $values
1600 * @return void
1601 */
1602 private static function clean_submit_html( &$values ) {
1603 if ( is_array( $values ) && isset( $values['submit_html'] ) ) {
1604 $values['submit_html'] = str_replace( 'javascript:void(0)', '#', $values['submit_html'] );
1605 }
1606 }
1607
1608 /**
1609 * Updates classes used in submit and prev buttons to avoid conflict with twenty twenty-one theme.
1610 *
1611 * @param array $classes
1612 *
1613 * @return array
1614 */
1615 public static function update_button_classes( $classes ) {
1616 if ( function_exists( 'twenty_twenty_one_setup' ) ) {
1617 $classes[] = 'has-text-color has-background';
1618 }
1619
1620 return $classes;
1621 }
1622
1623 /**
1624 * @param int|string $form_id
1625 * @param string $class
1626 * @return void
1627 */
1628 public static function mb_tags_box( $form_id, $class = '' ) {
1629 $fields = FrmField::get_all_for_form( $form_id, '', 'include' );
1630
1631 /**
1632 * Allows modifying the list of fields in the tags box.
1633 *
1634 * @since 5.0.04
1635 *
1636 * @param array $fields The list of fields.
1637 * @param array $args The arguments. Contains `form_id`.
1638 */
1639 $fields = apply_filters( 'frm_fields_in_tags_box', $fields, compact( 'form_id' ) );
1640 $linked_forms = array();
1641 $col = 'one';
1642 $settings_tab = FrmAppHelper::is_admin_page( 'formidable' );
1643
1644 $cond_shortcodes = apply_filters( 'frm_conditional_shortcodes', array() );
1645 $entry_shortcodes = self::get_shortcode_helpers( $settings_tab );
1646
1647 $advanced_helpers = self::advanced_helpers( compact( 'fields', 'form_id' ) );
1648
1649 include FrmAppHelper::plugin_path() . '/classes/views/shared/mb_adv_info.php';
1650 }
1651
1652 /**
1653 * @since 3.04.01
1654 */
1655 private static function advanced_helpers( $atts ) {
1656 $advanced_helpers = array(
1657 'default' => array(
1658 'heading' => __( 'Customize field values with the following parameters.', 'formidable' ),
1659 'codes' => self::get_advanced_shortcodes(),
1660 ),
1661 );
1662
1663 $user_fields = self::user_shortcodes();
1664 if ( $user_fields ) {
1665 $user_helpers = array();
1666 foreach ( $user_fields as $uk => $uf ) {
1667 $user_helpers[ '|user_id| show="' . $uk . '"' ] = $uf;
1668 unset( $uk, $uf );
1669 }
1670
1671 $advanced_helpers['user_id'] = array(
1672 'codes' => $user_helpers,
1673 );
1674 }
1675
1676 /**
1677 * Add extra helper shortcodes on the Advanced tab in form settings and views
1678 *
1679 * @since 3.04.01
1680 *
1681 * @param array $advanced_helpers
1682 * @param array $atts - Includes fields and form_id
1683 */
1684 return apply_filters( 'frm_advanced_helpers', $advanced_helpers, $atts );
1685 }
1686
1687 /**
1688 * Get an array of the options to display in the advanced tab
1689 * of the customization panel
1690 *
1691 * @since 2.0.6
1692 */
1693 private static function get_advanced_shortcodes() {
1694 $adv_shortcodes = array(
1695 'x sep=", "' => array(
1696 'label' => __( 'Separator', 'formidable' ),
1697 'title' => __( 'Use a different separator for checkbox fields', 'formidable' ),
1698 ),
1699 'x format="d-m-Y"' => array(
1700 'label' => __( 'Date Format', 'formidable' ),
1701 ),
1702 'x show="field_label"' => array(
1703 'label' => __( 'Field Label', 'formidable' ),
1704 ),
1705 'x wpautop=0' => array(
1706 'label' => __( 'No Auto P', 'formidable' ),
1707 'title' => __( 'Do not automatically add any paragraphs or line breaks', 'formidable' ),
1708 ),
1709 );
1710 $adv_shortcodes = apply_filters( 'frm_advanced_shortcodes', $adv_shortcodes );
1711
1712 // __( 'Leave blank instead of defaulting to User Login', 'formidable' ) : blank=1
1713
1714 return $adv_shortcodes;
1715 }
1716
1717 /**
1718 * @since 3.04.01
1719 */
1720 private static function user_shortcodes() {
1721 $options = array(
1722 'ID' => __( 'User ID', 'formidable' ),
1723 'first_name' => __( 'First Name', 'formidable' ),
1724 'last_name' => __( 'Last Name', 'formidable' ),
1725 'display_name' => __( 'Display Name', 'formidable' ),
1726 'user_login' => __( 'User Login', 'formidable' ),
1727 'user_email' => __( 'Email', 'formidable' ),
1728 'avatar' => __( 'Avatar', 'formidable' ),
1729 'author_link' => __( 'Author Link', 'formidable' ),
1730 );
1731
1732 return apply_filters( 'frm_user_shortcodes', $options );
1733 }
1734
1735 /**
1736 * Get an array of the helper shortcodes to display in the customization panel
1737 *
1738 * @since 2.0.6
1739 */
1740 private static function get_shortcode_helpers( $settings_tab ) {
1741 $entry_shortcodes = array(
1742 'id' => __( 'Entry ID', 'formidable' ),
1743 'key' => __( 'Entry Key', 'formidable' ),
1744 'post_id' => __( 'Post ID', 'formidable' ),
1745 'ip' => __( 'User IP', 'formidable' ),
1746 'created-at' => __( 'Entry created', 'formidable' ),
1747 'updated-at' => __( 'Entry updated', 'formidable' ),
1748 '' => '',
1749 'siteurl' => __( 'Site URL', 'formidable' ),
1750 'sitename' => __( 'Site Name', 'formidable' ),
1751 'form_name' => __( 'Form Name', 'formidable' ),
1752 );
1753
1754 $entry_shortcodes = array_merge( FrmShortcodeHelper::get_contextual_shortcode_values(), $entry_shortcodes );
1755 if ( ! FrmAppHelper::pro_is_installed() ) {
1756 unset( $entry_shortcodes['post_id'] );
1757 }
1758
1759 /**
1760 * Use this hook to add or remove buttons in the helpers section
1761 * in the customization panel
1762 *
1763 * @since 2.0.6
1764 */
1765 $entry_shortcodes = apply_filters( 'frm_helper_shortcodes', $entry_shortcodes, $settings_tab );
1766
1767 return $entry_shortcodes;
1768 }
1769
1770 /**
1771 * Insert the form class setting into the form.
1772 *
1773 * @param stdClass $form
1774 * @return void
1775 */
1776 public static function form_classes( $form ) {
1777 if ( isset( $form->options['form_class'] ) ) {
1778 echo esc_attr( sanitize_text_field( $form->options['form_class'] ) );
1779 }
1780
1781 if ( ! empty( $form->options['js_validate'] ) ) {
1782 echo ' frm_js_validate ';
1783 self::add_js_validate_form_to_global_vars( $form );
1784 }
1785
1786 if ( FrmForm::is_ajax_on( $form ) ) {
1787 echo ' frm_ajax_submit ';
1788 }
1789 }
1790
1791 /**
1792 * @since 5.0.03
1793 *
1794 * @param stdClass $form
1795 */
1796 public static function add_js_validate_form_to_global_vars( $form ) {
1797 global $frm_vars;
1798 if ( ! isset( $frm_vars['js_validate_forms'] ) ) {
1799 $frm_vars['js_validate_forms'] = array();
1800 }
1801 $frm_vars['js_validate_forms'][ $form->id ] = $form;
1802 }
1803
1804 public static function get_email_html() {
1805 FrmAppHelper::permission_check( 'frm_view_forms' );
1806 check_ajax_referer( 'frm_ajax', 'nonce' );
1807
1808 echo FrmEntriesController::show_entry_shortcode( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1809 array(
1810 'form_id' => FrmAppHelper::get_post_param( 'form_id', '', 'absint' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1811 'default_email' => true,
1812 'plain_text' => FrmAppHelper::get_post_param( 'plain_text', '', 'absint' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1813 )
1814 );
1815 wp_die();
1816 }
1817
1818 /**
1819 * @param string $content
1820 * @param int|stdClass|string $form
1821 * @param false|int|stdClass|string $entry
1822 * @return string
1823 */
1824 public static function filter_content( $content, $form, $entry = false ) {
1825 $content = self::replace_form_name_shortcodes( $content, $form );
1826
1827 self::get_entry_by_param( $entry );
1828 if ( ! $entry ) {
1829 return $content;
1830 }
1831
1832 if ( is_object( $form ) ) {
1833 $form = $form->id;
1834 }
1835
1836 /*
1837 * Repeater actions adds `parent_entry` to `$entry` store the parent entry data. If `parent_entry` is not empty,
1838 * use the parent form ID instead of repeater form ID to fix the parent form field shortcodes doesn't work.
1839 */
1840 if ( ! empty( $entry->parent_entry ) ) {
1841 $form = $entry->parent_entry->form_id;
1842 }
1843
1844 $shortcodes = FrmFieldsHelper::get_shortcodes( $content, $form );
1845 $content = apply_filters( 'frm_replace_content_shortcodes', $content, $entry, $shortcodes );
1846
1847 return $content;
1848 }
1849
1850 /**
1851 * Replace any [form_name] shortcodes in a string.
1852 *
1853 * @since 5.5
1854 *
1855 * @param array|string $string
1856 * @param int|stdClass|string $form
1857 * @return array|string
1858 */
1859 public static function replace_form_name_shortcodes( $string, $form ) {
1860 if ( ! is_string( $string ) ) {
1861 return $string;
1862 }
1863
1864 if ( false === strpos( $string, '[form_name]' ) ) {
1865 return $string;
1866 }
1867
1868 if ( ! is_object( $form ) ) {
1869 $form = FrmForm::getOne( $form );
1870 }
1871
1872 $form_name = is_object( $form ) ? $form->name : '';
1873 return str_replace( '[form_name]', $form_name, $string );
1874 }
1875
1876 /**
1877 * @param false|int|stdClass|string $entry
1878 * @return void
1879 */
1880 private static function get_entry_by_param( &$entry ) {
1881 if ( ! $entry || ! is_object( $entry ) ) {
1882 if ( ! $entry || ! is_numeric( $entry ) ) {
1883 $entry = FrmAppHelper::get_post_param( 'id', false, 'sanitize_title' );
1884 }
1885
1886 FrmEntry::maybe_get_entry( $entry );
1887 }
1888 }
1889
1890 public static function replace_content_shortcodes( $content, $entry, $shortcodes ) {
1891 return FrmFieldsHelper::replace_content_shortcodes( $content, $entry, $shortcodes );
1892 }
1893
1894 /**
1895 * @param array $errors
1896 * @return array
1897 */
1898 public static function process_bulk_form_actions( $errors ) {
1899 if ( ! $_REQUEST ) {
1900 return $errors;
1901 }
1902
1903 $bulkaction = FrmAppHelper::get_param( 'action', '', 'get', 'sanitize_text_field' );
1904 if ( $bulkaction == - 1 ) {
1905 $bulkaction = FrmAppHelper::get_param( 'action2', '', 'get', 'sanitize_title' );
1906 }
1907
1908 if ( ! empty( $bulkaction ) && strpos( $bulkaction, 'bulk_' ) === 0 ) {
1909 FrmAppHelper::remove_get_action();
1910
1911 $bulkaction = str_replace( 'bulk_', '', $bulkaction );
1912 }
1913
1914 $ids = FrmAppHelper::get_param( 'item-action', '', 'get', 'sanitize_text_field' );
1915 if ( empty( $ids ) ) {
1916 $errors[] = __( 'No forms were specified', 'formidable' );
1917
1918 return $errors;
1919 }
1920
1921 $permission_error = FrmAppHelper::permission_nonce_error( '', '_wpnonce', 'bulk-toplevel_page_formidable' );
1922 if ( $permission_error !== false ) {
1923 $errors[] = $permission_error;
1924
1925 return $errors;
1926 }
1927
1928 if ( ! is_array( $ids ) ) {
1929 $ids = explode( ',', $ids );
1930 }
1931
1932 switch ( $bulkaction ) {
1933 case 'delete':
1934 $message = self::bulk_destroy( $ids );
1935 break;
1936 case 'trash':
1937 $message = self::bulk_trash( $ids );
1938 break;
1939 case 'untrash':
1940 $message = self::bulk_untrash( $ids );
1941 }
1942
1943 if ( ! empty( $message ) ) {
1944 $errors['message'] = $message;
1945 }
1946
1947 return $errors;
1948 }
1949
1950 /**
1951 * Includes html that shows a message when the device is too small to use Formidable Forms admin pages.
1952 *
1953 * @since 6.20
1954 * @return void
1955 */
1956 public static function include_device_too_small_message() {
1957 if ( ! FrmAppHelper::is_formidable_admin() ) {
1958 return;
1959 }
1960
1961 include FrmAppHelper::plugin_path() . '/classes/views/shared/small-device-message.php';
1962 }
1963
1964 public static function route() {
1965 $action = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action';
1966 $vars = array();
1967 FrmAppHelper::include_svg();
1968 if ( isset( $_POST['frm_compact_fields'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1969 FrmAppHelper::permission_check( 'frm_edit_forms' );
1970
1971 // Javascript needs to be allowed in some field settings.
1972 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
1973 $json_vars = htmlspecialchars_decode( nl2br( str_replace( '&quot;', '"', wp_unslash( $_POST['frm_compact_fields'] ) ) ) );
1974 $json_vars = json_decode( $json_vars, true );
1975 if ( empty( $json_vars ) ) {
1976 // json decoding failed so we should return an error message.
1977 $action = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
1978 if ( 'edit' === $action ) {
1979 $action = 'update';
1980 }
1981
1982 add_filter( 'frm_validate_form', 'FrmFormsController::json_error' );
1983 } else {
1984 $vars = FrmAppHelper::json_to_array( $json_vars );
1985 $action = $vars[ $action ];
1986 unset( $_REQUEST['frm_compact_fields'], $_POST['frm_compact_fields'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1987 $_REQUEST = array_merge( $_REQUEST, $vars ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1988 $_POST = array_merge( $_POST, $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1989 }
1990 } else {
1991 $action = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
1992 if ( isset( $_REQUEST['delete_all'] ) ) {
1993 // Override the action for this page.
1994 $action = 'delete_all';
1995 }
1996 }//end if
1997
1998 add_action( 'frm_load_form_hooks', 'FrmHooksController::trigger_load_form_hooks' );
1999 FrmAppHelper::trigger_hook_load( 'form' );
2000
2001 switch ( $action ) {
2002 case 'create':
2003 case 'edit':
2004 case 'update':
2005 case 'trash':
2006 case 'untrash':
2007 case 'destroy':
2008 case 'settings':
2009 case 'update_settings':
2010 return self::$action( $vars );
2011 case 'lite-reports':
2012 self::no_reports( $vars );
2013 return;
2014 case 'views':
2015 self::no_views( $vars );
2016 return;
2017 default:
2018 do_action( 'frm_form_action_' . $action );
2019 if ( apply_filters( 'frm_form_stop_action_' . $action, false ) ) {
2020 return;
2021 }
2022
2023 $action = FrmAppHelper::get_param( 'action', '', 'get', 'sanitize_text_field' );
2024 if ( $action == - 1 ) {
2025 $action = FrmAppHelper::get_param( 'action2', '', 'get', 'sanitize_title' );
2026 }
2027
2028 if ( strpos( $action, 'bulk_' ) === 0 ) {
2029 FrmAppHelper::remove_get_action();
2030
2031 self::list_form();
2032 return;
2033 }
2034
2035 $message = FrmAppHelper::get_param( 'message' );
2036 if ( 'form_duplicate_error' === $message ) {
2037 self::display_forms_list( array(), '', array( __( 'There was a problem duplicating the form', 'formidable' ) ) );
2038 return;
2039 }
2040
2041 if ( 'forms_permanently_deleted' === $message ) {
2042 $count = FrmAppHelper::get_param( 'forms_deleted', 0, 'get', 'absint' );
2043 /* translators: %1$s: Number of forms */
2044 $message = sprintf( _n( '%1$s form permanently deleted.', '%1$s forms permanently deleted.', $count, 'formidable' ), $count );
2045 self::display_forms_list( array(), $message, '' );
2046 return;
2047 }
2048
2049 self::display_forms_list();
2050
2051 return;
2052 }//end switch
2053 }
2054
2055 /**
2056 * Rename a form.
2057 *
2058 * Handles the AJAX request for renaming a form.
2059 *
2060 * @since 6.7
2061 *
2062 * @return void
2063 */
2064 public static function rename_form() {
2065 // Check permission and nonce
2066 FrmAppHelper::permission_check( 'frm_edit_forms' );
2067 check_ajax_referer( 'frm_ajax', 'nonce' );
2068
2069 // Get posted data
2070 $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' );
2071 $name = FrmAppHelper::get_post_param( 'form_name', '', 'sanitize_text_field' );
2072
2073 $form = FrmForm::getOne( $form_id );
2074 if ( ! $form ) {
2075 wp_send_json_error( __( 'Form not found', 'formidable' ) );
2076 }
2077
2078 if ( $name === $form->name ) {
2079 // Nothing to change so exit early.
2080 wp_send_json_success();
2081 }
2082
2083 $to_update = array(
2084 'name' => $name,
2085 );
2086
2087 if ( '' !== $name ) {
2088 // Only update form_key if name is not empty.
2089 $form_key = FrmAppHelper::get_unique_key( sanitize_title( $name ), 'frm_forms', 'form_key' );
2090 $to_update['form_key'] = $form_key;
2091 } else {
2092 $form_key = $form->form_key;
2093 }
2094
2095 FrmForm::update( $form_id, $to_update );
2096
2097 wp_send_json_success( compact( 'form_key' ) );
2098 }
2099
2100 public static function json_error( $errors ) {
2101 $errors['json'] = __( 'Abnormal HTML characters prevented your form from saving correctly', 'formidable' );
2102
2103 return $errors;
2104 }
2105
2106 /**
2107 * Add education about views.
2108 *
2109 * @since 4.07
2110 *
2111 * @return void
2112 */
2113 public static function no_views( $values = array() ) {
2114 FrmAppHelper::include_svg();
2115 $id = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
2116 $form = $id ? FrmForm::getOne( $id ) : false;
2117
2118 include FrmAppHelper::plugin_path() . '/classes/views/shared/views-info.php';
2119 }
2120
2121 /**
2122 * Add education about reports.
2123 *
2124 * @since 4.07
2125 *
2126 * @return void
2127 */
2128 public static function no_reports( $values = array() ) {
2129 $id = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
2130 $form = $id ? FrmForm::getOne( $id ) : false;
2131
2132 include FrmAppHelper::plugin_path() . '/classes/views/shared/reports-info.php';
2133 }
2134
2135 /**
2136 * FRONT-END FORMS.
2137 */
2138 public static function admin_bar_css() {
2139 if ( is_admin() || ! current_user_can( 'frm_edit_forms' ) ) {
2140 return;
2141 }
2142
2143 self::move_menu_to_footer();
2144
2145 add_action( 'wp_before_admin_bar_render', 'FrmFormsController::admin_bar_configure' );
2146 FrmAppHelper::load_font_style();
2147 }
2148
2149 /**
2150 * @since 4.05.02
2151 */
2152 private static function move_menu_to_footer() {
2153 $settings = FrmAppHelper::get_settings();
2154 if ( empty( $settings->admin_bar ) ) {
2155 remove_action( 'wp_body_open', 'wp_admin_bar_render', 0 );
2156 }
2157 }
2158
2159 public static function admin_bar_configure() {
2160 global $frm_vars;
2161 if ( empty( $frm_vars['forms_loaded'] ) ) {
2162 return;
2163 }
2164
2165 $actions = array();
2166 foreach ( $frm_vars['forms_loaded'] as $form ) {
2167 if ( is_object( $form ) ) {
2168 $actions[ $form->id ] = $form->name;
2169 }
2170 unset( $form );
2171 }
2172
2173 if ( empty( $actions ) ) {
2174 return;
2175 }
2176
2177 self::add_menu_to_admin_bar();
2178 self::add_forms_to_admin_bar( $actions );
2179 }
2180
2181 /**
2182 * @since 2.05.07
2183 */
2184 public static function add_menu_to_admin_bar() {
2185 global $wp_admin_bar;
2186
2187 $wp_admin_bar->add_node(
2188 array(
2189 'id' => 'frm-forms',
2190 'title' => '<span class="ab-icon"></span><span class="ab-label">' . FrmAppHelper::get_menu_name() . '</span>',
2191 'href' => admin_url( 'admin.php?page=formidable' ),
2192 'meta' => array(
2193 'title' => FrmAppHelper::get_menu_name(),
2194 ),
2195 )
2196 );
2197 }
2198
2199 /**
2200 * @since 2.05.07
2201 */
2202 private static function add_forms_to_admin_bar( $actions ) {
2203 global $wp_admin_bar;
2204
2205 asort( $actions );
2206
2207 foreach ( $actions as $form_id => $name ) {
2208
2209 $wp_admin_bar->add_node(
2210 array(
2211 'parent' => 'frm-forms',
2212 'id' => 'edit_form_' . $form_id,
2213 'title' => empty( $name ) ? FrmFormsHelper::get_no_title_text() : $name,
2214 'href' => FrmForm::get_edit_link( $form_id ),
2215 )
2216 );
2217 }
2218 }
2219
2220 /**
2221 * The formidable shortcode
2222 *
2223 * @param array $atts The params from the shortcode.
2224 * @return string
2225 */
2226 public static function get_form_shortcode( $atts ) {
2227 global $frm_vars;
2228 if ( ! empty( $frm_vars['skip_shortcode'] ) ) {
2229 $sc = '[formidable';
2230 $sc .= FrmAppHelper::array_to_html_params( $atts );
2231 return $sc . ']';
2232 }
2233
2234 $shortcode_atts = shortcode_atts(
2235 array(
2236 'id' => '',
2237 'key' => '',
2238 'title' => 'auto',
2239 'description' => 'auto',
2240 'readonly' => false,
2241 'entry_id' => false,
2242 'fields' => array(),
2243 'exclude_fields' => array(),
2244 'minimize' => false,
2245 ),
2246 $atts
2247 );
2248 do_action( 'formidable_shortcode_atts', $shortcode_atts, $atts );
2249
2250 return self::show_form( $shortcode_atts['id'], $shortcode_atts['key'], $shortcode_atts['title'], $shortcode_atts['description'], $atts );
2251 }
2252
2253 /**
2254 * @since 5.2.01
2255 *
2256 * @param false|int|string $id
2257 * @param false|string $key
2258 * @return false|stdClass
2259 */
2260 private static function maybe_get_form_by_id_or_key( $id, $key ) {
2261 if ( ! $id ) {
2262 $id = $key;
2263 }
2264 return self::maybe_get_form_to_show( $id );
2265 }
2266
2267 /**
2268 * @param false|int|string $id
2269 * @param false|string $key
2270 * @param bool|int|string $title may be 'auto', true, false, 'true', 'false', 'yes', '1', 1, '0', 0.
2271 * @param bool|int|string $description may be 'auto', true, false, 'true', 'false', 'yes', '1', 1, '0', 0.
2272 * @param array $atts
2273 * @return string
2274 */
2275 public static function show_form( $id = '', $key = '', $title = false, $description = false, $atts = array() ) {
2276 $form = self::maybe_get_form_by_id_or_key( $id, $key );
2277
2278 if ( ! $form ) {
2279 return __( 'Please select a valid form', 'formidable' );
2280 }
2281
2282 if ( 'auto' === $title ) {
2283 $title = ! empty( $form->options['show_title'] );
2284 }
2285
2286 if ( 'auto' === $description ) {
2287 $description = ! empty( $form->options['show_description'] );
2288 }
2289
2290 FrmAppController::maybe_update_styles();
2291
2292 add_action( 'frm_load_form_hooks', 'FrmHooksController::trigger_load_form_hooks' );
2293 FrmAppHelper::trigger_hook_load( 'form', $form );
2294
2295 $form = apply_filters( 'frm_pre_display_form', $form );
2296
2297 $frm_settings = FrmAppHelper::get_settings( array( 'current_form' => $form->id ) );
2298
2299 if ( self::is_viewable_draft_form( $form ) ) {
2300 // don't show a draft form on a page
2301 $form = __( 'Please select a valid form', 'formidable' );
2302 } elseif ( ! FrmForm::is_visible_to_user( $form ) ) {
2303 $form = do_shortcode( $frm_settings->login_msg );
2304 } else {
2305 do_action( 'frm_pre_get_form', $form );
2306 $form = self::get_form( $form, $title, $description, $atts );
2307
2308 /**
2309 * Use this shortcode to check for external shortcodes that may span
2310 * across multiple fields in the customizable HTML
2311 *
2312 * @since 2.0.8
2313 */
2314 $form = apply_filters( 'frm_filter_final_form', $form );
2315 }
2316
2317 return $form;
2318 }
2319
2320 /**
2321 * @param false|int|string $id
2322 * @return false|stdClass
2323 */
2324 private static function maybe_get_form_to_show( $id ) {
2325 $form = false;
2326
2327 if ( ! empty( $id ) ) {
2328 // Form id or key is set.
2329 $form = FrmForm::getOne( $id );
2330 if ( ! $form || $form->parent_form_id || $form->status === 'trash' ) {
2331 $form = false;
2332 }
2333 }
2334
2335 return $form;
2336 }
2337
2338 private static function is_viewable_draft_form( $form ) {
2339 return $form->status === 'draft' && current_user_can( 'frm_edit_forms' ) && ! FrmAppHelper::is_preview_page();
2340 }
2341
2342 public static function get_form( $form, $title, $description, $atts = array() ) {
2343 ob_start();
2344
2345 do_action( 'frm_before_get_form', $atts );
2346
2347 self::get_form_contents( $form, $title, $description, $atts );
2348 self::enqueue_scripts( FrmForm::get_params( $form ) );
2349
2350 $contents = ob_get_contents();
2351 ob_end_clean();
2352
2353 self::maybe_minimize_form( $atts, $contents );
2354
2355 return $contents;
2356 }
2357
2358 public static function enqueue_scripts( $params ) {
2359 do_action( 'frm_enqueue_form_scripts', $params );
2360 }
2361
2362 public static function get_form_contents( $form, $title, $description, $atts ) {
2363 $params = FrmForm::get_params( $form );
2364 $errors = self::get_saved_errors( $form, $params );
2365 $fields = FrmFieldsHelper::get_form_fields( $form->id, $errors );
2366 $reset = false;
2367 $pass_args = compact( 'form', 'fields', 'errors', 'title', 'description', 'reset' );
2368
2369 $pass_args['action'] = $params['action'];
2370 $handle_process_here = $params['action'] === 'create' && $params['posted_form_id'] == $form->id && $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
2371
2372 if ( ! $handle_process_here ) {
2373 FrmFormState::set_initial_value( 'title', $title );
2374 FrmFormState::set_initial_value( 'description', $description );
2375
2376 do_action( 'frm_display_form_action', $params, $fields, $form, $title, $description );
2377 if ( apply_filters( 'frm_continue_to_new', true, $form->id, $params['action'] ) ) {
2378 self::show_form_after_submit( $pass_args );
2379 }
2380 } elseif ( ! empty( $errors ) ) {
2381 self::show_form_after_submit( $pass_args );
2382
2383 } else {
2384
2385 do_action( 'frm_validate_form_creation', $params, $fields, $form, $title, $description );
2386
2387 if ( apply_filters( 'frm_continue_to_create', true, $form->id ) ) {
2388 $entry_id = self::just_created_entry( $form->id );
2389 $pass_args['entry_id'] = $entry_id;
2390 $pass_args['reset'] = true;
2391
2392 self::run_on_submit_actions( $pass_args );
2393
2394 do_action(
2395 'frm_after_entry_processed',
2396 array(
2397 'entry_id' => $entry_id,
2398 'form' => $form,
2399 )
2400 );
2401 }
2402 }//end if
2403 }
2404
2405 /**
2406 * If the form was processed earlier (init), get the generated errors
2407 *
2408 * @since 2.05
2409 */
2410 private static function get_saved_errors( $form, $params ) {
2411 global $frm_vars;
2412
2413 if ( $params['posted_form_id'] == $form->id && $_POST && isset( $frm_vars['created_entries'][ $form->id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
2414 $errors = $frm_vars['created_entries'][ $form->id ]['errors'];
2415 } else {
2416 $errors = array();
2417 }
2418
2419 /**
2420 * Allows modifying the generated errors if the form was processed earlier.
2421 *
2422 * @since 5.2.03
2423 *
2424 * @param array $errors Errors data. Is empty array if no errors found.
2425 * @param array $params Form params. See {@see FrmForm::get_params()}.
2426 */
2427 return apply_filters( 'frm_saved_errors', $errors, $params );
2428 }
2429
2430 /**
2431 * @since 2.2.7
2432 */
2433 public static function just_created_entry( $form_id ) {
2434 global $frm_vars;
2435
2436 return isset( $frm_vars['created_entries'] ) && isset( $frm_vars['created_entries'][ $form_id ] ) && isset( $frm_vars['created_entries'][ $form_id ]['entry_id'] ) ? $frm_vars['created_entries'][ $form_id ]['entry_id'] : 0;
2437 }
2438
2439 /**
2440 * Gets confirmation method.
2441 *
2442 * @since 3.0
2443 * @since 6.0 This method can return an array of met On Submit actions.
2444 *
2445 * @param array $atts {
2446 * Atts.
2447 *
2448 * @type object $form Form object.
2449 * @type int $entry_id Entry ID.
2450 * }
2451 * @return array|string
2452 */
2453 private static function get_confirmation_method( $atts ) {
2454 $action = FrmOnSubmitHelper::current_event( $atts );
2455 $opt = 'update' === $action ? 'edit_action' : 'success_action';
2456 $method = ! empty( $atts['form']->options[ $opt ] ) ? $atts['form']->options[ $opt ] : 'message';
2457
2458 if ( ! empty( $atts['entry_id'] ) ) {
2459 $met_actions = self::get_met_on_submit_actions( $atts, $action );
2460 if ( $met_actions ) {
2461 $method = $met_actions;
2462 }
2463 }
2464
2465 $method = apply_filters( 'frm_success_filter', $method, $atts['form'], $action );
2466
2467 if ( $method !== 'message' && ( ! $atts['entry_id'] || ! is_numeric( $atts['entry_id'] ) ) ) {
2468 $method = 'message';
2469 }
2470
2471 return $method;
2472 }
2473
2474 public static function maybe_trigger_redirect( $form, $params, $args ) {
2475 if ( ! isset( $params['id'] ) ) {
2476 global $frm_vars;
2477 $params['id'] = $frm_vars['created_entries'][ $form->id ]['entry_id'];
2478 }
2479
2480 $conf_method = self::get_confirmation_method(
2481 array(
2482 'form' => $form,
2483 'entry_id' => $params['id'],
2484 'action' => FrmOnSubmitHelper::current_event( $params ),
2485 )
2486 );
2487
2488 self::maybe_trigger_redirect_with_action( $conf_method, $form, $params, $args );
2489 }
2490
2491 /**
2492 * Maybe trigger redirect with the new Confirmation action.
2493 *
2494 * @since 6.1.1
2495 *
2496 * @param array|string $conf_method Array of confirmation actions or the action type string.
2497 * @param object $form Form object.
2498 * @param array $params See {@see FrmFormsController::maybe_trigger_redirect()}.
2499 * @param array $args See {@see FrmFormsController::maybe_trigger_redirect()}.
2500 */
2501 public static function maybe_trigger_redirect_with_action( $conf_method, $form, $params, $args ) {
2502 if ( is_array( $conf_method ) && 1 === count( $conf_method ) ) {
2503 if ( 'redirect' === FrmOnSubmitHelper::get_action_type( $conf_method[0] ) && empty( $conf_method[0]->post_content['redirect_delay'] ) ) {
2504 $event = FrmOnSubmitHelper::current_event( $params );
2505 FrmOnSubmitHelper::populate_on_submit_data( $form->options, $conf_method[0], $event );
2506 $conf_method = 'redirect';
2507 }
2508 }
2509
2510 if ( 'redirect' === $conf_method ) {
2511 self::trigger_redirect( $form, $params, $args );
2512 }
2513 }
2514
2515 public static function trigger_redirect( $form, $params, $args ) {
2516 $success_args = array(
2517 'action' => $params['action'],
2518 'conf_method' => 'redirect',
2519 'form' => $form,
2520 'entry_id' => $params['id'],
2521 );
2522
2523 if ( isset( $args['ajax'] ) ) {
2524 $success_args['ajax'] = $args['ajax'];
2525 }
2526
2527 self::run_success_action( $success_args );
2528 }
2529
2530 /**
2531 * Used when the success action is not 'message'
2532 *
2533 * @since 2.05
2534 * @since 6.0 `$args['force_delay_redirect']` is added.
2535 *
2536 * @param array $args {
2537 * The args.
2538 *
2539 * @type string $conf_method The method.
2540 * @type object $form Form object.
2541 * @type int $entry_id Entry ID.
2542 * @type string $action The action event. Accepts `create` or `update`.
2543 * @type array $fields The array of fields.
2544 * @type int $force_delay_redirect Force to show the message before redirecting in case redirect method runs.
2545 * }
2546 */
2547 public static function run_success_action( $args ) {
2548 global $frm_vars;
2549 $extra_args = $args;
2550 unset( $extra_args['form'] );
2551
2552 do_action( 'frm_success_action', $args['conf_method'], $args['form'], $args['form']->options, $args['entry_id'], $extra_args );
2553
2554 $opt = ! isset( $args['action'] ) || $args['action'] === 'create' ? 'success' : 'edit';
2555
2556 $args['success_opt'] = $opt;
2557 $args['ajax'] = ! empty( $frm_vars['ajax'] );
2558
2559 if ( $args['conf_method'] === 'page' && is_numeric( $args['form']->options[ $opt . '_page_id' ] ) ) {
2560 self::load_page_after_submit( $args );
2561 } elseif ( $args['conf_method'] === 'redirect' ) {
2562 self::redirect_after_submit( $args );
2563 } else {
2564 self::show_message_after_save( $args );
2565 }
2566 }
2567
2568 /**
2569 * Gets met On Submit actions.
2570 *
2571 * @since 6.0
2572 *
2573 * @param array $args See {@see FrmFormsController::run_success_action()}.
2574 * @param string $event Form event. Default is `create`.
2575 * @return array Array of actions that meet the conditional logics.
2576 */
2577 public static function get_met_on_submit_actions( $args, $event = 'create' ) {
2578 if ( ! FrmOnSubmitHelper::form_has_migrated( $args['form'] ) ) {
2579 return array();
2580 }
2581
2582 // If a redirect action has already opened the URL in a new tab, we show the default message in the current tab.
2583 if ( ! empty( self::$redirected_in_new_tab[ $args['form']->id ] ) ) {
2584 return array( FrmOnSubmitHelper::get_fallback_action_after_open_in_new_tab( $event ) );
2585 }
2586
2587 $entry = FrmEntry::getOne( $args['entry_id'], true );
2588 $actions = FrmOnSubmitHelper::get_actions( $args['form']->id );
2589 $met_actions = array();
2590 $has_redirect = false;
2591
2592 foreach ( $actions as $action ) {
2593 if ( ! in_array( $event, $action->post_content['event'], true ) ) {
2594 continue;
2595 }
2596
2597 if ( FrmFormAction::action_conditions_met( $action, $entry ) ) {
2598 continue;
2599 }
2600
2601 $action_type = FrmOnSubmitHelper::get_action_type( $action );
2602
2603 if ( 'redirect' === $action_type ) {
2604 if ( $has_redirect ) {
2605 // Do not process because we run the first redirect action only.
2606 continue;
2607 }
2608 }
2609
2610 if ( ! self::is_valid_on_submit_action( $action, $args, $event ) ) {
2611 continue;
2612 }
2613
2614 if ( 'redirect' === $action_type ) {
2615 $has_redirect = true;
2616 }
2617
2618 $met_actions[] = $action;
2619 unset( $action );
2620 }//end foreach
2621
2622 $args['event'] = $event;
2623
2624 /**
2625 * Filters the On Submit actions that meet the conditional logics.
2626 *
2627 * @since 6.0
2628 *
2629 * @param array $met_actions Actions that meet the conditional logics.
2630 * @param array $args See {@see FrmFormsController::run_success_action()}. `$args['event']` is also added.
2631 */
2632 $met_actions = apply_filters( 'frm_get_met_on_submit_actions', $met_actions, $args );
2633
2634 if ( empty( $met_actions ) ) {
2635 $met_actions = array( FrmOnSubmitHelper::get_fallback_action( $event ) );
2636 }
2637
2638 return $met_actions;
2639 }
2640
2641 /**
2642 * Checks if a Confirmation action has the valid data.
2643 *
2644 * @since 6.1.2
2645 *
2646 * @param object $action Form action object.
2647 * @param array $args See {@see FrmFormsController::run_success_action()}.
2648 * @param string $event Form event. Default is `create`.
2649 * @return bool
2650 */
2651 private static function is_valid_on_submit_action( $action, $args, $event = 'create' ) {
2652 $action_type = FrmOnSubmitHelper::get_action_type( $action );
2653
2654 if ( 'redirect' === $action_type ) {
2655 // Run through frm_redirect_url filter. This is used for the valid action check.
2656 $action->post_content['success_url'] = apply_filters(
2657 'frm_redirect_url',
2658 $action->post_content['success_url'],
2659 $args['form'],
2660 $args + array( 'action' => $event )
2661 );
2662
2663 return ! empty( $action->post_content['success_url'] );
2664 }
2665
2666 if ( 'page' === $action_type ) {
2667 if ( empty( $action->post_content['success_page_id'] ) ) {
2668 return false;
2669 }
2670
2671 $page = get_post( $action->post_content['success_page_id'] );
2672 if ( ! $page || 'trash' === $page->post_status ) {
2673 return false;
2674 }
2675 }
2676
2677 return true;
2678 }
2679
2680 /**
2681 * Runs On Submit actions.
2682 *
2683 * @since 6.0
2684 *
2685 * @param array $args See inside {@see FrmFormsController::get_form_contents()} method.
2686 */
2687 public static function run_on_submit_actions( $args ) {
2688 $args['conf_method'] = self::get_confirmation_method(
2689 array(
2690 'form' => $args['form'],
2691 'entry_id' => $args['entry_id'],
2692 'action' => FrmOnSubmitHelper::current_event( $args ),
2693 )
2694 );
2695 if ( ! is_array( $args['conf_method'] ) ) {
2696 self::run_success_action( $args );
2697 return;
2698 }
2699
2700 // If conf_method is an array, run On Submit actions.
2701 if ( ! $args['conf_method'] ) {
2702 // Use default message.
2703 FrmOnSubmitHelper::populate_on_submit_data( $args['form']->options );
2704 self::run_success_action( $args );
2705 } elseif ( 1 === count( $args['conf_method'] ) ) {
2706 FrmOnSubmitHelper::populate_on_submit_data( $args['form']->options, reset( $args['conf_method'] ) );
2707 $args['conf_method'] = $args['form']->options['success_action'];
2708 self::run_success_action( $args );
2709 } else {
2710 self::run_multi_on_submit_actions( $args );
2711 }
2712 }
2713
2714 /**
2715 * Runs multiple success actions.
2716 *
2717 * @since 6.0
2718 *
2719 * @param array $args See {@see FrmFormsController::run_success_action()}.
2720 */
2721 public static function run_multi_on_submit_actions( $args ) {
2722 $redirect_action = null;
2723 foreach ( $args['conf_method'] as $action ) {
2724 if ( 'redirect' === FrmOnSubmitHelper::get_action_type( $action ) ) {
2725 // We catch the redirect action to run it last.
2726 $redirect_action = $action;
2727 continue;
2728 }
2729
2730 self::run_single_on_submit_action( $args, $action );
2731
2732 unset( $action );
2733 }
2734
2735 if ( $redirect_action ) {
2736 // Show script to delay the redirection.
2737 $args['force_delay_redirect'] = true;
2738 self::run_single_on_submit_action( $args, $redirect_action );
2739 }
2740 }
2741
2742 /**
2743 * Runs single On Submit action.
2744 *
2745 * @since 6.0
2746 *
2747 * @param array $args See {@see FrmFormsController::run_success_action()}.
2748 * @param object $action On Submit action object.
2749 */
2750 public static function run_single_on_submit_action( $args, $action ) {
2751 $new_args = self::get_run_success_action_args( $args, $action );
2752 self::run_success_action( $new_args );
2753 }
2754
2755 /**
2756 * Gets run_success_action() args from the On Submit action.
2757 *
2758 * @since 6.0
2759 *
2760 * @param array $args See {@see FrmFormsController::run_success_action()}.
2761 * @param object $action On Submit action object.
2762 * @return array
2763 */
2764 private static function get_run_success_action_args( $args, $action ) {
2765 $new_args = $args;
2766
2767 FrmOnSubmitHelper::populate_on_submit_data( $new_args['form']->options, $action, $args['action'] );
2768
2769 $opt = 'update' === $args['action'] ? 'edit_' : 'success_';
2770
2771 $new_args['conf_method'] = $new_args['form']->options[ $opt . 'action' ];
2772
2773 /**
2774 * Filters the run success action args.
2775 *
2776 * @since 6.0
2777 *
2778 * @param array $new_args The new args.
2779 * @param array $args The old args. See {@see FrmFormsController::run_success_action()}.
2780 * @param object $action On Submit action object.
2781 */
2782 return apply_filters( 'frm_get_run_success_action_args', $new_args, $args, $action );
2783 }
2784
2785 /**
2786 * @since 3.0
2787 */
2788 private static function load_page_after_submit( $args ) {
2789 global $post;
2790 $opt = $args['success_opt'];
2791 if ( ! $post || $args['form']->options[ $opt . '_page_id' ] != $post->ID ) {
2792 $page = get_post( $args['form']->options[ $opt . '_page_id' ] );
2793 $old_post = $post;
2794 $post = $page;
2795 $content = apply_filters( 'frm_content', $page->post_content, $args['form'], $args['entry_id'] );
2796
2797 // Fix the On Submit page content doesn't show when previewing In theme.
2798 $has_preview_filter = has_filter( 'the_content', 'FrmFormsController::preview_content' );
2799
2800 if ( $has_preview_filter ) {
2801 remove_filter( 'the_content', 'FrmFormsController::preview_content', 9999 );
2802 }
2803
2804 echo apply_filters( 'the_content', $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2805
2806 if ( $has_preview_filter ) {
2807 add_filter( 'the_content', 'FrmFormsController::preview_content', 9999 );
2808 }
2809
2810 $post = $old_post;
2811 }//end if
2812 }
2813
2814 /**
2815 * @since 3.0
2816 * @param array $args See {@see FrmFormsController::run_success_action()}.
2817 */
2818 private static function redirect_after_submit( $args ) {
2819 add_filter( 'frm_use_wpautop', '__return_false' );
2820
2821 $opt = $args['success_opt'];
2822 $success_url = trim( $args['form']->options[ $opt . '_url' ] );
2823 $success_url = apply_filters( 'frm_content', $success_url, $args['form'], $args['entry_id'] );
2824 $success_url = do_shortcode( $success_url );
2825
2826 $args['id'] = $args['entry_id'];
2827 FrmEntriesController::delete_entry_before_redirect( $success_url, $args['form'], $args );
2828
2829 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
2830 $success_url = apply_filters( 'frm_redirect_url', $success_url, $args['form'], $args );
2831
2832 $doing_ajax = FrmAppHelper::doing_ajax();
2833
2834 if ( ! empty( $args['ajax'] ) && $doing_ajax && empty( $args['force_delay_redirect'] ) ) {
2835 // Is AJAX submit and there is just one Redirect action runs.
2836 echo json_encode( self::get_ajax_redirect_response_data( $args + compact( 'success_url' ) ) );
2837 wp_die();
2838 }
2839
2840 if ( ! headers_sent() && empty( $args['force_delay_redirect'] ) && empty( $args['form']->options['redirect_delay'] ) ) {
2841 // Not AJAX submit, no headers sent, and there is just one Redirect action runs.
2842 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2843 self::print_open_in_new_tab_js_with_fallback_handler( $success_url, $args );
2844 self::$redirected_in_new_tab[ $args['form']->id ] = 1;
2845 return;
2846 }
2847
2848 wp_redirect( esc_url_raw( $success_url ) );
2849 // Do not use wp_die or redirect fails.
2850 die();
2851 }
2852
2853 // Redirect with a delay.
2854 self::redirect_after_submit_using_js( $args + compact( 'success_url', 'doing_ajax' ) );
2855 }
2856
2857 /**
2858 * Prints open in new tab js with fallback handler.
2859 *
2860 * @since 6.3.1
2861 *
2862 * @param string $success_url Success URL.
2863 * @param array $args See {@see FrmFormsController::redirect_after_submit()}.
2864 */
2865 private static function print_open_in_new_tab_js_with_fallback_handler( $success_url, $args ) {
2866 echo '<script>var newTab = window.open("' . esc_url_raw( $success_url ) . '", "_blank");';
2867 echo 'if ( ! newTab ) {';
2868
2869 $data = array(
2870 'formId' => intval( $args['form']->id ),
2871 'message' => self::get_redirect_fallback_message( $success_url, $args ),
2872 );
2873 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2874 echo 'frmShowNewTabFallback = ' . FrmAppHelper::maybe_json_encode( $data ) . ';';
2875 echo '}</script>';
2876 }
2877
2878 /**
2879 * Gets response data for redirect action when AJAX submitting.
2880 *
2881 * @since 6.3.1
2882 *
2883 * @param array $args See {@see FrmFormsController::run_success_action()}.
2884 * @return array
2885 */
2886 private static function get_ajax_redirect_response_data( $args ) {
2887 $response_data = array(
2888 'redirect' => $args['success_url'],
2889 'content' => '',
2890 );
2891 $unset_content = true;
2892
2893 if ( ! empty( $args['form']->options['redirect_delay'] ) ) {
2894 $response_data['delay'] = $args['form']->options['redirect_delay_time'];
2895 $response_data['content'] .= self::get_redirect_message( $args['success_url'], $args['form']->options['redirect_delay_msg'], $args );
2896 $unset_content = false;
2897 }
2898
2899 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2900 $response_data['openInNewTab'] = 1;
2901
2902 // Only show open in new tab text if there is no delay.
2903 if ( empty( $response_data['content'] ) ) {
2904 $args['message'] = FrmOnSubmitHelper::get_default_new_tab_msg();
2905
2906 $args['form']->options['success_msg'] = $args['message'];
2907 $args['form']->options['edit_msg'] = $args['message'];
2908 if ( ! isset( $args['fields'] ) ) {
2909 $args['fields'] = FrmField::get_all_for_form( $args['form']->id );
2910 }
2911
2912 $args['message'] = self::prepare_submit_message( $args['form'], $args['entry_id'], $args );
2913
2914 ob_start();
2915 self::show_lone_success_message( $args );
2916 $response_data['content'] .= ob_get_clean();
2917 $unset_content = false;
2918 }//end if
2919
2920 $response_data['fallbackMsg'] = self::get_redirect_fallback_message( $args['success_url'], $args );
2921 }//end if
2922
2923 if ( $unset_content && '' === $response_data['content'] ) {
2924 unset( $response_data['content'] );
2925 }
2926
2927 return $response_data;
2928 }
2929
2930 /**
2931 * Redirects after submitting using JS. This is used when showing message before redirecting.
2932 *
2933 * @since 6.0
2934 *
2935 * @param array $args See {@see FrmFormsController::run_success_action()}.
2936 */
2937 private static function redirect_after_submit_using_js( $args ) {
2938 $success_msg = $args['form']->options['redirect_delay_msg'];
2939 $redirect_msg = self::get_redirect_message( $args['success_url'], $success_msg, $args );
2940 $success_url = esc_url_raw( $args['success_url'] );
2941
2942 /**
2943 * Filters the delay time before redirecting when On Submit Redirect action is delayed.
2944 *
2945 * @since 6.0
2946 *
2947 * @param int $delay_time Delay time in milliseconds.
2948 */
2949 $delay_time = apply_filters( 'frm_redirect_delay_time', 1000 * $args['form']->options['redirect_delay_time'] );
2950
2951 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2952 $redirect_js = 'window.open("' . $success_url . '", "_blank")';
2953 } else {
2954 $redirect_js = 'window.location="' . $success_url . '";';
2955 }
2956
2957 add_filter( 'frm_use_wpautop', '__return_true' );
2958
2959 echo FrmAppHelper::maybe_kses( $redirect_msg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2960 echo '<script>';
2961 if ( empty( $args['doing_ajax'] ) ) {
2962 // Not AJAX submit, delay JS until window.load.
2963 echo 'window.onload=function(){';
2964 }
2965 echo 'setTimeout(function(){' . $redirect_js . '}, ' . intval( $delay_time ) . ');'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2966 if ( empty( $args['doing_ajax'] ) ) {
2967 echo '};';
2968 }
2969 echo '</script>';
2970 }
2971
2972 /**
2973 * @since 3.0
2974 *
2975 * @param string $success_url
2976 * @param string $success_msg
2977 * @param array $args
2978 */
2979 private static function get_redirect_message( $success_url, $success_msg, $args ) {
2980 $success_msg = apply_filters( 'frm_content', $success_msg, $args['form'], $args['entry_id'] );
2981 $success_msg = do_shortcode( FrmAppHelper::use_wpautop( $success_msg ) );
2982 $redirect_msg = '<div class="' . esc_attr( FrmFormsHelper::get_form_style_class( $args['form'] ) ) . '"><div class="frm-redirect-msg" role="status">' . $success_msg . '<br/>' .
2983 self::get_redirect_fallback_message( $success_url, $args ) .
2984 '</div></div>';
2985
2986 $redirect_args = array(
2987 'entry_id' => $args['entry_id'],
2988 'form_id' => $args['form']->id,
2989 'form' => $args['form'],
2990 );
2991
2992 return apply_filters( 'frm_redirect_msg', $redirect_msg, $redirect_args );
2993 }
2994
2995 /**
2996 * Gets fallback message when redirecting failed.
2997 *
2998 * @since 6.3.1
2999 *
3000 * @param string $success_url Redirect URL.
3001 * @param array $args Contains `form` object.
3002 * @return string
3003 */
3004 private static function get_redirect_fallback_message( $success_url, $args ) {
3005 $target = '';
3006 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
3007 $target = ' target="_blank"';
3008 }
3009
3010 return sprintf(
3011 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
3012 __( '%1$sClick here%2$s if you are not automatically redirected.', 'formidable' ),
3013 '<a href="' . esc_url( $success_url ) . '"' . $target . '>',
3014 '</a>'
3015 );
3016 }
3017
3018 /**
3019 * Prepare to show the success message and empty form after submit
3020 *
3021 * @since 2.05
3022 */
3023 public static function show_message_after_save( $atts ) {
3024 $atts['message'] = self::prepare_submit_message( $atts['form'], $atts['entry_id'], $atts );
3025
3026 if ( ! isset( $atts['form']->options['show_form'] ) || $atts['form']->options['show_form'] ) {
3027 if ( isset( $atts['action'] ) && 'update' === $atts['action'] && is_callable( array( 'FrmProEntriesController', 'show_front_end_form_with_entry' ) ) ) {
3028 $entry = FrmEntry::getOne( $atts['entry_id'] );
3029 if ( $entry ) {
3030 // This is copied from the Pro plugin.
3031 $atts['conf_message'] = FrmProEntriesController::confirmation( 'message', $atts['form'], $atts['form']->options, $entry->id, $atts );
3032 $atts['show_form'] = FrmProEntriesController::is_form_displayed_after_edit( $atts['form'] );
3033 FrmProEntriesController::show_front_end_form_with_entry( $entry, $atts );
3034 }
3035 } else {
3036 self::show_form_after_submit( $atts );
3037 }
3038 } else {
3039 self::show_lone_success_message( $atts );
3040 }
3041 }
3042
3043 /**
3044 * Show an empty form
3045 *
3046 * @since 2.05
3047 */
3048 private static function show_form_after_submit( $args ) {
3049 self::fill_atts_for_form_display( $args );
3050
3051 $errors = $args['errors'];
3052 $message = $args['message'];
3053 $form = $args['form'];
3054 $title = $args['title'];
3055 $description = $args['description'];
3056
3057 if ( empty( $args['fields'] ) ) {
3058 $values = array(
3059 'custom_style' => FrmAppHelper::custom_style_value( array() ),
3060 );
3061 } else {
3062 $values = FrmEntriesHelper::setup_new_vars( $args['fields'], $form, $args['reset'] );
3063 }
3064 unset( $args );
3065
3066 $include_form_tag = apply_filters( 'frm_include_form_tag', true, $form );
3067
3068 $frm_settings = FrmAppHelper::get_settings();
3069 $submit = $form->options['submit_value'] ?? $frm_settings->submit_value;
3070
3071 global $frm_vars;
3072 self::maybe_load_css( $form, $values['custom_style'], $frm_vars['load_css'] );
3073
3074 $message_placement = self::message_placement( $form, $message );
3075
3076 include FrmAppHelper::plugin_path() . '/classes/views/frm-entries/new.php';
3077 }
3078
3079 /**
3080 * @since 4.05.02
3081 * @return string - 'before', 'after', or 'submit'
3082 */
3083 private static function message_placement( $form, $message ) {
3084 $place = 'before';
3085
3086 if ( $message && isset( $form->options['form_class'] ) ) {
3087 if ( strpos( $form->options['form_class'], 'frm_below_success' ) !== false ) {
3088 $place = 'after';
3089 } elseif ( strpos( $form->options['form_class'], 'frm_inline_success' ) !== false ) {
3090 $place = 'submit';
3091 }
3092 }
3093
3094 /**
3095 * @since 4.05.02
3096 * @return string - 'before' or 'after'
3097 */
3098 return apply_filters( 'frm_message_placement', $place, compact( 'form', 'message' ) );
3099 }
3100
3101 /**
3102 * Get all the values needed on the new.php entry page
3103 *
3104 * @since 2.05
3105 */
3106 private static function fill_atts_for_form_display( &$args ) {
3107 if ( ! isset( $args['title'] ) && isset( $args['show_title'] ) ) {
3108 $args['title'] = $args['show_title'];
3109 }
3110
3111 if ( ! isset( $args['description'] ) && isset( $args['show_description'] ) ) {
3112 $args['description'] = $args['show_description'];
3113 }
3114
3115 $defaults = array(
3116 'errors' => array(),
3117 'message' => '',
3118 'fields' => array(),
3119 'form' => array(),
3120 'title' => true,
3121 'description' => false,
3122 'reset' => false,
3123 );
3124 $args = wp_parse_args( $args, $defaults );
3125 }
3126
3127 /**
3128 * Show the success message without the form
3129 *
3130 * @since 2.05
3131 */
3132 private static function show_lone_success_message( $atts ) {
3133 global $frm_vars;
3134 $values = FrmEntriesHelper::setup_new_vars( $atts['fields'], $atts['form'], true );
3135 self::maybe_load_css( $atts['form'], $values['custom_style'], $frm_vars['load_css'] );
3136
3137 $include_extra_container = 'frm_forms' . FrmFormsHelper::get_form_style_class( $values );
3138
3139 $errors = array();
3140 $form = $atts['form'];
3141 $message = $atts['message'];
3142
3143 include FrmAppHelper::plugin_path() . '/classes/views/frm-entries/errors.php';
3144 }
3145
3146 /**
3147 * Prepare the success message before it's shown
3148 *
3149 * @since 2.05
3150 * @since 6.0.x Added the third parameter.
3151 *
3152 * @param object $form Form object.
3153 * @param int $entry_id Entry ID.
3154 * @param array $args See {@see FrmFormsController::run_success_action()}.
3155 * @return string
3156 */
3157 private static function prepare_submit_message( $form, $entry_id, $args = array() ) {
3158 $frm_settings = FrmAppHelper::get_settings( array( 'current_form' => $form->id ) );
3159 $opt = $args['success_opt'] ?? 'success';
3160
3161 if ( $entry_id && is_numeric( $entry_id ) ) {
3162 $message = $form->options[ $opt . '_msg' ] ?? $frm_settings->success_msg;
3163 $class = 'frm_message';
3164 } else {
3165 $message = $frm_settings->failed_msg;
3166 $class = FrmFormsHelper::form_error_class();
3167 }
3168
3169 $message = FrmFormsHelper::get_success_message( compact( 'message', 'form', 'entry_id', 'class' ) );
3170
3171 return apply_filters( 'frm_main_feedback', $message, $form, $entry_id );
3172 }
3173
3174 /**
3175 * @return void
3176 */
3177 public static function front_head() {
3178 $version = FrmAppHelper::plugin_version();
3179 $suffix = FrmAppHelper::js_suffix();
3180
3181 if ( ! empty( $suffix ) && self::has_combo_js_file() ) {
3182 wp_register_script( 'formidable', FrmAppHelper::plugin_url() . '/js/frm.min.js', array( 'jquery' ), $version, true );
3183 } else {
3184 wp_register_script( 'formidable', FrmAppHelper::plugin_url() . "/js/formidable{$suffix}.js", array( 'jquery' ), $version, true );
3185 }
3186
3187 add_filter( 'script_loader_tag', 'FrmFormsController::defer_script_loading', 10, 2 );
3188
3189 if ( FrmAppHelper::is_admin() ) {
3190 // don't load this in back-end
3191 return;
3192 }
3193
3194 FrmAppHelper::localize_script( 'front' );
3195 FrmStylesController::enqueue_css( 'register' );
3196 }
3197
3198 /**
3199 * @since 3.0
3200 */
3201 public static function has_combo_js_file() {
3202 return is_readable( FrmAppHelper::plugin_path() . '/js/frm.min.js' );
3203 }
3204
3205 public static function maybe_load_css( $form, $this_load, $global_load ) {
3206 $load_css = FrmForm::is_form_loaded( $form, $this_load, $global_load );
3207
3208 if ( ! $load_css ) {
3209 return;
3210 }
3211
3212 global $frm_vars;
3213 self::footer_js( 'header' );
3214 $frm_vars['css_loaded'] = true;
3215
3216 self::load_late_css();
3217 }
3218
3219 /**
3220 * If css is loaded only on applicable pages, include it before the form loads
3221 * to prevent a flash of unstyled form.
3222 *
3223 * @since 4.01
3224 *
3225 * @return void
3226 */
3227 private static function load_late_css() {
3228 $frm_settings = FrmAppHelper::get_settings();
3229 $late_css = $frm_settings->load_style === 'dynamic';
3230
3231 if ( ! $late_css || ! self::should_load_late() ) {
3232 return;
3233 }
3234
3235 global $wp_styles;
3236 if ( is_array( $wp_styles->queue ) && in_array( 'formidable', $wp_styles->queue, true ) ) {
3237 wp_print_styles( 'formidable' );
3238 }
3239 }
3240
3241 /**
3242 * Avoid late load if All in One SEO is active because it prevents CSS from loading entirely.
3243 *
3244 * @since 5.2.03
3245 *
3246 * @return bool
3247 */
3248 private static function should_load_late() {
3249 return ! function_exists( 'aioseo' );
3250 }
3251
3252 public static function defer_script_loading( $tag, $handle ) {
3253 if ( 'captcha-api' == $handle && ! strpos( $tag, 'defer' ) ) {
3254 $tag = str_replace( ' src', ' defer="defer" async="async" src', $tag );
3255 }
3256
3257 return $tag;
3258 }
3259
3260 public static function footer_js( $location = 'footer' ) {
3261 global $frm_vars;
3262
3263 FrmStylesController::enqueue_css();
3264
3265 if ( ! FrmAppHelper::is_admin() && $location !== 'header' && ! empty( $frm_vars['forms_loaded'] ) ) {
3266 // load formidable js
3267 wp_enqueue_script( 'formidable' );
3268
3269 FrmHoneypot::maybe_print_honeypot_js();
3270 }
3271 }
3272
3273 /**
3274 * @since 2.0.8
3275 */
3276 private static function maybe_minimize_form( $atts, &$content ) {
3277 // check if minimizing is turned on
3278 if ( self::is_minification_on( $atts ) ) {
3279 $content = str_replace( array( "\r\n", "\r", "\n", "\t", ' ' ), '', $content );
3280 }
3281 }
3282
3283 /**
3284 * @since 2.0.8
3285 * @return bool
3286 */
3287 private static function is_minification_on( $atts ) {
3288 return ! empty( $atts['minimize'] );
3289 }
3290
3291 /**
3292 * @since 5.0.16
3293 *
3294 * @return void
3295 */
3296 public static function landing_page_preview_option() {
3297 $dir = apply_filters( 'frm_landing_page_preview_option', false );
3298 if ( false === $dir || ! file_exists( $dir . 'landing-page-preview-option.php' ) ) {
3299 $dir = self::get_form_views_path();
3300 }
3301 include $dir . 'landing-page-preview-option.php';
3302 }
3303
3304 /**
3305 * @since 5.0.16
3306 *
3307 * @return string
3308 */
3309 private static function get_form_views_path() {
3310 return FrmAppHelper::plugin_path() . '/classes/views/frm-forms/';
3311 }
3312
3313 /**
3314 * Create a page with an embedded formidable Gutenberg block.
3315 *
3316 * @since 5.2
3317 *
3318 * @return void
3319 */
3320 public static function create_page_with_shortcode() {
3321 if ( ! current_user_can( 'publish_posts' ) ) {
3322 die( 0 );
3323 }
3324
3325 check_ajax_referer( 'frm_ajax', 'nonce' );
3326
3327 $type = FrmAppHelper::get_post_param( 'type', '', 'sanitize_text_field' );
3328 if ( ! $type || ! in_array( $type, array( 'form', 'view' ), true ) ) {
3329 die( 0 );
3330 }
3331
3332 $object_id = FrmAppHelper::get_post_param( 'object_id', '', 'absint' );
3333 if ( ! $object_id ) {
3334 die( 0 );
3335 }
3336
3337 $postarr = array( 'post_type' => 'page' );
3338
3339 if ( 'form' === $type ) {
3340 $postarr['post_content'] = self::get_page_shortcode_content_for_form( $object_id );
3341 } else {
3342 $postarr['post_content'] = apply_filters( 'frm_create_page_with_' . $type . '_shortcode_content', '', $object_id );
3343 }
3344
3345 $name = FrmAppHelper::get_post_param( 'name', '', 'sanitize_text_field' );
3346 if ( $name ) {
3347 $postarr['post_title'] = $name;
3348 }
3349
3350 $success = wp_insert_post( $postarr );
3351 if ( ! is_numeric( $success ) || ! $success ) {
3352 die( 0 );
3353 }
3354
3355 wp_send_json(
3356 array(
3357 'redirect' => get_edit_post_link( $success, 'redirect' ),
3358 )
3359 );
3360 }
3361
3362 /**
3363 * @since 5.3
3364 *
3365 * @param int $form_id
3366 * @return string
3367 */
3368 private static function get_page_shortcode_content_for_form( $form_id ) {
3369 $shortcode = '[formidable id="' . $form_id . '"]';
3370 $html_comment_start = '<!-- wp:formidable/simple-form {"formId":"' . $form_id . '"} -->';
3371 $html_comment_end = '<!-- /wp:formidable/simple-form -->';
3372 return $html_comment_start . '<div>' . $shortcode . '</div>' . $html_comment_end;
3373 }
3374
3375 /**
3376 * Get page dropdown for AJAX request for embedding form in an existing page.
3377 *
3378 * @return void
3379 */
3380 public static function get_page_dropdown() {
3381 if ( ! current_user_can( 'publish_posts' ) ) {
3382 die( 0 );
3383 }
3384
3385 check_ajax_referer( 'frm_ajax', 'nonce' );
3386
3387 $html = FrmAppHelper::clip(
3388 function () {
3389 FrmAppHelper::maybe_autocomplete_pages_options(
3390 array(
3391 'field_name' => 'frm_page_dropdown',
3392 'page_id' => '',
3393 'placeholder' => __( 'Select a Page', 'formidable' ),
3394 )
3395 );
3396 }
3397 );
3398 $post_type_object = get_post_type_object( 'page' );
3399 wp_send_json(
3400 array(
3401 'html' => $html,
3402 'edit_page_url' => admin_url( sprintf( $post_type_object->_edit_link . '&action=edit', 0 ) ),
3403 )
3404 );
3405 }
3406
3407 /**
3408 * @deprecated 4.0
3409 */
3410 public static function create( $values = array() ) {
3411 _deprecated_function( __METHOD__, '4.0', 'FrmFormsController::update' );
3412 self::update( $values );
3413 }
3414
3415 /**
3416 * Education for premium features.
3417 *
3418 * @since 4.05
3419 * @deprecated 6.16.3
3420 *
3421 * @return void
3422 */
3423 public static function add_form_style_tab_options() {
3424 _deprecated_function( __METHOD__, '6.16.3' );
3425 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/add_form_style_options.php';
3426 }
3427 }
3428