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

3,277 lines 94.1 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 foreach ( array( 'landing', 'chat', 'abandonment' ) as $feature ) {
1417 if ( ! FrmAppHelper::show_new_feature( $feature ) ) {
1418 unset( $sections[ $feature ] );
1419 }
1420 }
1421
1422 $sections = apply_filters( 'frm_add_form_settings_section', $sections, $values );
1423
1424 foreach ( $sections as $key => $section ) {
1425 $defaults = array(
1426 'html_class' => '',
1427 'name' => ucfirst( $key ),
1428 'icon' => 'frm_icon_font frm_settings_icon',
1429 );
1430
1431 $section = array_merge( $defaults, $section );
1432
1433 if ( ! isset( $section['anchor'] ) ) {
1434 $section['anchor'] = $key;
1435 }
1436 $section['anchor'] .= '_settings';
1437
1438 if ( ! isset( $section['title'] ) ) {
1439 $section['title'] = $section['name'];
1440 }
1441
1442 if ( ! isset( $section['id'] ) ) {
1443 $section['id'] = $section['anchor'];
1444 }
1445
1446 $sections[ $key ] = $section;
1447 }//end foreach
1448
1449 return $sections;
1450 }
1451
1452 /**
1453 * @since 4.0
1454 *
1455 * @param array $values
1456 * @return void
1457 */
1458 public static function advanced_settings( $values ) {
1459 $first_h3 = 'frm_first_h3';
1460
1461 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-advanced.php';
1462 }
1463
1464 /**
1465 * @param array $values
1466 * @return void
1467 */
1468 public static function render_spam_settings( $values ) {
1469 if ( function_exists( 'akismet_http_post' ) ) {
1470 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/akismet.php';
1471 }
1472 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/honeypot.php';
1473 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/antispam.php';
1474 }
1475
1476 /**
1477 * @since 4.0
1478 *
1479 * @param array $values
1480 * @return void
1481 */
1482 public static function buttons_settings( $values ) {
1483 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-buttons.php';
1484 }
1485
1486 /**
1487 * @since 4.0
1488 *
1489 * @param array $values
1490 * @return void
1491 */
1492 public static function html_settings( $values ) {
1493 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/settings-html.php';
1494 }
1495
1496 /**
1497 * Replace old Submit Button href with new href to avoid errors in Chrome
1498 *
1499 * @since 2.03.08
1500 *
1501 * @param array|bool $values
1502 * @return void
1503 */
1504 private static function clean_submit_html( &$values ) {
1505 if ( is_array( $values ) && isset( $values['submit_html'] ) ) {
1506 $values['submit_html'] = str_replace( 'javascript:void(0)', '#', $values['submit_html'] );
1507 }
1508 }
1509
1510 /**
1511 * Updates classes used in submit and prev buttons to avoid conflict with twenty twenty-one theme.
1512 *
1513 * @param array $classes
1514 *
1515 * @return array
1516 */
1517 public static function update_button_classes( $classes ) {
1518 if ( function_exists( 'twenty_twenty_one_setup' ) ) {
1519 $classes[] = 'has-text-color has-background';
1520 }
1521
1522 return $classes;
1523 }
1524
1525 /**
1526 * @param int|string $form_id
1527 * @param string $class
1528 * @return void
1529 */
1530 public static function mb_tags_box( $form_id, $class = '' ) {
1531 $fields = FrmField::get_all_for_form( $form_id, '', 'include' );
1532
1533 /**
1534 * Allows modifying the list of fields in the tags box.
1535 *
1536 * @since 5.0.04
1537 *
1538 * @param array $fields The list of fields.
1539 * @param array $args The arguments. Contains `form_id`.
1540 */
1541 $fields = apply_filters( 'frm_fields_in_tags_box', $fields, compact( 'form_id' ) );
1542 $linked_forms = array();
1543 $col = 'one';
1544 $settings_tab = FrmAppHelper::is_admin_page( 'formidable' );
1545
1546 $cond_shortcodes = apply_filters( 'frm_conditional_shortcodes', array() );
1547 $entry_shortcodes = self::get_shortcode_helpers( $settings_tab );
1548
1549 $advanced_helpers = self::advanced_helpers( compact( 'fields', 'form_id' ) );
1550
1551 include FrmAppHelper::plugin_path() . '/classes/views/shared/mb_adv_info.php';
1552 }
1553
1554 /**
1555 * @since 3.04.01
1556 */
1557 private static function advanced_helpers( $atts ) {
1558 $advanced_helpers = array(
1559 'default' => array(
1560 'heading' => __( 'Customize field values with the following parameters.', 'formidable' ),
1561 'codes' => self::get_advanced_shortcodes(),
1562 ),
1563 );
1564
1565 $user_fields = self::user_shortcodes();
1566 if ( $user_fields ) {
1567 $user_helpers = array();
1568 foreach ( $user_fields as $uk => $uf ) {
1569 $user_helpers[ '|user_id| show="' . $uk . '"' ] = $uf;
1570 unset( $uk, $uf );
1571 }
1572
1573 $advanced_helpers['user_id'] = array(
1574 'codes' => $user_helpers,
1575 );
1576 }
1577
1578 /**
1579 * Add extra helper shortcodes on the Advanced tab in form settings and views
1580 *
1581 * @since 3.04.01
1582 *
1583 * @param array $advanced_helpers
1584 * @param array $atts - Includes fields and form_id
1585 */
1586 return apply_filters( 'frm_advanced_helpers', $advanced_helpers, $atts );
1587 }
1588
1589 /**
1590 * Get an array of the options to display in the advanced tab
1591 * of the customization panel
1592 *
1593 * @since 2.0.6
1594 */
1595 private static function get_advanced_shortcodes() {
1596 $adv_shortcodes = array(
1597 'x sep=", "' => array(
1598 'label' => __( 'Separator', 'formidable' ),
1599 'title' => __( 'Use a different separator for checkbox fields', 'formidable' ),
1600 ),
1601 'x format="d-m-Y"' => array(
1602 'label' => __( 'Date Format', 'formidable' ),
1603 ),
1604 'x show="field_label"' => array(
1605 'label' => __( 'Field Label', 'formidable' ),
1606 ),
1607 'x wpautop=0' => array(
1608 'label' => __( 'No Auto P', 'formidable' ),
1609 'title' => __( 'Do not automatically add any paragraphs or line breaks', 'formidable' ),
1610 ),
1611 );
1612 $adv_shortcodes = apply_filters( 'frm_advanced_shortcodes', $adv_shortcodes );
1613
1614 // __( 'Leave blank instead of defaulting to User Login', 'formidable' ) : blank=1
1615
1616 return $adv_shortcodes;
1617 }
1618
1619 /**
1620 * @since 3.04.01
1621 */
1622 private static function user_shortcodes() {
1623 $options = array(
1624 'ID' => __( 'User ID', 'formidable' ),
1625 'first_name' => __( 'First Name', 'formidable' ),
1626 'last_name' => __( 'Last Name', 'formidable' ),
1627 'display_name' => __( 'Display Name', 'formidable' ),
1628 'user_login' => __( 'User Login', 'formidable' ),
1629 'user_email' => __( 'Email', 'formidable' ),
1630 'avatar' => __( 'Avatar', 'formidable' ),
1631 'author_link' => __( 'Author Link', 'formidable' ),
1632 );
1633
1634 return apply_filters( 'frm_user_shortcodes', $options );
1635 }
1636
1637 /**
1638 * Get an array of the helper shortcodes to display in the customization panel
1639 *
1640 * @since 2.0.6
1641 */
1642 private static function get_shortcode_helpers( $settings_tab ) {
1643 $entry_shortcodes = array(
1644 'id' => __( 'Entry ID', 'formidable' ),
1645 'key' => __( 'Entry Key', 'formidable' ),
1646 'post_id' => __( 'Post ID', 'formidable' ),
1647 'ip' => __( 'User IP', 'formidable' ),
1648 'created-at' => __( 'Entry created', 'formidable' ),
1649 'updated-at' => __( 'Entry updated', 'formidable' ),
1650 '' => '',
1651 'siteurl' => __( 'Site URL', 'formidable' ),
1652 'sitename' => __( 'Site Name', 'formidable' ),
1653 );
1654
1655 if ( ! FrmAppHelper::pro_is_installed() ) {
1656 unset( $entry_shortcodes['post_id'] );
1657 }
1658
1659 if ( $settings_tab ) {
1660 $entry_shortcodes['default-message'] = __( 'Default Msg', 'formidable' );
1661 $entry_shortcodes['default-html'] = __( 'Default HTML', 'formidable' );
1662 $entry_shortcodes['default-plain'] = __( 'Default Plain', 'formidable' );
1663 $entry_shortcodes['form_name'] = __( 'Form Name', 'formidable' );
1664 }
1665
1666 /**
1667 * Use this hook to add or remove buttons in the helpers section
1668 * in the customization panel
1669 *
1670 * @since 2.0.6
1671 */
1672 $entry_shortcodes = apply_filters( 'frm_helper_shortcodes', $entry_shortcodes, $settings_tab );
1673
1674 return $entry_shortcodes;
1675 }
1676
1677 /**
1678 * Insert the form class setting into the form.
1679 *
1680 * @param stdClass $form
1681 * @return void
1682 */
1683 public static function form_classes( $form ) {
1684 if ( isset( $form->options['form_class'] ) ) {
1685 echo esc_attr( sanitize_text_field( $form->options['form_class'] ) );
1686 }
1687
1688 if ( ! empty( $form->options['js_validate'] ) ) {
1689 echo ' frm_js_validate ';
1690 self::add_js_validate_form_to_global_vars( $form );
1691 }
1692
1693 if ( ! FrmFormsHelper::should_use_pro_for_ajax_submit() && FrmForm::is_ajax_on( $form ) ) {
1694 echo ' frm_ajax_submit ';
1695 }
1696 }
1697
1698 /**
1699 * @since 5.0.03
1700 *
1701 * @param stdClass $form
1702 */
1703 public static function add_js_validate_form_to_global_vars( $form ) {
1704 global $frm_vars;
1705 if ( ! isset( $frm_vars['js_validate_forms'] ) ) {
1706 $frm_vars['js_validate_forms'] = array();
1707 }
1708 $frm_vars['js_validate_forms'][ $form->id ] = $form;
1709 }
1710
1711 public static function get_email_html() {
1712 FrmAppHelper::permission_check( 'frm_view_forms' );
1713 check_ajax_referer( 'frm_ajax', 'nonce' );
1714
1715 echo FrmEntriesController::show_entry_shortcode( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1716 array(
1717 'form_id' => FrmAppHelper::get_post_param( 'form_id', '', 'absint' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1718 'default_email' => true,
1719 'plain_text' => FrmAppHelper::get_post_param( 'plain_text', '', 'absint' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1720 )
1721 );
1722 wp_die();
1723 }
1724
1725 /**
1726 * @param string $content
1727 * @param int|stdClass|string $form
1728 * @param false|int|stdClass|string $entry
1729 * @return string
1730 */
1731 public static function filter_content( $content, $form, $entry = false ) {
1732 $content = self::replace_form_name_shortcodes( $content, $form );
1733
1734 self::get_entry_by_param( $entry );
1735 if ( ! $entry ) {
1736 return $content;
1737 }
1738
1739 if ( is_object( $form ) ) {
1740 $form = $form->id;
1741 }
1742
1743 /*
1744 * Repeater actions adds `parent_entry` to `$entry` store the parent entry data. If `parent_entry` is not empty,
1745 * use the parent form ID instead of repeater form ID to fix the parent form field shortcodes doesn't work.
1746 */
1747 if ( ! empty( $entry->parent_entry ) ) {
1748 $form = $entry->parent_entry->form_id;
1749 }
1750
1751 $shortcodes = FrmFieldsHelper::get_shortcodes( $content, $form );
1752 $content = apply_filters( 'frm_replace_content_shortcodes', $content, $entry, $shortcodes );
1753
1754 return $content;
1755 }
1756
1757 /**
1758 * Replace any [form_name] shortcodes in a string.
1759 *
1760 * @since 5.5
1761 *
1762 * @param array|string $string
1763 * @param int|stdClass|string $form
1764 * @return array|string
1765 */
1766 public static function replace_form_name_shortcodes( $string, $form ) {
1767 if ( ! is_string( $string ) ) {
1768 return $string;
1769 }
1770
1771 if ( false === strpos( $string, '[form_name]' ) ) {
1772 return $string;
1773 }
1774
1775 if ( ! is_object( $form ) ) {
1776 $form = FrmForm::getOne( $form );
1777 }
1778
1779 $form_name = is_object( $form ) ? $form->name : '';
1780 return str_replace( '[form_name]', $form_name, $string );
1781 }
1782
1783 /**
1784 * @param false|int|stdClass|string $entry
1785 * @return void
1786 */
1787 private static function get_entry_by_param( &$entry ) {
1788 if ( ! $entry || ! is_object( $entry ) ) {
1789 if ( ! $entry || ! is_numeric( $entry ) ) {
1790 $entry = FrmAppHelper::get_post_param( 'id', false, 'sanitize_title' );
1791 }
1792
1793 FrmEntry::maybe_get_entry( $entry );
1794 }
1795 }
1796
1797 public static function replace_content_shortcodes( $content, $entry, $shortcodes ) {
1798 return FrmFieldsHelper::replace_content_shortcodes( $content, $entry, $shortcodes );
1799 }
1800
1801 /**
1802 * @param array $errors
1803 * @return array
1804 */
1805 public static function process_bulk_form_actions( $errors ) {
1806 if ( ! $_REQUEST ) {
1807 return $errors;
1808 }
1809
1810 $bulkaction = FrmAppHelper::get_param( 'action', '', 'get', 'sanitize_text_field' );
1811 if ( $bulkaction == - 1 ) {
1812 $bulkaction = FrmAppHelper::get_param( 'action2', '', 'get', 'sanitize_title' );
1813 }
1814
1815 if ( ! empty( $bulkaction ) && strpos( $bulkaction, 'bulk_' ) === 0 ) {
1816 FrmAppHelper::remove_get_action();
1817
1818 $bulkaction = str_replace( 'bulk_', '', $bulkaction );
1819 }
1820
1821 $ids = FrmAppHelper::get_param( 'item-action', '', 'get', 'sanitize_text_field' );
1822 if ( empty( $ids ) ) {
1823 $errors[] = __( 'No forms were specified', 'formidable' );
1824
1825 return $errors;
1826 }
1827
1828 $permission_error = FrmAppHelper::permission_nonce_error( '', '_wpnonce', 'bulk-toplevel_page_formidable' );
1829 if ( $permission_error !== false ) {
1830 $errors[] = $permission_error;
1831
1832 return $errors;
1833 }
1834
1835 if ( ! is_array( $ids ) ) {
1836 $ids = explode( ',', $ids );
1837 }
1838
1839 switch ( $bulkaction ) {
1840 case 'delete':
1841 $message = self::bulk_destroy( $ids );
1842 break;
1843 case 'trash':
1844 $message = self::bulk_trash( $ids );
1845 break;
1846 case 'untrash':
1847 $message = self::bulk_untrash( $ids );
1848 }
1849
1850 if ( ! empty( $message ) ) {
1851 $errors['message'] = $message;
1852 }
1853
1854 return $errors;
1855 }
1856
1857 public static function route() {
1858 $action = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action';
1859 $vars = array();
1860 FrmAppHelper::include_svg();
1861
1862 if ( isset( $_POST['frm_compact_fields'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1863 FrmAppHelper::permission_check( 'frm_edit_forms' );
1864
1865 // Javascript needs to be allowed in some field settings.
1866 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
1867 $json_vars = htmlspecialchars_decode( nl2br( str_replace( '&quot;', '"', wp_unslash( $_POST['frm_compact_fields'] ) ) ) );
1868 $json_vars = json_decode( $json_vars, true );
1869 if ( empty( $json_vars ) ) {
1870 // json decoding failed so we should return an error message.
1871 $action = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
1872 if ( 'edit' === $action ) {
1873 $action = 'update';
1874 }
1875
1876 add_filter( 'frm_validate_form', 'FrmFormsController::json_error' );
1877 } else {
1878 $vars = FrmAppHelper::json_to_array( $json_vars );
1879 $action = $vars[ $action ];
1880 unset( $_REQUEST['frm_compact_fields'], $_POST['frm_compact_fields'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1881 $_REQUEST = array_merge( $_REQUEST, $vars ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1882 $_POST = array_merge( $_POST, $_REQUEST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1883 }
1884 } else {
1885 $action = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
1886 if ( isset( $_REQUEST['delete_all'] ) ) {
1887 // Override the action for this page.
1888 $action = 'delete_all';
1889 }
1890 }//end if
1891
1892 add_action( 'frm_load_form_hooks', 'FrmHooksController::trigger_load_form_hooks' );
1893 FrmAppHelper::trigger_hook_load( 'form' );
1894
1895 switch ( $action ) {
1896 case 'create':
1897 case 'edit':
1898 case 'update':
1899 case 'trash':
1900 case 'untrash':
1901 case 'destroy':
1902 case 'settings':
1903 case 'update_settings':
1904 return self::$action( $vars );
1905 case 'lite-reports':
1906 self::no_reports( $vars );
1907 return;
1908 case 'views':
1909 self::no_views( $vars );
1910 return;
1911 default:
1912 do_action( 'frm_form_action_' . $action );
1913 if ( apply_filters( 'frm_form_stop_action_' . $action, false ) ) {
1914 return;
1915 }
1916
1917 $action = FrmAppHelper::get_param( 'action', '', 'get', 'sanitize_text_field' );
1918 if ( $action == - 1 ) {
1919 $action = FrmAppHelper::get_param( 'action2', '', 'get', 'sanitize_title' );
1920 }
1921
1922 if ( strpos( $action, 'bulk_' ) === 0 ) {
1923 FrmAppHelper::remove_get_action();
1924
1925 self::list_form();
1926 return;
1927 }
1928
1929 $message = FrmAppHelper::get_param( 'message' );
1930 if ( 'form_duplicate_error' === $message ) {
1931 self::display_forms_list( array(), '', array( __( 'There was a problem duplicating the form', 'formidable' ) ) );
1932 return;
1933 }
1934
1935 if ( 'forms_permanently_deleted' === $message ) {
1936 $count = FrmAppHelper::get_param( 'forms_deleted', 0, 'get', 'absint' );
1937 /* translators: %1$s: Number of forms */
1938 $message = sprintf( _n( '%1$s form permanently deleted.', '%1$s forms permanently deleted.', $count, 'formidable' ), $count );
1939 self::display_forms_list( array(), $message, '' );
1940 return;
1941 }
1942
1943 self::display_forms_list();
1944
1945 return;
1946 }//end switch
1947 }
1948
1949 /**
1950 * Rename a form.
1951 *
1952 * Handles the AJAX request for renaming a form.
1953 *
1954 * @since 6.7
1955 *
1956 * @return void
1957 */
1958 public static function rename_form() {
1959 // Check permission and nonce
1960 FrmAppHelper::permission_check( 'frm_edit_forms' );
1961 check_ajax_referer( 'frm_ajax', 'nonce' );
1962
1963 // Get posted data
1964 $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' );
1965 $name = FrmAppHelper::get_post_param( 'form_name', '', 'sanitize_text_field' );
1966
1967 // Update the form name and form key.
1968 $form_key = FrmAppHelper::get_unique_key( sanitize_title( $name ), 'frm_forms', 'form_key' );
1969 FrmForm::update( $form_id, compact( 'name', 'form_key' ) );
1970
1971 wp_send_json_success( compact( 'form_key' ) );
1972 }
1973
1974 public static function json_error( $errors ) {
1975 $errors['json'] = __( 'Abnormal HTML characters prevented your form from saving correctly', 'formidable' );
1976
1977 return $errors;
1978 }
1979
1980 /**
1981 * Education for premium features.
1982 *
1983 * @since 4.05
1984 * @return void
1985 */
1986 public static function add_form_style_tab_options() {
1987 include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/add_form_style_options.php';
1988 }
1989
1990 /**
1991 * Add education about views.
1992 *
1993 * @since 4.07
1994 *
1995 * @return void
1996 */
1997 public static function no_views( $values = array() ) {
1998 FrmAppHelper::include_svg();
1999 $id = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
2000 $form = $id ? FrmForm::getOne( $id ) : false;
2001
2002 include FrmAppHelper::plugin_path() . '/classes/views/shared/views-info.php';
2003 }
2004
2005 /**
2006 * Add education about reports.
2007 *
2008 * @since 4.07
2009 *
2010 * @return void
2011 */
2012 public static function no_reports( $values = array() ) {
2013 $id = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
2014 $form = $id ? FrmForm::getOne( $id ) : false;
2015
2016 include FrmAppHelper::plugin_path() . '/classes/views/shared/reports-info.php';
2017 }
2018
2019 /**
2020 * FRONT-END FORMS.
2021 */
2022 public static function admin_bar_css() {
2023 if ( is_admin() || ! current_user_can( 'frm_edit_forms' ) ) {
2024 return;
2025 }
2026
2027 self::move_menu_to_footer();
2028
2029 add_action( 'wp_before_admin_bar_render', 'FrmFormsController::admin_bar_configure' );
2030 FrmAppHelper::load_font_style();
2031 }
2032
2033 /**
2034 * @since 4.05.02
2035 */
2036 private static function move_menu_to_footer() {
2037 $settings = FrmAppHelper::get_settings();
2038 if ( empty( $settings->admin_bar ) ) {
2039 remove_action( 'wp_body_open', 'wp_admin_bar_render', 0 );
2040 }
2041 }
2042
2043 public static function admin_bar_configure() {
2044 global $frm_vars;
2045 if ( empty( $frm_vars['forms_loaded'] ) ) {
2046 return;
2047 }
2048
2049 $actions = array();
2050 foreach ( $frm_vars['forms_loaded'] as $form ) {
2051 if ( is_object( $form ) ) {
2052 $actions[ $form->id ] = $form->name;
2053 }
2054 unset( $form );
2055 }
2056
2057 if ( empty( $actions ) ) {
2058 return;
2059 }
2060
2061 self::add_menu_to_admin_bar();
2062 self::add_forms_to_admin_bar( $actions );
2063 }
2064
2065 /**
2066 * @since 2.05.07
2067 */
2068 public static function add_menu_to_admin_bar() {
2069 global $wp_admin_bar;
2070
2071 $wp_admin_bar->add_node(
2072 array(
2073 'id' => 'frm-forms',
2074 'title' => '<span class="ab-icon"></span><span class="ab-label">' . FrmAppHelper::get_menu_name() . '</span>',
2075 'href' => admin_url( 'admin.php?page=formidable' ),
2076 'meta' => array(
2077 'title' => FrmAppHelper::get_menu_name(),
2078 ),
2079 )
2080 );
2081 }
2082
2083 /**
2084 * @since 2.05.07
2085 */
2086 private static function add_forms_to_admin_bar( $actions ) {
2087 global $wp_admin_bar;
2088
2089 asort( $actions );
2090
2091 foreach ( $actions as $form_id => $name ) {
2092
2093 $wp_admin_bar->add_node(
2094 array(
2095 'parent' => 'frm-forms',
2096 'id' => 'edit_form_' . $form_id,
2097 'title' => empty( $name ) ? FrmFormsHelper::get_no_title_text() : $name,
2098 'href' => FrmForm::get_edit_link( $form_id ),
2099 )
2100 );
2101 }
2102 }
2103
2104 /**
2105 * The formidable shortcode
2106 *
2107 * @param array $atts The params from the shortcode.
2108 * @return string
2109 */
2110 public static function get_form_shortcode( $atts ) {
2111 global $frm_vars;
2112 if ( ! empty( $frm_vars['skip_shortcode'] ) ) {
2113 $sc = '[formidable';
2114 $sc .= FrmAppHelper::array_to_html_params( $atts );
2115 return $sc . ']';
2116 }
2117
2118 $shortcode_atts = shortcode_atts(
2119 array(
2120 'id' => '',
2121 'key' => '',
2122 'title' => 'auto',
2123 'description' => 'auto',
2124 'readonly' => false,
2125 'entry_id' => false,
2126 'fields' => array(),
2127 'exclude_fields' => array(),
2128 'minimize' => false,
2129 ),
2130 $atts
2131 );
2132 do_action( 'formidable_shortcode_atts', $shortcode_atts, $atts );
2133
2134 return self::show_form( $shortcode_atts['id'], $shortcode_atts['key'], $shortcode_atts['title'], $shortcode_atts['description'], $atts );
2135 }
2136
2137 /**
2138 * @since 5.2.01
2139 *
2140 * @param false|int|string $id
2141 * @param false|string $key
2142 * @return false|stdClass
2143 */
2144 private static function maybe_get_form_by_id_or_key( $id, $key ) {
2145 if ( ! $id ) {
2146 $id = $key;
2147 }
2148 return self::maybe_get_form_to_show( $id );
2149 }
2150
2151 /**
2152 * @param false|int|string $id
2153 * @param false|string $key
2154 * @param bool|int|string $title may be 'auto', true, false, 'true', 'false', 'yes', '1', 1, '0', 0.
2155 * @param bool|int|string $description may be 'auto', true, false, 'true', 'false', 'yes', '1', 1, '0', 0.
2156 * @param array $atts
2157 * @return string
2158 */
2159 public static function show_form( $id = '', $key = '', $title = false, $description = false, $atts = array() ) {
2160 $form = self::maybe_get_form_by_id_or_key( $id, $key );
2161
2162 if ( ! $form ) {
2163 return __( 'Please select a valid form', 'formidable' );
2164 }
2165
2166 if ( 'auto' === $title ) {
2167 $title = ! empty( $form->options['show_title'] );
2168 }
2169
2170 if ( 'auto' === $description ) {
2171 $description = ! empty( $form->options['show_description'] );
2172 }
2173
2174 FrmAppController::maybe_update_styles();
2175
2176 add_action( 'frm_load_form_hooks', 'FrmHooksController::trigger_load_form_hooks' );
2177 FrmAppHelper::trigger_hook_load( 'form', $form );
2178
2179 $form = apply_filters( 'frm_pre_display_form', $form );
2180
2181 $frm_settings = FrmAppHelper::get_settings( array( 'current_form' => $form->id ) );
2182
2183 if ( self::is_viewable_draft_form( $form ) ) {
2184 // don't show a draft form on a page
2185 $form = __( 'Please select a valid form', 'formidable' );
2186 } elseif ( ! FrmForm::is_visible_to_user( $form ) ) {
2187 $form = do_shortcode( $frm_settings->login_msg );
2188 } else {
2189 do_action( 'frm_pre_get_form', $form );
2190 $form = self::get_form( $form, $title, $description, $atts );
2191
2192 /**
2193 * Use this shortcode to check for external shortcodes that may span
2194 * across multiple fields in the customizable HTML
2195 *
2196 * @since 2.0.8
2197 */
2198 $form = apply_filters( 'frm_filter_final_form', $form );
2199 }
2200
2201 return $form;
2202 }
2203
2204 /**
2205 * @param false|int|string $id
2206 * @return false|stdClass
2207 */
2208 private static function maybe_get_form_to_show( $id ) {
2209 $form = false;
2210
2211 if ( ! empty( $id ) ) {
2212 // Form id or key is set.
2213 $form = FrmForm::getOne( $id );
2214 if ( ! $form || $form->parent_form_id || $form->status === 'trash' ) {
2215 $form = false;
2216 }
2217 }
2218
2219 return $form;
2220 }
2221
2222 private static function is_viewable_draft_form( $form ) {
2223 return $form->status === 'draft' && current_user_can( 'frm_edit_forms' ) && ! FrmAppHelper::is_preview_page();
2224 }
2225
2226 public static function get_form( $form, $title, $description, $atts = array() ) {
2227 ob_start();
2228
2229 do_action( 'frm_before_get_form', $atts );
2230
2231 self::get_form_contents( $form, $title, $description, $atts );
2232 self::enqueue_scripts( FrmForm::get_params( $form ) );
2233
2234 $contents = ob_get_contents();
2235 ob_end_clean();
2236
2237 self::maybe_minimize_form( $atts, $contents );
2238
2239 return $contents;
2240 }
2241
2242 public static function enqueue_scripts( $params ) {
2243 do_action( 'frm_enqueue_form_scripts', $params );
2244 }
2245
2246 public static function get_form_contents( $form, $title, $description, $atts ) {
2247 $params = FrmForm::get_params( $form );
2248 $errors = self::get_saved_errors( $form, $params );
2249 $fields = FrmFieldsHelper::get_form_fields( $form->id, $errors );
2250 $reset = false;
2251 $pass_args = compact( 'form', 'fields', 'errors', 'title', 'description', 'reset' );
2252
2253 $pass_args['action'] = $params['action'];
2254 $handle_process_here = $params['action'] === 'create' && $params['posted_form_id'] == $form->id && $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing
2255
2256 if ( ! $handle_process_here ) {
2257 FrmFormState::set_initial_value( 'title', $title );
2258 FrmFormState::set_initial_value( 'description', $description );
2259
2260 do_action( 'frm_display_form_action', $params, $fields, $form, $title, $description );
2261 if ( apply_filters( 'frm_continue_to_new', true, $form->id, $params['action'] ) ) {
2262 self::show_form_after_submit( $pass_args );
2263 }
2264 } elseif ( ! empty( $errors ) ) {
2265 self::show_form_after_submit( $pass_args );
2266
2267 } else {
2268
2269 do_action( 'frm_validate_form_creation', $params, $fields, $form, $title, $description );
2270
2271 if ( apply_filters( 'frm_continue_to_create', true, $form->id ) ) {
2272 $entry_id = self::just_created_entry( $form->id );
2273 $pass_args['entry_id'] = $entry_id;
2274 $pass_args['reset'] = true;
2275
2276 self::run_on_submit_actions( $pass_args );
2277
2278 do_action(
2279 'frm_after_entry_processed',
2280 array(
2281 'entry_id' => $entry_id,
2282 'form' => $form,
2283 )
2284 );
2285 }
2286 }//end if
2287 }
2288
2289 /**
2290 * If the form was processed earlier (init), get the generated errors
2291 *
2292 * @since 2.05
2293 */
2294 private static function get_saved_errors( $form, $params ) {
2295 global $frm_vars;
2296
2297 if ( $params['posted_form_id'] == $form->id && $_POST && isset( $frm_vars['created_entries'][ $form->id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
2298 $errors = $frm_vars['created_entries'][ $form->id ]['errors'];
2299 } else {
2300 $errors = array();
2301 }
2302
2303 /**
2304 * Allows modifying the generated errors if the form was processed earlier.
2305 *
2306 * @since 5.2.03
2307 *
2308 * @param array $errors Errors data. Is empty array if no errors found.
2309 * @param array $params Form params. See {@see FrmForm::get_params()}.
2310 */
2311 return apply_filters( 'frm_saved_errors', $errors, $params );
2312 }
2313
2314 /**
2315 * @since 2.2.7
2316 */
2317 public static function just_created_entry( $form_id ) {
2318 global $frm_vars;
2319
2320 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;
2321 }
2322
2323 /**
2324 * Gets confirmation method.
2325 *
2326 * @since 3.0
2327 * @since 6.0 This method can return an array of met On Submit actions.
2328 *
2329 * @param array $atts {
2330 * Atts.
2331 *
2332 * @type object $form Form object.
2333 * @type int $entry_id Entry ID.
2334 * }
2335 * @return array|string
2336 */
2337 private static function get_confirmation_method( $atts ) {
2338 $action = FrmOnSubmitHelper::current_event( $atts );
2339 $opt = 'update' === $action ? 'edit_action' : 'success_action';
2340 $method = ! empty( $atts['form']->options[ $opt ] ) ? $atts['form']->options[ $opt ] : 'message';
2341
2342 if ( ! empty( $atts['entry_id'] ) ) {
2343 $met_actions = self::get_met_on_submit_actions( $atts, $action );
2344 if ( $met_actions ) {
2345 $method = $met_actions;
2346 }
2347 }
2348
2349 $method = apply_filters( 'frm_success_filter', $method, $atts['form'], $action );
2350
2351 if ( $method !== 'message' && ( ! $atts['entry_id'] || ! is_numeric( $atts['entry_id'] ) ) ) {
2352 $method = 'message';
2353 }
2354
2355 return $method;
2356 }
2357
2358 public static function maybe_trigger_redirect( $form, $params, $args ) {
2359 if ( ! isset( $params['id'] ) ) {
2360 global $frm_vars;
2361 $params['id'] = $frm_vars['created_entries'][ $form->id ]['entry_id'];
2362 }
2363
2364 $conf_method = self::get_confirmation_method(
2365 array(
2366 'form' => $form,
2367 'entry_id' => $params['id'],
2368 'action' => FrmOnSubmitHelper::current_event( $params ),
2369 )
2370 );
2371
2372 self::maybe_trigger_redirect_with_action( $conf_method, $form, $params, $args );
2373 }
2374
2375 /**
2376 * Maybe trigger redirect with the new Confirmation action.
2377 *
2378 * @since 6.1.1
2379 *
2380 * @param array|string $conf_method Array of confirmation actions or the action type string.
2381 * @param object $form Form object.
2382 * @param array $params See {@see FrmFormsController::maybe_trigger_redirect()}.
2383 * @param array $args See {@see FrmFormsController::maybe_trigger_redirect()}.
2384 */
2385 public static function maybe_trigger_redirect_with_action( $conf_method, $form, $params, $args ) {
2386 if ( is_array( $conf_method ) && 1 === count( $conf_method ) ) {
2387 if ( 'redirect' === FrmOnSubmitHelper::get_action_type( $conf_method[0] ) ) {
2388 $event = FrmOnSubmitHelper::current_event( $params );
2389 FrmOnSubmitHelper::populate_on_submit_data( $form->options, $conf_method[0], $event );
2390 $conf_method = 'redirect';
2391 }
2392 }
2393
2394 if ( 'redirect' === $conf_method ) {
2395 self::trigger_redirect( $form, $params, $args );
2396 }
2397 }
2398
2399 public static function trigger_redirect( $form, $params, $args ) {
2400 $success_args = array(
2401 'action' => $params['action'],
2402 'conf_method' => 'redirect',
2403 'form' => $form,
2404 'entry_id' => $params['id'],
2405 );
2406
2407 if ( isset( $args['ajax'] ) ) {
2408 $success_args['ajax'] = $args['ajax'];
2409 }
2410
2411 self::run_success_action( $success_args );
2412 }
2413
2414 /**
2415 * Used when the success action is not 'message'
2416 *
2417 * @since 2.05
2418 * @since 6.0 `$args['force_delay_redirect']` is added.
2419 *
2420 * @param array $args {
2421 * The args.
2422 *
2423 * @type string $conf_method The method.
2424 * @type object $form Form object.
2425 * @type int $entry_id Entry ID.
2426 * @type string $action The action event. Accepts `create` or `update`.
2427 * @type array $fields The array of fields.
2428 * @type int $force_delay_redirect Force to show the message before redirecting in case redirect method runs.
2429 * }
2430 */
2431 public static function run_success_action( $args ) {
2432 global $frm_vars;
2433 $extra_args = $args;
2434 unset( $extra_args['form'] );
2435
2436 do_action( 'frm_success_action', $args['conf_method'], $args['form'], $args['form']->options, $args['entry_id'], $extra_args );
2437
2438 $opt = ! isset( $args['action'] ) || $args['action'] === 'create' ? 'success' : 'edit';
2439
2440 $args['success_opt'] = $opt;
2441 $args['ajax'] = ! empty( $frm_vars['ajax'] );
2442
2443 if ( $args['conf_method'] === 'page' && is_numeric( $args['form']->options[ $opt . '_page_id' ] ) ) {
2444 self::load_page_after_submit( $args );
2445 } elseif ( $args['conf_method'] === 'redirect' ) {
2446 self::redirect_after_submit( $args );
2447 } else {
2448 self::show_message_after_save( $args );
2449 }
2450 }
2451
2452 /**
2453 * Gets met On Submit actions.
2454 *
2455 * @since 6.0
2456 *
2457 * @param array $args See {@see FrmFormsController::run_success_action()}.
2458 * @param string $event Form event. Default is `create`.
2459 * @return array Array of actions that meet the conditional logics.
2460 */
2461 public static function get_met_on_submit_actions( $args, $event = 'create' ) {
2462 if ( ! FrmOnSubmitHelper::form_has_migrated( $args['form'] ) ) {
2463 return array();
2464 }
2465
2466 // If a redirect action has already opened the URL in a new tab, we show the default message in the current tab.
2467 if ( ! empty( self::$redirected_in_new_tab[ $args['form']->id ] ) ) {
2468 return array( FrmOnSubmitHelper::get_fallback_action_after_open_in_new_tab( $event ) );
2469 }
2470
2471 $entry = FrmEntry::getOne( $args['entry_id'], true );
2472 $actions = FrmOnSubmitHelper::get_actions( $args['form']->id );
2473 $met_actions = array();
2474 $has_redirect = false;
2475
2476 foreach ( $actions as $action ) {
2477 if ( ! in_array( $event, $action->post_content['event'], true ) ) {
2478 continue;
2479 }
2480
2481 if ( FrmFormAction::action_conditions_met( $action, $entry ) ) {
2482 continue;
2483 }
2484
2485 $action_type = FrmOnSubmitHelper::get_action_type( $action );
2486
2487 if ( 'redirect' === $action_type ) {
2488 if ( $has_redirect ) {
2489 // Do not process because we run the first redirect action only.
2490 continue;
2491 }
2492 }
2493
2494 if ( ! self::is_valid_on_submit_action( $action, $args, $event ) ) {
2495 continue;
2496 }
2497
2498 if ( 'redirect' === $action_type ) {
2499 $has_redirect = true;
2500 }
2501
2502 $met_actions[] = $action;
2503 unset( $action );
2504 }//end foreach
2505
2506 $args['event'] = $event;
2507
2508 /**
2509 * Filters the On Submit actions that meet the conditional logics.
2510 *
2511 * @since 6.0
2512 *
2513 * @param array $met_actions Actions that meet the conditional logics.
2514 * @param array $args See {@see FrmFormsController::run_success_action()}. `$args['event']` is also added.
2515 */
2516 $met_actions = apply_filters( 'frm_get_met_on_submit_actions', $met_actions, $args );
2517
2518 if ( empty( $met_actions ) ) {
2519 $met_actions = array( FrmOnSubmitHelper::get_fallback_action( $event ) );
2520 }
2521
2522 return $met_actions;
2523 }
2524
2525 /**
2526 * Checks if a Confirmation action has the valid data.
2527 *
2528 * @since 6.1.2
2529 *
2530 * @param object $action Form action object.
2531 * @param array $args See {@see FrmFormsController::run_success_action()}.
2532 * @param string $event Form event. Default is `create`.
2533 * @return bool
2534 */
2535 private static function is_valid_on_submit_action( $action, $args, $event = 'create' ) {
2536 $action_type = FrmOnSubmitHelper::get_action_type( $action );
2537
2538 if ( 'redirect' === $action_type ) {
2539 // Run through frm_redirect_url filter. This is used for the valid action check.
2540 $action->post_content['success_url'] = apply_filters(
2541 'frm_redirect_url',
2542 $action->post_content['success_url'],
2543 $args['form'],
2544 $args + array( 'action' => $event )
2545 );
2546
2547 return ! empty( $action->post_content['success_url'] );
2548 }
2549
2550 if ( 'page' === $action_type ) {
2551 if ( empty( $action->post_content['success_page_id'] ) ) {
2552 return false;
2553 }
2554
2555 $page = get_post( $action->post_content['success_page_id'] );
2556 if ( ! $page || 'trash' === $page->post_status ) {
2557 return false;
2558 }
2559 }
2560
2561 return true;
2562 }
2563
2564 /**
2565 * Runs On Submit actions.
2566 *
2567 * @since 6.0
2568 *
2569 * @param array $args See inside {@see FrmFormsController::get_form_contents()} method.
2570 */
2571 public static function run_on_submit_actions( $args ) {
2572 $args['conf_method'] = self::get_confirmation_method(
2573 array(
2574 'form' => $args['form'],
2575 'entry_id' => $args['entry_id'],
2576 'action' => FrmOnSubmitHelper::current_event( $args ),
2577 )
2578 );
2579 if ( ! is_array( $args['conf_method'] ) ) {
2580 self::run_success_action( $args );
2581 return;
2582 }
2583
2584 // If conf_method is an array, run On Submit actions.
2585 if ( ! $args['conf_method'] ) {
2586 // Use default message.
2587 FrmOnSubmitHelper::populate_on_submit_data( $args['form']->options );
2588 self::run_success_action( $args );
2589 } elseif ( 1 === count( $args['conf_method'] ) ) {
2590 FrmOnSubmitHelper::populate_on_submit_data( $args['form']->options, reset( $args['conf_method'] ) );
2591 $args['conf_method'] = $args['form']->options['success_action'];
2592 self::run_success_action( $args );
2593 } else {
2594 self::run_multi_on_submit_actions( $args );
2595 }
2596 }
2597
2598 /**
2599 * Runs multiple success actions.
2600 *
2601 * @since 6.0
2602 *
2603 * @param array $args See {@see FrmFormsController::run_success_action()}.
2604 */
2605 public static function run_multi_on_submit_actions( $args ) {
2606 $redirect_action = null;
2607 foreach ( $args['conf_method'] as $action ) {
2608 if ( 'redirect' === FrmOnSubmitHelper::get_action_type( $action ) ) {
2609 // We catch the redirect action to run it last.
2610 $redirect_action = $action;
2611 continue;
2612 }
2613
2614 self::run_single_on_submit_action( $args, $action );
2615
2616 unset( $action );
2617 }
2618
2619 if ( $redirect_action ) {
2620 // Show script to delay the redirection.
2621 $args['force_delay_redirect'] = true;
2622 self::run_single_on_submit_action( $args, $redirect_action );
2623 }
2624 }
2625
2626 /**
2627 * Runs single On Submit action.
2628 *
2629 * @since 6.0
2630 *
2631 * @param array $args See {@see FrmFormsController::run_success_action()}.
2632 * @param object $action On Submit action object.
2633 */
2634 public static function run_single_on_submit_action( $args, $action ) {
2635 $new_args = self::get_run_success_action_args( $args, $action );
2636 self::run_success_action( $new_args );
2637 }
2638
2639 /**
2640 * Gets run_success_action() args from the On Submit action.
2641 *
2642 * @since 6.0
2643 *
2644 * @param array $args See {@see FrmFormsController::run_success_action()}.
2645 * @param object $action On Submit action object.
2646 * @return array
2647 */
2648 private static function get_run_success_action_args( $args, $action ) {
2649 $new_args = $args;
2650
2651 FrmOnSubmitHelper::populate_on_submit_data( $new_args['form']->options, $action, $args['action'] );
2652
2653 $opt = 'update' === $args['action'] ? 'edit_' : 'success_';
2654
2655 $new_args['conf_method'] = $new_args['form']->options[ $opt . 'action' ];
2656
2657 /**
2658 * Filters the run success action args.
2659 *
2660 * @since 6.0
2661 *
2662 * @param array $new_args The new args.
2663 * @param array $args The old args. See {@see FrmFormsController::run_success_action()}.
2664 * @param object $action On Submit action object.
2665 */
2666 return apply_filters( 'frm_get_run_success_action_args', $new_args, $args, $action );
2667 }
2668
2669 /**
2670 * @since 3.0
2671 */
2672 private static function load_page_after_submit( $args ) {
2673 global $post;
2674 $opt = $args['success_opt'];
2675 if ( ! $post || $args['form']->options[ $opt . '_page_id' ] != $post->ID ) {
2676 $page = get_post( $args['form']->options[ $opt . '_page_id' ] );
2677 $old_post = $post;
2678 $post = $page;
2679 $content = apply_filters( 'frm_content', $page->post_content, $args['form'], $args['entry_id'] );
2680
2681 // Fix the On Submit page content doesn't show when previewing In theme.
2682 $has_preview_filter = has_filter( 'the_content', 'FrmFormsController::preview_content' );
2683
2684 if ( $has_preview_filter ) {
2685 remove_filter( 'the_content', 'FrmFormsController::preview_content', 9999 );
2686 }
2687
2688 echo apply_filters( 'the_content', $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2689
2690 if ( $has_preview_filter ) {
2691 add_filter( 'the_content', 'FrmFormsController::preview_content', 9999 );
2692 }
2693
2694 $post = $old_post;
2695 }//end if
2696 }
2697
2698 /**
2699 * @since 3.0
2700 * @param array $args See {@see FrmFormsController::run_success_action()}.
2701 */
2702 private static function redirect_after_submit( $args ) {
2703 add_filter( 'frm_use_wpautop', '__return_false' );
2704
2705 $opt = $args['success_opt'];
2706 $success_url = trim( $args['form']->options[ $opt . '_url' ] );
2707 $success_url = apply_filters( 'frm_content', $success_url, $args['form'], $args['entry_id'] );
2708 $success_url = do_shortcode( $success_url );
2709
2710 $args['id'] = $args['entry_id'];
2711 FrmEntriesController::delete_entry_before_redirect( $success_url, $args['form'], $args );
2712
2713 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
2714 $success_url = apply_filters( 'frm_redirect_url', $success_url, $args['form'], $args );
2715
2716 $doing_ajax = FrmAppHelper::doing_ajax();
2717
2718 if ( ! empty( $args['ajax'] ) && $doing_ajax && empty( $args['force_delay_redirect'] ) ) {
2719 // Is AJAX submit and there is just one Redirect action runs.
2720 echo json_encode( self::get_ajax_redirect_response_data( $args + compact( 'success_url' ) ) );
2721 wp_die();
2722 }
2723
2724 if ( ! headers_sent() && empty( $args['force_delay_redirect'] ) ) {
2725 // Not AJAX submit, no headers sent, and there is just one Redirect action runs.
2726 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2727 self::print_open_in_new_tab_js_with_fallback_handler( $success_url, $args );
2728 self::$redirected_in_new_tab[ $args['form']->id ] = 1;
2729 return;
2730 }
2731
2732 wp_redirect( esc_url_raw( $success_url ) );
2733 // Do not use wp_die or redirect fails.
2734 die();
2735 }
2736
2737 // Redirect with a delay.
2738 self::redirect_after_submit_using_js( $args + compact( 'success_url', 'doing_ajax' ) );
2739 }
2740
2741 /**
2742 * Prints open in new tab js with fallback handler.
2743 *
2744 * @since 6.3.1
2745 *
2746 * @param string $success_url Success URL.
2747 * @param array $args See {@see FrmFormsController::redirect_after_submit()}.
2748 */
2749 private static function print_open_in_new_tab_js_with_fallback_handler( $success_url, $args ) {
2750 echo '<script>var newTab = window.open("' . esc_url_raw( $success_url ) . '", "_blank");';
2751 echo 'if ( ! newTab ) {';
2752
2753 $data = array(
2754 'formId' => intval( $args['form']->id ),
2755 'message' => self::get_redirect_fallback_message( $success_url, $args ),
2756 );
2757 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2758 echo 'frmShowNewTabFallback = ' . FrmAppHelper::maybe_json_encode( $data ) . ';';
2759 echo '}</script>';
2760 }
2761
2762 /**
2763 * Gets response data for redirect action when AJAX submitting.
2764 *
2765 * @since 6.3.1
2766 *
2767 * @param array $args See {@see FrmFormsController::run_success_action()}.
2768 * @return array
2769 */
2770 private static function get_ajax_redirect_response_data( $args ) {
2771 $response_data = array( 'redirect' => $args['success_url'] );
2772
2773 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2774 $response_data['openInNewTab'] = 1;
2775
2776 $args['message'] = FrmOnSubmitHelper::get_default_new_tab_msg();
2777
2778 $args['form']->options['success_msg'] = $args['message'];
2779 $args['form']->options['edit_msg'] = $args['message'];
2780 if ( ! isset( $args['fields'] ) ) {
2781 $args['fields'] = FrmField::get_all_for_form( $args['form']->id );
2782 }
2783
2784 $args['message'] = self::prepare_submit_message( $args['form'], $args['entry_id'], $args );
2785
2786 ob_start();
2787 self::show_lone_success_message( $args );
2788 $response_data['content'] = ob_get_clean();
2789
2790 $response_data['fallbackMsg'] = self::get_redirect_fallback_message( $args['success_url'], $args );
2791 }
2792
2793 return $response_data;
2794 }
2795
2796 /**
2797 * Redirects after submitting using JS. This is used when showing message before redirecting.
2798 *
2799 * @since 6.0
2800 *
2801 * @param array $args See {@see FrmFormsController::run_success_action()}.
2802 */
2803 private static function redirect_after_submit_using_js( $args ) {
2804 $success_msg = FrmOnSubmitHelper::get_default_redirect_msg();
2805 $redirect_msg = self::get_redirect_message( $args['success_url'], $success_msg, $args );
2806 $success_url = esc_url_raw( $args['success_url'] );
2807
2808 /**
2809 * Filters the delay time before redirecting when On Submit Redirect action is delayed.
2810 *
2811 * @since 6.0
2812 *
2813 * @param int $delay_time Delay time in milliseconds.
2814 */
2815 $delay_time = apply_filters( 'frm_redirect_delay_time', 8000 );
2816
2817 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2818 $redirect_js = 'window.open("' . $success_url . '", "_blank")';
2819 } else {
2820 $redirect_js = 'window.location="' . $success_url . '";';
2821 }
2822
2823 add_filter( 'frm_use_wpautop', '__return_true' );
2824
2825 echo FrmAppHelper::maybe_kses( $redirect_msg ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2826 echo '<script>';
2827 if ( empty( $args['doing_ajax'] ) ) {
2828 // Not AJAX submit, delay JS until window.load.
2829 echo 'window.onload=function(){';
2830 }
2831 echo 'setTimeout(function(){' . $redirect_js . '}, ' . intval( $delay_time ) . ');'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2832 if ( empty( $args['doing_ajax'] ) ) {
2833 echo '};';
2834 }
2835 echo '</script>';
2836 }
2837
2838 /**
2839 * @since 3.0
2840 *
2841 * @param string $success_url
2842 * @param string $success_msg
2843 * @param array $args
2844 */
2845 private static function get_redirect_message( $success_url, $success_msg, $args ) {
2846 $redirect_msg = '<div class="' . esc_attr( FrmFormsHelper::get_form_style_class( $args['form'] ) ) . '"><div class="frm-redirect-msg" role="status">' . $success_msg . '<br/>' .
2847 self::get_redirect_fallback_message( $success_url, $args ) .
2848 '</div></div>';
2849
2850 $redirect_args = array(
2851 'entry_id' => $args['entry_id'],
2852 'form_id' => $args['form']->id,
2853 'form' => $args['form'],
2854 );
2855
2856 return apply_filters( 'frm_redirect_msg', $redirect_msg, $redirect_args );
2857 }
2858
2859 /**
2860 * Gets fallback message when redirecting failed.
2861 *
2862 * @since 6.3.1
2863 *
2864 * @param string $success_url Redirect URL.
2865 * @param array $args Contains `form` object.
2866 * @return string
2867 */
2868 private static function get_redirect_fallback_message( $success_url, $args ) {
2869 $target = '';
2870 if ( ! empty( $args['form']->options['open_in_new_tab'] ) ) {
2871 $target = ' target="_blank"';
2872 }
2873
2874 return sprintf(
2875 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
2876 __( '%1$sClick here%2$s if you are not automatically redirected.', 'formidable' ),
2877 '<a href="' . esc_url( $success_url ) . '"' . $target . '>',
2878 '</a>'
2879 );
2880 }
2881
2882 /**
2883 * Prepare to show the success message and empty form after submit
2884 *
2885 * @since 2.05
2886 */
2887 public static function show_message_after_save( $atts ) {
2888 $atts['message'] = self::prepare_submit_message( $atts['form'], $atts['entry_id'], $atts );
2889
2890 if ( ! isset( $atts['form']->options['show_form'] ) || $atts['form']->options['show_form'] ) {
2891 if ( isset( $atts['action'] ) && 'update' === $atts['action'] && is_callable( array( 'FrmProEntriesController', 'show_front_end_form_with_entry' ) ) ) {
2892 $entry = FrmEntry::getOne( $atts['entry_id'] );
2893 if ( $entry ) {
2894 // This is copied from the Pro plugin.
2895 $atts['conf_message'] = FrmProEntriesController::confirmation( 'message', $atts['form'], $atts['form']->options, $entry->id, $atts );
2896 $atts['show_form'] = FrmProEntriesController::is_form_displayed_after_edit( $atts['form'] );
2897 FrmProEntriesController::show_front_end_form_with_entry( $entry, $atts );
2898 }
2899 } else {
2900 self::show_form_after_submit( $atts );
2901 }
2902 } else {
2903 self::show_lone_success_message( $atts );
2904 }
2905 }
2906
2907 /**
2908 * Show an empty form
2909 *
2910 * @since 2.05
2911 */
2912 private static function show_form_after_submit( $args ) {
2913 self::fill_atts_for_form_display( $args );
2914
2915 $errors = $args['errors'];
2916 $message = $args['message'];
2917 $form = $args['form'];
2918 $title = $args['title'];
2919 $description = $args['description'];
2920
2921 if ( empty( $args['fields'] ) ) {
2922 $values = array(
2923 'custom_style' => FrmAppHelper::custom_style_value( array() ),
2924 );
2925 } else {
2926 $values = FrmEntriesHelper::setup_new_vars( $args['fields'], $form, $args['reset'] );
2927 }
2928 unset( $args );
2929
2930 $include_form_tag = apply_filters( 'frm_include_form_tag', true, $form );
2931
2932 $frm_settings = FrmAppHelper::get_settings();
2933 $submit = isset( $form->options['submit_value'] ) ? $form->options['submit_value'] : $frm_settings->submit_value;
2934
2935 global $frm_vars;
2936 self::maybe_load_css( $form, $values['custom_style'], $frm_vars['load_css'] );
2937
2938 $message_placement = self::message_placement( $form, $message );
2939
2940 include FrmAppHelper::plugin_path() . '/classes/views/frm-entries/new.php';
2941 }
2942
2943 /**
2944 * @since 4.05.02
2945 * @return string - 'before', 'after', or 'submit'
2946 */
2947 private static function message_placement( $form, $message ) {
2948 $place = 'before';
2949
2950 if ( $message && isset( $form->options['form_class'] ) ) {
2951 if ( strpos( $form->options['form_class'], 'frm_below_success' ) !== false ) {
2952 $place = 'after';
2953 } elseif ( strpos( $form->options['form_class'], 'frm_inline_success' ) !== false ) {
2954 $place = 'submit';
2955 }
2956 }
2957
2958 /**
2959 * @since 4.05.02
2960 * @return string - 'before' or 'after'
2961 */
2962 return apply_filters( 'frm_message_placement', $place, compact( 'form', 'message' ) );
2963 }
2964
2965 /**
2966 * Get all the values needed on the new.php entry page
2967 *
2968 * @since 2.05
2969 */
2970 private static function fill_atts_for_form_display( &$args ) {
2971 if ( ! isset( $args['title'] ) && isset( $args['show_title'] ) ) {
2972 $args['title'] = $args['show_title'];
2973 }
2974
2975 if ( ! isset( $args['description'] ) && isset( $args['show_description'] ) ) {
2976 $args['description'] = $args['show_description'];
2977 }
2978
2979 $defaults = array(
2980 'errors' => array(),
2981 'message' => '',
2982 'fields' => array(),
2983 'form' => array(),
2984 'title' => true,
2985 'description' => false,
2986 'reset' => false,
2987 );
2988 $args = wp_parse_args( $args, $defaults );
2989 }
2990
2991 /**
2992 * Show the success message without the form
2993 *
2994 * @since 2.05
2995 */
2996 private static function show_lone_success_message( $atts ) {
2997 global $frm_vars;
2998 $values = FrmEntriesHelper::setup_new_vars( $atts['fields'], $atts['form'], true );
2999 self::maybe_load_css( $atts['form'], $values['custom_style'], $frm_vars['load_css'] );
3000
3001 $include_extra_container = 'frm_forms' . FrmFormsHelper::get_form_style_class( $values );
3002
3003 $errors = array();
3004 $form = $atts['form'];
3005 $message = $atts['message'];
3006
3007 include FrmAppHelper::plugin_path() . '/classes/views/frm-entries/errors.php';
3008 }
3009
3010 /**
3011 * Prepare the success message before it's shown
3012 *
3013 * @since 2.05
3014 * @since 6.0.x Added the third parameter.
3015 *
3016 * @param object $form Form object.
3017 * @param int $entry_id Entry ID.
3018 * @param array $args See {@see FrmFormsController::run_success_action()}.
3019 * @return string
3020 */
3021 private static function prepare_submit_message( $form, $entry_id, $args = array() ) {
3022 $frm_settings = FrmAppHelper::get_settings( array( 'current_form' => $form->id ) );
3023 $opt = isset( $args['success_opt'] ) ? $args['success_opt'] : 'success';
3024
3025 if ( $entry_id && is_numeric( $entry_id ) ) {
3026 $message = isset( $form->options[ $opt . '_msg' ] ) ? $form->options[ $opt . '_msg' ] : $frm_settings->success_msg;
3027 $class = 'frm_message';
3028 } else {
3029 $message = $frm_settings->failed_msg;
3030 $class = FrmFormsHelper::form_error_class();
3031 }
3032
3033 $message = FrmFormsHelper::get_success_message( compact( 'message', 'form', 'entry_id', 'class' ) );
3034
3035 return apply_filters( 'frm_main_feedback', $message, $form, $entry_id );
3036 }
3037
3038 /**
3039 * @return void
3040 */
3041 public static function front_head() {
3042 $version = FrmAppHelper::plugin_version();
3043 $suffix = FrmAppHelper::js_suffix();
3044
3045 if ( ! empty( $suffix ) && self::has_combo_js_file() ) {
3046 wp_register_script( 'formidable', FrmAppHelper::plugin_url() . '/js/frm.min.js', array( 'jquery' ), $version, true );
3047 } else {
3048 wp_register_script( 'formidable', FrmAppHelper::plugin_url() . "/js/formidable{$suffix}.js", array( 'jquery' ), $version, true );
3049 }
3050
3051 add_filter( 'script_loader_tag', 'FrmFormsController::defer_script_loading', 10, 2 );
3052
3053 if ( FrmAppHelper::is_admin() ) {
3054 // don't load this in back-end
3055 return;
3056 }
3057
3058 FrmAppHelper::localize_script( 'front' );
3059 FrmStylesController::enqueue_css( 'register' );
3060 }
3061
3062 /**
3063 * @since 3.0
3064 */
3065 public static function has_combo_js_file() {
3066 return is_readable( FrmAppHelper::plugin_path() . '/js/frm.min.js' );
3067 }
3068
3069 public static function maybe_load_css( $form, $this_load, $global_load ) {
3070 $load_css = FrmForm::is_form_loaded( $form, $this_load, $global_load );
3071
3072 if ( ! $load_css ) {
3073 return;
3074 }
3075
3076 global $frm_vars;
3077 self::footer_js( 'header' );
3078 $frm_vars['css_loaded'] = true;
3079
3080 self::load_late_css();
3081 }
3082
3083 /**
3084 * If css is loaded only on applicable pages, include it before the form loads
3085 * to prevent a flash of unstyled form.
3086 *
3087 * @since 4.01
3088 *
3089 * @return void
3090 */
3091 private static function load_late_css() {
3092 $frm_settings = FrmAppHelper::get_settings();
3093 $late_css = $frm_settings->load_style === 'dynamic';
3094
3095 if ( ! $late_css || ! self::should_load_late() ) {
3096 return;
3097 }
3098
3099 global $wp_styles;
3100 if ( is_array( $wp_styles->queue ) && in_array( 'formidable', $wp_styles->queue, true ) ) {
3101 wp_print_styles( 'formidable' );
3102 }
3103 }
3104
3105 /**
3106 * Avoid late load if All in One SEO is active because it prevents CSS from loading entirely.
3107 *
3108 * @since 5.2.03
3109 *
3110 * @return bool
3111 */
3112 private static function should_load_late() {
3113 return ! function_exists( 'aioseo' );
3114 }
3115
3116 public static function defer_script_loading( $tag, $handle ) {
3117 if ( 'captcha-api' == $handle && ! strpos( $tag, 'defer' ) ) {
3118 $tag = str_replace( ' src', ' defer="defer" async="async" src', $tag );
3119 }
3120
3121 return $tag;
3122 }
3123
3124 public static function footer_js( $location = 'footer' ) {
3125 global $frm_vars;
3126
3127 FrmStylesController::enqueue_css();
3128
3129 if ( ! FrmAppHelper::is_admin() && $location !== 'header' && ! empty( $frm_vars['forms_loaded'] ) ) {
3130 // load formidable js
3131 wp_enqueue_script( 'formidable' );
3132 }
3133 }
3134
3135 /**
3136 * @since 2.0.8
3137 */
3138 private static function maybe_minimize_form( $atts, &$content ) {
3139 // check if minimizing is turned on
3140 if ( self::is_minification_on( $atts ) ) {
3141 $content = str_replace( array( "\r\n", "\r", "\n", "\t", ' ' ), '', $content );
3142 }
3143 }
3144
3145 /**
3146 * @since 2.0.8
3147 * @return bool
3148 */
3149 private static function is_minification_on( $atts ) {
3150 return ! empty( $atts['minimize'] );
3151 }
3152
3153 /**
3154 * @since 5.0.16
3155 *
3156 * @return void
3157 */
3158 public static function landing_page_preview_option() {
3159 $dir = apply_filters( 'frm_landing_page_preview_option', false );
3160 if ( false === $dir || ! file_exists( $dir . 'landing-page-preview-option.php' ) ) {
3161 $dir = self::get_form_views_path();
3162 }
3163 include $dir . 'landing-page-preview-option.php';
3164 }
3165
3166 /**
3167 * @since 5.0.16
3168 *
3169 * @return string
3170 */
3171 private static function get_form_views_path() {
3172 return FrmAppHelper::plugin_path() . '/classes/views/frm-forms/';
3173 }
3174
3175 /**
3176 * Create a page with an embedded formidable Gutenberg block.
3177 *
3178 * @since 5.2
3179 *
3180 * @return void
3181 */
3182 public static function create_page_with_shortcode() {
3183 if ( ! current_user_can( 'publish_posts' ) ) {
3184 die( 0 );
3185 }
3186
3187 check_ajax_referer( 'frm_ajax', 'nonce' );
3188
3189 $type = FrmAppHelper::get_post_param( 'type', '', 'sanitize_text_field' );
3190 if ( ! $type || ! in_array( $type, array( 'form', 'view' ), true ) ) {
3191 die( 0 );
3192 }
3193
3194 $object_id = FrmAppHelper::get_post_param( 'object_id', '', 'absint' );
3195 if ( ! $object_id ) {
3196 die( 0 );
3197 }
3198
3199 $postarr = array( 'post_type' => 'page' );
3200
3201 if ( 'form' === $type ) {
3202 $postarr['post_content'] = self::get_page_shortcode_content_for_form( $object_id );
3203 } else {
3204 $postarr['post_content'] = apply_filters( 'frm_create_page_with_' . $type . '_shortcode_content', '', $object_id );
3205 }
3206
3207 $name = FrmAppHelper::get_post_param( 'name', '', 'sanitize_text_field' );
3208 if ( $name ) {
3209 $postarr['post_title'] = $name;
3210 }
3211
3212 $success = wp_insert_post( $postarr );
3213 if ( ! is_numeric( $success ) || ! $success ) {
3214 die( 0 );
3215 }
3216
3217 wp_send_json(
3218 array(
3219 'redirect' => get_edit_post_link( $success, 'redirect' ),
3220 )
3221 );
3222 }
3223
3224 /**
3225 * @since 5.3
3226 *
3227 * @param int $form_id
3228 * @return string
3229 */
3230 private static function get_page_shortcode_content_for_form( $form_id ) {
3231 $shortcode = '[formidable id="' . $form_id . '"]';
3232 $html_comment_start = '<!-- wp:formidable/simple-form {"formId":"' . $form_id . '"} -->';
3233 $html_comment_end = '<!-- /wp:formidable/simple-form -->';
3234 return $html_comment_start . '<div>' . $shortcode . '</div>' . $html_comment_end;
3235 }
3236
3237 /**
3238 * Get page dropdown for AJAX request for embedding form in an existing page.
3239 *
3240 * @return void
3241 */
3242 public static function get_page_dropdown() {
3243 if ( ! current_user_can( 'publish_posts' ) ) {
3244 die( 0 );
3245 }
3246
3247 check_ajax_referer( 'frm_ajax', 'nonce' );
3248
3249 $html = FrmAppHelper::clip(
3250 function () {
3251 FrmAppHelper::maybe_autocomplete_pages_options(
3252 array(
3253 'field_name' => 'frm_page_dropdown',
3254 'page_id' => '',
3255 'placeholder' => __( 'Select a Page', 'formidable' ),
3256 )
3257 );
3258 }
3259 );
3260 $post_type_object = get_post_type_object( 'page' );
3261 wp_send_json(
3262 array(
3263 'html' => $html,
3264 'edit_page_url' => admin_url( sprintf( $post_type_object->_edit_link . '&action=edit', 0 ) ),
3265 )
3266 );
3267 }
3268
3269 /**
3270 * @deprecated 4.0
3271 */
3272 public static function create( $values = array() ) {
3273 _deprecated_function( __METHOD__, '4.0', 'FrmFormsController::update' );
3274 self::update( $values );
3275 }
3276 }
3277