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

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