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

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