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

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