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

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