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

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