PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.16.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.16.1
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / controllers / FrmFormsController.php

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

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