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

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