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

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