PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.30
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.30
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / controllers / FrmFormsController.php

FrmFormsController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.30, at classes/controllers/FrmFormsController.php

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