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

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