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

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

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