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

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