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

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