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

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