PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.27
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.27
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 / helpers / FrmFormsHelper.php

FrmFormsHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.27, at classes/helpers/FrmFormsHelper.php

2,136 lines 59.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmFormsHelper {
7
8 /**
9 * Store and re-use field type data for the insert_opt_html function (to avoid multiple calls to FrmField::all_field_selection).
10 *
11 * @since 6.10
12 *
13 * @var array|null
14 */
15 private static $field_type_data_for_insert_opt_html;
16
17 /**
18 * @since 2.2.10
19 *
20 * @return string
21 */
22 public static function form_error_class() {
23 return apply_filters( 'frm_form_error_class', 'frm_error_style' );
24 }
25
26 /**
27 * @param string $key
28 * @param false|object $form
29 *
30 * @return string
31 */
32 public static function get_direct_link( $key, $form = false ) {
33 $target_url = esc_url( admin_url( 'admin-ajax.php?action=frm_forms_preview&form=' . $key ) );
34 return apply_filters( 'frm_direct_link', $target_url, $key, $form );
35 }
36
37 /**
38 * @param string $field_name
39 * @param int|string $field_value
40 * @param array $args
41 *
42 * @return void
43 */
44 public static function forms_dropdown( $field_name, $field_value = '', $args = array() ) {
45 $defaults = array(
46 'blank' => true,
47 'field_id' => false,
48 'onchange' => false,
49 'exclude' => false,
50 'class' => '',
51 'inc_children' => 'exclude',
52 );
53 $args = wp_parse_args( $args, $defaults );
54
55 if ( ! $args['field_id'] ) {
56 $args['field_id'] = $field_name;
57 }
58
59 $query = array();
60
61 if ( $args['exclude'] ) {
62 $query['id !'] = $args['exclude'];
63 }
64
65 $where = apply_filters( 'frm_forms_dropdown', $query, $field_name );
66 $forms = FrmForm::get_published_forms( $where, 999, $args['inc_children'] );
67 $add_html = array();
68 self::add_html_attr( $args['onchange'], 'onchange', $add_html );
69 self::add_html_attr( $args['class'], 'class', $add_html );
70
71 ?>
72 <select name="<?php echo esc_attr( $field_name ); ?>"
73 id="<?php echo esc_attr( $args['field_id'] ); ?>"
74 <?php echo wp_strip_all_tags( implode( ' ', $add_html ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
75 <?php if ( $args['blank'] ) { ?>
76 <option value=""><?php echo $args['blank'] == 1 ? ' ' : '- ' . esc_attr( $args['blank'] ) . ' -'; // phpcs:ignore Universal.Operators.StrictComparisons ?></option>
77 <?php } ?>
78 <?php foreach ( $forms as $form ) { ?>
79 <option value="<?php echo esc_attr( $form->id ); ?>" <?php selected( $field_value, $form->id ); ?>>
80 <?php echo esc_html( '' === $form->name ? self::get_no_title_text() : FrmAppHelper::truncate( $form->name, 50 ) . ( $form->parent_form_id ? __( ' (child)', 'formidable' ) : '' ) ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
81 </option>
82 <?php } ?>
83 </select>
84 <?php
85 }
86
87 /**
88 * @since 2.0.6
89 *
90 * @param string $class
91 * @param string $param
92 * @param array $add_html
93 *
94 * @return void
95 */
96 public static function add_html_attr( $class, $param, &$add_html ) {
97 if ( $class ) {
98 $add_html[ $param ] = sanitize_title( $param ) . '="' . esc_attr( trim( sanitize_text_field( $class ) ) ) . '"';
99 }
100 }
101
102 /**
103 * @param false|object|string $selected - The label for the placeholder, or the form object.
104 *
105 * @return void
106 */
107 public static function form_switcher( $selected = false ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded, SlevomatCodingStandard.Files.LineLength.LineTooLong
108 $where = apply_filters( 'frm_forms_dropdown', array(), '' );
109 $forms = FrmForm::get_published_forms( $where );
110
111 $args = array(
112 'id' => 0,
113 'form' => 0,
114 );
115
116 if ( isset( $_GET['id'] ) && ! isset( $_GET['form'] ) ) {
117 unset( $args['form'] );
118 } elseif ( isset( $_GET['form'] ) && ! isset( $_GET['id'] ) ) {
119 unset( $args['id'] );
120 }
121
122 $frm_action = FrmAppHelper::simple_get( 'frm_action', 'sanitize_title' );
123
124 if ( FrmAppHelper::is_admin_page( 'formidable-entries' ) && in_array( $frm_action, array( 'edit', 'show', 'destroy', 'destroy_all' ), true ) ) {
125 $args['frm_action'] = 'list';
126 $args['form'] = 0;
127 } elseif ( FrmAppHelper::is_admin_page( 'formidable' ) && in_array( $frm_action, array( 'new', 'duplicate' ), true ) ) {
128 $args['frm_action'] = 'edit';
129 } elseif ( FrmAppHelper::is_style_editor_page() ) {
130 // Avoid passing style into form switcher on style page.
131 unset( $args['id'] );
132 $query_args = array(
133 'page' => 'formidable-styles',
134 );
135
136 if ( $frm_action ) {
137 $query_args['frm_action'] = $frm_action;
138 }
139
140 $base = add_query_arg( $query_args, admin_url( 'admin.php' ) );
141 } elseif ( isset( $_GET['post'] ) ) {
142 $args['form'] = 0;
143 $base = admin_url( 'edit.php?post_type=frm_display' );
144 }//end if
145
146 $form_id = 0;
147
148 if ( is_object( $selected ) ) {
149 $form_id = $selected->id;
150 $selected = $selected->name;
151 }
152
153 $name = $selected === false ? __( 'Switch Form', 'formidable' ) : $selected;
154 $name = '' === $name || is_null( $name ) ? self::get_no_title_text() : strip_tags( $name );
155 $truncated_name = FrmAppHelper::truncate( $name, 25 );
156
157 if ( count( $forms ) < 2 ) {
158 ?>
159 <div id="frm_bs_dropdown">
160 <h1>
161 <span class="frm_bstooltip" title="<?php echo esc_attr( $truncated_name === $name ? '' : $name ); ?>" data-placement="right">
162 <?php echo esc_html( $name ); ?>
163 </span>
164 </h1>
165 </div>
166 <?php
167 return;
168 }
169 ?>
170 <div id="frm_bs_dropdown" class="dropdown <?php echo esc_attr( is_rtl() ? 'dropdown-menu-right' : 'dropdown-menu-left' ); ?>">
171 <a href="#" id="frm-navbarDrop" class="frm-dropdown-toggle" data-toggle="dropdown">
172 <h1>
173 <span class="frm_bstooltip" title="<?php echo esc_attr( $truncated_name === $name ? '' : $name ); ?>" data-placement="right">
174 <?php echo esc_html( $name ); ?>
175 </span>
176 <?php FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown6_icon', array( 'aria-hidden' => 'true' ) ); ?>
177 </h1>
178 </a>
179 <ul class="frm-dropdown-menu frm-on-top frm-inline-modal frm_code_list frm-full-hover" role="menu" aria-labelledby="frm-navbarDrop">
180 <?php if ( count( $forms ) > 8 ) { ?>
181 <li class="frm-with-search">
182 <?php
183 FrmAppHelper::show_search_box(
184 array(
185 'input_id' => 'dropform',
186 'placeholder' => __( 'Search Forms', 'formidable' ),
187 'tosearch' => 'frm-dropdown-form',
188 // Specify a value to avoid the $_REQUEST['s'] default value.
189 'value' => '',
190 )
191 );
192 ?>
193 </li>
194 <?php } ?>
195 <?php
196 foreach ( $forms as $form ) {
197 if ( $form->id === $form_id ) {
198 // Don't include the selected form in the switcher since it does nothing.
199 continue;
200 }
201
202 if ( isset( $args['id'] ) ) {
203 $args['id'] = $form->id;
204 }
205
206 if ( isset( $args['form'] ) ) {
207 $args['form'] = $form->id;
208 }
209
210 $url = isset( $base ) ? add_query_arg( $args, $base ) : add_query_arg( $args );
211 $form_name = empty( $form->name ) ? self::get_no_title_text() : $form->name;
212 ?>
213 <li class="frm-dropdown-form">
214 <a href="<?php echo esc_url( $url ); ?>" tabindex="-1" class="frm-justify-between">
215 <?php echo esc_html( $form_name ); ?>
216 <span>
217 <?php
218 printf(
219 /* translators: %d: Form ID */
220 esc_html__( '(ID %d)', 'formidable' ),
221 esc_attr( $form->id )
222 );
223 ?>
224 </span>
225 <span class="frm_hidden"><?php echo esc_html( $form->form_key ); ?></span>
226 </a>
227 </li>
228 <?php
229 unset( $form );
230 }//end foreach
231 ?>
232 </ul>
233 </div>
234 <?php
235 }
236
237 /**
238 * @param string $col
239 * @param string $sort_col
240 * @param string $sort_dir
241 *
242 * @return void
243 */
244 public static function get_sortable_classes( $col, $sort_col, $sort_dir ) {
245 echo $sort_col == $col ? 'sorted' : 'sortable'; // phpcs:ignore Universal.Operators.StrictComparisons
246 echo $sort_col == $col && $sort_dir === 'desc' ? ' asc' : ' desc'; // phpcs:ignore Universal.Operators.StrictComparisons
247 }
248
249 /**
250 * @since 3.0
251 *
252 * @param array|string $field_type
253 *
254 * @return string
255 */
256 public static function get_field_link_name( $field_type ) {
257 return is_array( $field_type ) ? $field_type['name'] : $field_type;
258 }
259
260 /**
261 * @since 3.0
262 *
263 * @param array|string $field_type
264 *
265 * @return string
266 */
267 public static function get_field_link_icon( $field_type ) {
268 return is_array( $field_type ) && isset( $field_type['icon'] ) ? $field_type['icon'] : 'frmfont frm_pencil_icon';
269 }
270
271 /**
272 * Get the invalid form error message
273 *
274 * @since 2.02.07
275 *
276 * @param array $args
277 *
278 * @return string
279 */
280 public static function get_invalid_error_message( $args ) {
281 $settings_args = $args;
282
283 if ( isset( $args['form'] ) ) {
284 $settings_args['current_form'] = $args['form']->id;
285 }
286
287 $frm_settings = FrmAppHelper::get_settings( $settings_args );
288 $invalid_msg = do_shortcode( $frm_settings->invalid_msg );
289 return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args );
290 }
291
292 /**
293 * @param array $atts {
294 * The success message details.
295 *
296 * @type string $message
297 * @type stdClass $form
298 * @type int $entry_id
299 * @type string $class
300 * }
301 *
302 * @return string
303 */
304 public static function get_success_message( $atts ) {
305 $message = apply_filters( 'frm_content', $atts['message'], $atts['form'], $atts['entry_id'] );
306
307 // Only autop if the message includes line breaks.
308 $autop = str_contains( $message, "\n" );
309
310 /**
311 * Filters whether to autop the success message.
312 * This is false by default if the message does not include line breaks.
313 *
314 * @since 6.23
315 *
316 * @param bool $autop
317 * @param string $message
318 * @param object $form
319 */
320 $autop = (bool) apply_filters( 'frm_wpautop_success_message', $autop, $message, $atts['form'] );
321
322 if ( $autop ) {
323 $message = FrmAppHelper::use_wpautop( $message );
324 }
325
326 $message = do_shortcode( $message );
327 return '<div class="' . esc_attr( $atts['class'] ) . '" role="status">' . $message . '</div>';
328 }
329
330 /**
331 * Used when a form is created
332 *
333 * @param array $values
334 *
335 * @return array
336 */
337 public static function setup_new_vars( $values = array() ) {
338 global $wpdb;
339
340 if ( $values ) {
341 $post_values = $values;
342 } else {
343 $values = array();
344 $post_values = ! empty( $_POST ) ? $_POST : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
345 }
346
347 $defaults = array(
348 'name' => '',
349 'description' => '',
350 );
351
352 foreach ( $defaults as $var => $default ) {
353 if ( ! isset( $values[ $var ] ) ) {
354 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
355 }
356 }
357
358 $values['description'] = FrmAppHelper::use_wpautop( $values['description'] );
359
360 $defaults = array(
361 'form_id' => '',
362 'logged_in' => '',
363 'editable' => '',
364 'is_template' => 0,
365 'status' => 'published',
366 'parent_form_id' => 0,
367 );
368
369 foreach ( $defaults as $var => $default ) {
370 if ( ! isset( $values[ $var ] ) ) {
371 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
372 }
373 }
374 unset( $defaults );
375
376 if ( ! isset( $values['form_key'] ) ) {
377 $values['form_key'] = $post_values && isset( $post_values['form_key'] ) ? $post_values['form_key'] : FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_forms', 'form_key' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
378 }
379
380 $values = self::fill_default_opts( $values, false, $post_values );
381 $values['custom_style'] = FrmAppHelper::custom_style_value( $post_values );
382
383 return apply_filters( 'frm_setup_new_form_vars', $values );
384 }
385
386 /**
387 * Used when editing a form
388 *
389 * @param array $values
390 * @param object $record
391 * @param array $post_values
392 *
393 * @return array
394 */
395 public static function setup_edit_vars( $values, $record, $post_values = array() ) {
396 if ( ! $post_values ) {
397 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
398 }
399
400 $values['form_key'] = $post_values['form_key'] ?? $record->form_key;
401 $values['is_template'] = $post_values['is_template'] ?? $record->is_template;
402 $values['status'] = $record->status;
403
404 $values = self::fill_default_opts( $values, $record, $post_values );
405
406 return apply_filters( 'frm_setup_edit_form_vars', $values );
407 }
408
409 /**
410 * @param array $values
411 * @param false|object $record
412 * @param array|false $post_values
413 *
414 * @return array
415 */
416 public static function fill_default_opts( $values, $record, $post_values ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
417
418 $defaults = self::get_default_opts();
419
420 foreach ( $defaults as $var => $default ) {
421 if ( is_array( $default ) ) {
422 if ( ! isset( $values[ $var ] ) ) {
423 $values[ $var ] = $record && isset( $record->options[ $var ] ) ? $record->options[ $var ] : array();
424 }
425
426 foreach ( $default as $k => $v ) {
427 $values[ $var ][ $k ] = $post_values && isset( $post_values[ $var ][ $k ] ) ? $post_values[ $var ][ $k ] : ( $record && isset( $record->options[ $var ] ) && isset( $record->options[ $var ][ $k ] ) ? $record->options[ $var ][ $k ] : $v ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
428
429 if ( is_array( $v ) ) {
430 foreach ( $v as $k1 => $v1 ) {
431 $values[ $var ][ $k ][ $k1 ] = $post_values && isset( $post_values[ $var ][ $k ][ $k1 ] ) ? $post_values[ $var ][ $k ][ $k1 ] : ( $record && isset( $record->options[ $var ] ) && isset( $record->options[ $var ][ $k ] ) && isset( $record->options[ $var ][ $k ][ $k1 ] ) ? $record->options[ $var ][ $k ][ $k1 ] : $v1 ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
432 unset( $k1, $v1 );
433 }
434 }
435
436 unset( $k, $v );
437 }
438 } else {
439 $values[ $var ] = $post_values && isset( $post_values['options'][ $var ] ) ? $post_values['options'][ $var ] : ( $record && isset( $record->options[ $var ] ) ? $record->options[ $var ] : $default ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
440 }
441
442 unset( $var, $default );
443 }//end foreach
444
445 return $values;
446 }
447
448 /**
449 * @return array
450 */
451 public static function get_default_opts() {
452 $frm_settings = FrmAppHelper::get_settings();
453
454 return array(
455 'submit_value' => $frm_settings->submit_value,
456 'success_action' => 'message',
457 'success_msg' => $frm_settings->success_msg,
458 'show_form' => 0,
459 'akismet' => '',
460 'stopforumspam' => 0,
461 'antispam' => 0,
462 'no_save' => 0,
463 'ajax_load' => 0,
464 'js_validate' => 0,
465 'form_class' => '',
466 'custom_style' => 1,
467 'before_html' => self::get_default_html( 'before' ),
468 'after_html' => '',
469 'submit_html' => self::get_default_html( 'submit' ),
470 'show_title' => 0,
471 'show_description' => 0,
472 'ajax_submit' => 0,
473 );
474 }
475
476 /**
477 * @since 2.0.6
478 *
479 * @param array $options
480 * @param array $values
481 *
482 * @return void
483 */
484 public static function fill_form_options( &$options, $values ) {
485 $defaults = self::get_default_opts();
486
487 foreach ( $defaults as $var => $default ) {
488 $options[ $var ] = $values['options'][ $var ] ?? $default;
489 unset( $var, $default );
490 }
491 }
492
493 /**
494 * @param string $loc
495 *
496 * @return string
497 */
498 public static function get_default_html( $loc ) {
499 if ( $loc === 'submit' ) {
500 $draft_link = self::get_draft_link();
501 $start_over = self::get_start_over_shortcode();
502 $default_html = <<<SUBMIT_HTML
503 <div class="frm_submit frm_flex">
504 <button class="frm_button_submit" type="submit" [button_action]>[button_label]</button>
505 [if back_button]<button type="submit" name="frm_prev_page" formnovalidate="formnovalidate" class="frm_prev_page" [back_hook]>[back_label]</button>[/if back_button]
506 $draft_link
507 $start_over
508 </div>
509 SUBMIT_HTML;
510 } elseif ( $loc === 'before' ) {
511 $default_html = <<<BEFORE_HTML
512 <legend class="frm_screen_reader">[form_name]</legend>
513 [if form_name]<h3 class="frm_form_title">[form_name]</h3>[/if form_name]
514 [if form_description]<div class="frm_description">[form_description]</div>[/if form_description]
515 BEFORE_HTML;
516 } else {
517 $default_html = '';
518 }
519
520 return $default_html;
521 }
522
523 /**
524 * @return string
525 */
526 public static function get_draft_link() {
527 return '[if save_draft]<button class="frm_save_draft" [draft_hook]>[draft_label]</button>[/if save_draft]';
528 }
529
530 /**
531 * Gets start over button shortcode.
532 *
533 * @since 5.4
534 *
535 * @return string
536 */
537 public static function get_start_over_shortcode() {
538 return '[if start_over]<a href="#" tabindex="0" class="frm_start_over" [start_over_hook]>[start_over_label]</a>[/if start_over]';
539 }
540
541 /**
542 * @param string $html
543 * @param object $form
544 * @param string $submit
545 * @param object|null $form_action
546 * @param array $values
547 *
548 * @return void
549 */
550 public static function get_custom_submit( $html, $form, $submit, $form_action, $values ) {
551 $button = self::replace_shortcodes( $html, $form, $submit, $form_action, $values );
552
553 if ( ! str_contains( $button, '[button_action]' ) ) {
554 echo FrmAppHelper::maybe_kses( $button ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
555 return;
556 }
557
558 /**
559 * @since 5.0.06
560 */
561 $button = apply_filters( 'frm_submit_button_html', $button, compact( 'form' ) );
562
563 if ( FrmAppHelper::should_never_allow_unfiltered_html() ) {
564 $button = FrmAppHelper::kses_submit_button( $button );
565 }
566
567 $button_parts = explode( '[button_action]', $button );
568 $classes = apply_filters( 'frm_submit_button_class', array(), $form );
569
570 if ( $classes ) {
571 $classes = implode( ' ', $classes );
572 $button_class = 'frm_button_submit';
573
574 if ( preg_match( '/\bclass="[^"]*?\b' . preg_quote( $button_class, '/' ) . '\b[^"]*?"/', $button_parts[0] ) ) {
575 $button_parts[0] = str_replace( $button_class, $button_class . ' ' . esc_attr( $classes ), $button_parts[0] );
576 } else {
577 $button_parts[0] .= ' class="' . esc_attr( $classes ) . '"';
578 }
579 }
580
581 echo $button_parts[0]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
582 do_action( 'frm_submit_button_action', $form, $form_action );
583 echo $button_parts[1]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
584 }
585
586 /**
587 * @since 4.0
588 *
589 * @return array
590 */
591 public static function html_shortcodes() {
592 $codes = array(
593 'id' => array(
594 'label' => __( 'Field ID', 'formidable' ),
595 'class' => 'show_field_custom_html',
596 ),
597 'key' => array(
598 'label' => __( 'Field Key', 'formidable' ),
599 'class' => 'show_field_custom_html',
600 ),
601 'field_name' => array(
602 'label' => __( 'Field Name', 'formidable' ),
603 'class' => 'show_field_custom_html',
604 ),
605 'description' => array(
606 'label' => __( 'Field Description', 'formidable' ),
607 'class' => 'show_field_custom_html',
608 ),
609 'label_position' => array(
610 'label' => __( 'Label Position', 'formidable' ),
611 'class' => 'show_field_custom_html',
612 ),
613 'required_label' => array(
614 'label' => __( 'Required Label', 'formidable' ),
615 'class' => 'show_field_custom_html',
616 ),
617 'input' => array(
618 'label' => __( 'Input Field', 'formidable' ),
619 'class' => 'show_field_custom_html',
620 ),
621 'input opt=1' => array(
622 'label' => __( 'Single Option', 'formidable' ),
623 'title' => __( 'Show a single radio or checkbox option by replacing 1 with the order of the option', 'formidable' ),
624 'class' => 'show_field_custom_html',
625 ),
626 'input label=0' => array(
627 'label' => __( 'Hide Option Label', 'formidable' ),
628 'class' => 'show_field_custom_html',
629 ),
630 'required_class' => array(
631 'label' => __( 'Required Class', 'formidable' ),
632 'title' => __( 'Add class name if field is required', 'formidable' ),
633 'class' => 'show_field_custom_html',
634 ),
635 'error_class' => array(
636 'label' => __( 'Error Class', 'formidable' ),
637 'title' => __( 'Add class name if field has an error on form submit', 'formidable' ),
638 'class' => 'show_field_custom_html',
639 ),
640
641 'form_name' => array(
642 'label' => __( 'Form Name', 'formidable' ),
643 'class' => 'show_before_html show_after_html',
644 ),
645 'form_description' => array(
646 'label' => __( 'Form Description', 'formidable' ),
647 'class' => 'show_before_html show_after_html',
648 ),
649 'form_key' => array(
650 'label' => __( 'Form Key', 'formidable' ),
651 'class' => 'show_before_html show_after_html',
652 ),
653 'deletelink' => array(
654 'label' => __( 'Delete Entry Link', 'formidable' ),
655 'class' => 'show_before_html show_after_html',
656 ),
657
658 'button_label' => array(
659 'label' => __( 'Button Label', 'formidable' ),
660 'class' => 'show_submit_html',
661 ),
662 'button_action' => array(
663 'label' => __( 'Button Hook', 'formidable' ),
664 'class' => 'show_submit_html',
665 ),
666 );
667
668 /**
669 * @since 4.0
670 */
671 return apply_filters( 'frm_html_codes', $codes );
672 }
673
674 /**
675 * @since 4.0
676 *
677 * @param array $args
678 *
679 * @return void
680 */
681 public static function insert_opt_html( $args ) {
682 $class = $args['class'] ?? '';
683 $fields = self::get_field_type_data_for_insert_opt_html();
684 $field = $fields[ $args['type'] ] ?? array();
685
686 self::prepare_field_type( $field );
687
688 if ( ! isset( $field['icon'] ) ) {
689 $field['icon'] = 'frmfont frm_pencil_icon';
690 }
691
692 $possible_email_field = FrmFieldFactory::field_has_property( $args['type'], 'holds_email_values' );
693
694 if ( $possible_email_field ) {
695 $class .= ' show_frm_not_email_to';
696 }
697
698 if ( 'url' === $args['type'] ) {
699 $class .= ' frm_insert_url';
700 }
701
702 $truncated_name = FrmAppHelper::truncate( $args['name'], 60 );
703
704 ?>
705 <li class="<?php echo esc_attr( $class ); ?>">
706 <a href="javascript:void(0)" class="frmids frm_insert_code" data-code="<?php echo esc_attr( $args['id'] ); ?>">
707 <?php
708 if ( isset( $field['icon'] ) ) {
709 FrmAppHelper::icon_by_class( $field['icon'], array( 'aria-hidden' => 'true' ) );
710 }
711
712 echo esc_html( $truncated_name );
713 ?>
714 <span>[<?php echo esc_attr( $args['id_label'] ?? $args['id'] ); ?>]</span>
715 </a>
716 <a href="javascript:void(0)" class="frmkeys frm_insert_code frm_hidden" data-code="<?php echo esc_attr( $args['key'] ); ?>">
717 <?php
718 if ( isset( $field['icon'] ) ) {
719 FrmAppHelper::icon_by_class( $field['icon'], array( 'aria-hidden' => 'true' ) );
720 }
721
722 echo esc_html( $truncated_name );
723 ?>
724 <span>[<?php echo esc_attr( FrmAppHelper::truncate( $args['key_label'] ?? $args['key'], 7 ) ); ?>]</span>
725 </a>
726 </li>
727 <?php
728 }
729
730 /**
731 * Store and re-use field selection data for use when outputting shortcodes options in shortcode pop up.
732 * This significantly improves performance by avoiding repeat calls to FrmField::all_field_selection.
733 *
734 * @since 6.10
735 *
736 * @return array
737 */
738 private static function get_field_type_data_for_insert_opt_html() {
739 if ( ! isset( self::$field_type_data_for_insert_opt_html ) ) {
740 self::$field_type_data_for_insert_opt_html = FrmField::all_field_selection();
741 }
742 return self::$field_type_data_for_insert_opt_html;
743 }
744
745 /**
746 * @since 4.0
747 *
748 * @param array $args
749 *
750 * @return void
751 */
752 public static function insert_code_html( $args ) {
753 $defaults = array(
754 'class' => '',
755 'code' => '',
756 'label' => '',
757 'title' => '',
758 );
759
760 $args = array_merge( $defaults, $args );
761 $has_tooltip = ! empty( $args['title'] );
762
763 ?>
764 <li class="<?php echo esc_attr( $args['class'] ); ?>">
765 <a href="javascript:void(0)" class="frm_insert_code <?php echo $has_tooltip ? 'frm_help' : ''; ?>"
766 <?php echo $has_tooltip ? 'title="' . esc_attr( $args['title'] ) . '"' : ''; ?>
767 data-code="<?php echo esc_attr( $args['code'] ); ?>">
768 <?php echo esc_attr( FrmAppHelper::truncate( $args['label'], 60 ) ); ?>
769 <span>
770 [<?php echo esc_attr( FrmAppHelper::truncate( $args['code'], 10 ) ); ?>]
771 </span>
772 </a>
773 </li>
774 <?php
775 }
776
777 /**
778 * Some field types in add-ons may have been added with only
779 * a field type and name.
780 *
781 * @since 4.0
782 *
783 * @param array|string $field
784 *
785 * @return void
786 */
787 public static function prepare_field_type( &$field ) {
788 if ( ! is_array( $field ) ) {
789 $field = array(
790 'name' => $field,
791 'icon' => 'frmfont frm_pencil_icon',
792 );
793 }
794 }
795
796 /**
797 * Automatically add end section fields if they don't exist (2.0 migration)
798 *
799 * @since 2.0
800 *
801 * @param object $form
802 * @param array $fields
803 * @param bool $reset_fields
804 *
805 * @return void
806 */
807 public static function auto_add_end_section_fields( $form, $fields, &$reset_fields ) {
808 if ( ! $fields ) {
809 return;
810 }
811
812 $end_section_values = apply_filters( 'frm_before_field_created', FrmFieldsHelper::setup_new_vars( 'end_divider', $form->id ) );
813 $open = false;
814 $prev_order = false;
815 $add_order = 0;
816 $last_field = false;
817
818 foreach ( $fields as $field ) {
819 if ( $prev_order === $field->field_order ) {
820 ++$add_order;
821 }
822
823 if ( $add_order ) {
824 $reset_fields = true;
825 $field->field_order = $field->field_order + $add_order;
826 FrmField::update( $field->id, array( 'field_order' => $field->field_order ) );
827 }
828
829 switch ( $field->type ) {
830 case 'divider':
831 // Create an end section if open.
832 self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $field, 'move' );
833
834 // Mark it open for the next end section.
835 $open = true;
836 break;
837 case 'break':
838 self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $field, 'move' );
839 break;
840 case 'end_divider':
841 if ( ! $open ) {
842 // The section isn't open, so this is an extra field that needs to be removed.
843 FrmField::destroy( $field->id );
844 $reset_fields = true;
845 }
846
847 // There is already an end section here, so there is no need to create one.
848 $open = false;
849 }//end switch
850 $prev_order = $field->field_order;
851
852 $last_field = $field;
853 unset( $field );
854 }//end foreach
855
856 self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $last_field );
857 }
858
859 /**
860 * Create end section field if it doesn't exist. This is for migration from < 2.0
861 * Fix any ordering that may be messed up
862 *
863 * @param bool $open
864 * @param bool $reset_fields
865 * @param int $add_order
866 * @param array $end_section_values
867 * @param object $field
868 * @param string $move
869 *
870 * @return void
871 */
872 public static function maybe_create_end_section( &$open, &$reset_fields, &$add_order, $end_section_values, $field, $move = 'no' ) {
873 if ( ! $open ) {
874 return;
875 }
876
877 $end_section_values['field_order'] = $field->field_order + 1;
878
879 FrmField::create( $end_section_values );
880
881 if ( $move === 'move' ) {
882 // bump the order of current field unless we're at the end of the form
883 FrmField::update( $field->id, array( 'field_order' => $field->field_order + 2 ) );
884 }
885
886 $add_order += 2;
887 $open = false;
888 $reset_fields = true;
889 }
890
891 /**
892 * @param string $html
893 * @param object $form
894 * @param bool $title
895 * @param bool $description
896 * @param array $values
897 *
898 * @return string
899 */
900 public static function replace_shortcodes( $html, $form, $title = false, $description = false, $values = array() ) {
901 $codes = array(
902 'form_name' => $title,
903 'form_description' => $description,
904 'entry_key' => true,
905 );
906
907 foreach ( $codes as $code => $show ) {
908 if ( $code === 'form_name' ) {
909 $replace_with = $form->name;
910 } elseif ( $code === 'form_description' ) {
911 $replace_with = FrmAppHelper::use_wpautop( $form->description );
912 } elseif ( $code === 'entry_key' && ! empty( $_GET ) && isset( $_GET['entry'] ) ) {
913 $replace_with = FrmAppHelper::simple_get( 'entry' );
914 } else {
915 $replace_with = '';
916 }
917
918 FrmShortcodeHelper::remove_inline_conditions( ( FrmAppHelper::is_true( $show ) && $replace_with != '' ), $code, $replace_with, $html ); // phpcs:ignore Universal.Operators.StrictComparisons, SlevomatCodingStandard.Files.LineLength.LineTooLong
919 }
920
921 // Replace [form_key].
922 $html = str_replace( '[form_key]', $form->form_key, $html );
923
924 // Replace [frmurl].
925 $html = str_replace( '[frmurl]', FrmFieldsHelper::dynamic_default_values( 'frmurl' ), $html );
926
927 if ( str_contains( $html, '[button_label]' ) ) {
928 add_filter( 'frm_submit_button', 'FrmFormsHelper::submit_button_label', 1 );
929 $submit_label = apply_filters( 'frm_submit_button', $title, $form );
930 $submit_label = esc_attr( do_shortcode( $submit_label ) );
931 $html = str_replace( '[button_label]', $submit_label, $html );
932 }
933
934 $html = apply_filters( 'frm_form_replace_shortcodes', $html, $form, $values );
935
936 if ( str_contains( $html, '[if back_button]' ) ) {
937 $html = preg_replace( '/(\[if\s+back_button\])(.*?)(\[\/if\s+back_button\])/mis', '', $html );
938 }
939
940 if ( str_contains( $html, '[if save_draft]' ) ) {
941 $html = preg_replace( '/(\[if\s+save_draft\])(.*?)(\[\/if\s+save_draft\])/mis', '', $html );
942 }
943
944 if ( str_contains( $html, '[if start_over]' ) ) {
945 $html = preg_replace( '/(\[if\s+start_over\])(.*?)(\[\/if\s+start_over\])/mis', '', $html );
946 }
947
948 if ( apply_filters( 'frm_do_html_shortcodes', true ) ) {
949 $html = do_shortcode( $html );
950 }
951
952 return $html;
953 }
954
955 /**
956 * @param string $submit
957 *
958 * @return string
959 */
960 public static function submit_button_label( $submit ) {
961 if ( ! $submit ) {
962 $frm_settings = FrmAppHelper::get_settings();
963 $submit = $frm_settings->submit_value;
964 }
965
966 return $submit;
967 }
968
969 /**
970 * If the Formidable styling isn't being loaded,
971 * use inline styling to hide the element
972 *
973 * @since 2.03.05
974 *
975 * @return void
976 */
977 public static function maybe_hide_inline() {
978 $frm_settings = FrmAppHelper::get_settings();
979
980 if ( $frm_settings->load_style === 'none' ) {
981 echo ' style="display:none;"';
982 } elseif ( $frm_settings->load_style === 'dynamic' ) {
983 FrmStylesController::enqueue_style();
984 }
985 }
986
987 /**
988 * @param array|bool|int|object|string $form
989 *
990 * @return string|null
991 */
992 public static function get_form_style_class( $form = false ) {
993 $style = self::get_form_style( $form );
994 $class = ' with_frm_style';
995
996 if ( ! $style ) {
997 return FrmAppHelper::is_admin_page( 'formidable-entries' ) ? $class : null;
998 }
999
1000 // If submit button needs to be inline or centered.
1001 if ( is_object( $form ) ) {
1002 $form = $form->options;
1003 }
1004
1005 if ( ! empty( $form['submit_align'] ) ) {
1006 $submit_align = $form['submit_align'];
1007 } elseif ( self::form_should_be_inline_and_missing_class( $form ) ) {
1008 $submit_align = 'inline';
1009 } else {
1010 $submit_align = '';
1011 }
1012
1013 if ( 'inline' === $submit_align ) {
1014 $class .= ' frm_inline_form';
1015 $class .= self::maybe_align_fields_top( $form );
1016 } elseif ( 'center' === $submit_align ) {
1017 $class .= ' frm_center_submit';
1018 }
1019
1020 return apply_filters( 'frm_add_form_style_class', $class, $style, compact( 'form' ) );
1021 }
1022
1023 /**
1024 * In order for frm_inline_submit to inline the submit button the form must also have the frm_inline_form that adds the grid-column style rules required for it to work.
1025 *
1026 * @since 5.0.12
1027 *
1028 * @param array $form
1029 *
1030 * @return bool
1031 */
1032 private static function form_should_be_inline_and_missing_class( $form ) {
1033 if ( isset( $form['form_class'] ) && str_contains( ' ' . $form['form_class'] . ' ', ' frm_inline_form ' ) ) {
1034 // not missing class, avoid adding it twice.
1035 return false;
1036 }
1037 return ! empty( $form['submit_html'] ) && str_contains( $form['submit_html'], 'frm_inline_submit' );
1038 }
1039
1040 /**
1041 * Returns appropriate class if form has top labels
1042 *
1043 * @param array $form
1044 *
1045 * @return string
1046 */
1047 private static function maybe_align_fields_top( $form ) {
1048 return self::form_has_top_labels( $form ) ? ' frm_inline_top' : '';
1049 }
1050
1051 /**
1052 * Determine if a form has fields with top labels so submit button can be aligned properly
1053 *
1054 * @param array $form
1055 *
1056 * @return bool
1057 */
1058 private static function form_has_top_labels( $form ) {
1059 if ( ! isset( $form['fields'] ) ) {
1060 return false;
1061 }
1062
1063 $fields = $form['fields'];
1064
1065 if ( count( $fields ) <= 0 ) {
1066 return false;
1067 }
1068
1069 // Start from the fields closest to the submit button.
1070 $fields = array_reverse( $fields );
1071
1072 foreach ( $fields as $field ) {
1073 $type = $field['original_type'] ?? $field['type'];
1074 $has_input = FrmFieldFactory::field_has_property( $type, 'has_input' );
1075
1076 if ( $has_input ) {
1077 return self::field_has_top_label( $field, $form );
1078 }
1079 }
1080
1081 return false;
1082 }
1083
1084 /**
1085 * Check if a field's label position is set to "top"
1086 *
1087 * @param array $field
1088 * @param bool|object|string $form
1089 *
1090 * @return bool
1091 */
1092 private static function field_has_top_label( $field, $form ) {
1093 $label_position = FrmFieldsHelper::label_position( $field['label'], $field, $form );
1094 return in_array( $label_position, array( 'top', 'inside', 'hidden' ), true );
1095 }
1096
1097 /**
1098 * @param array|bool|int|object|string $form
1099 *
1100 * @return string
1101 */
1102 public static function get_form_style( $form ) {
1103 $style = 1;
1104
1105 if ( ! $form || 'default' === $form ) {
1106 return $style;
1107 }
1108
1109 if ( is_object( $form ) && $form->parent_form_id ) {
1110 // get the parent form if this is a child
1111 $form = $form->parent_form_id;
1112 } elseif ( is_array( $form ) && ! empty( $form['parent_form_id'] ) ) {
1113 $form = $form['parent_form_id'];
1114 } elseif ( is_array( $form ) && isset( $form['custom_style'] ) ) {
1115 $style = $form['custom_style'];
1116 }
1117
1118 if ( $form && ( is_string( $form ) || is_int( $form ) ) ) {
1119 $form = FrmForm::getOne( $form );
1120 }
1121
1122 return $form && is_object( $form ) && isset( $form->options['custom_style'] ) ? $form->options['custom_style'] : $style;
1123 }
1124
1125 /**
1126 * Display the validation error messages when an entry is submitted
1127 *
1128 * @since 2.0.6
1129 *
1130 * @param array $args Includes img, errors.
1131 *
1132 * @return void
1133 */
1134 public static function show_errors( $args ) {
1135 $invalid_msg = self::get_invalid_error_message( $args );
1136
1137 if ( ! $invalid_msg ) {
1138 $show_img = false;
1139 } else {
1140 echo wp_kses_post( $invalid_msg );
1141 $show_img = true;
1142 }
1143
1144 self::show_error(
1145 array(
1146 'img' => $args['img'],
1147 'errors' => $args['errors'],
1148 'show_img' => $show_img,
1149 )
1150 );
1151 }
1152
1153 /**
1154 * Display the error message in the front-end along with the image if set
1155 * The image was removed from the styling settings, but it may still be set with a hook
1156 * If the message in the global settings is empty, show every validation message in the error box
1157 *
1158 * @since 2.0.6
1159 *
1160 * @param array $args Includes img, errors, and show_img.
1161 *
1162 * @return void
1163 */
1164 public static function show_error( $args ) {
1165 // remove any blank messages
1166 $args['errors'] = array_filter( (array) $args['errors'] );
1167
1168 $line_break_first = $args['show_img'];
1169
1170 foreach ( $args['errors'] as $error_key => $error ) {
1171 if ( $line_break_first && ! is_numeric( $error_key ) && ( $error_key === 'cptch_number' || str_starts_with( $error_key, 'field' ) ) ) {
1172 continue;
1173 }
1174
1175 $id = str_replace( 'field', 'field_', $error_key );
1176 echo '<div id="' . esc_attr( $id ) . '_error">';
1177
1178 if ( $args['show_img'] && ! empty( $args['img'] ) ) {
1179 echo '<img src="' . esc_url( $args['img'] ) . '" alt="" />';
1180 } else {
1181 $args['show_img'] = true;
1182 }
1183
1184 echo wp_kses_post( $error );
1185
1186 echo '</div>';
1187 }
1188 }
1189
1190 /**
1191 * @param int|string $id
1192 *
1193 * @return void
1194 */
1195 public static function maybe_get_scroll_js( $id ) {
1196 $offset = apply_filters( 'frm_scroll_offset', 4, array( 'form_id' => $id ) );
1197
1198 // phpcs:ignore Universal.Operators.StrictComparisons
1199 if ( $offset != - 1 ) {
1200 self::get_scroll_js( $id );
1201 }
1202 }
1203
1204 /**
1205 * @param int|string $form_id
1206 *
1207 * @return void
1208 */
1209 public static function get_scroll_js( $form_id ) {
1210 echo '<script type="text/javascript">document.addEventListener(\'DOMContentLoaded\',function(){frmFrontForm.scrollMsg(' . (int) $form_id . ');})</script>';
1211 }
1212
1213 /**
1214 * @since 3.0
1215 *
1216 * @param int|object|string $form_id
1217 * @param mixed $form
1218 *
1219 * @return array
1220 */
1221 public static function get_action_links( $form_id, $form ) {
1222 if ( ! is_object( $form ) ) {
1223 $form = FrmForm::getOne( $form_id );
1224 }
1225
1226 $actions = array();
1227 $trash_links = self::delete_trash_links( $form_id );
1228
1229 if ( 'trash' === $form->status ) {
1230 $actions['restore'] = $trash_links['restore'];
1231
1232 if ( current_user_can( 'frm_delete_forms' ) ) {
1233 $actions['trash'] = $trash_links['delete'];
1234 }
1235 } elseif ( current_user_can( 'frm_edit_forms' ) ) {
1236 $duplicate_link = '?page=formidable&frm_action=duplicate&id=' . $form_id;
1237
1238 if ( $form->is_template ) {
1239 $actions['frm_duplicate'] = array(
1240 'url' => wp_nonce_url( $duplicate_link ),
1241 'label' => __( 'Create Form from Template', 'formidable' ),
1242 'icon' => 'frmfont frm_clone_icon',
1243 );
1244 } else {
1245 $actions['duplicate'] = array(
1246 'url' => wp_nonce_url( $duplicate_link ),
1247 'label' => __( 'Duplicate Form', 'formidable' ),
1248 'icon' => 'frmfont frm_clone_icon',
1249 );
1250 }
1251
1252 $actions['trash'] = self::delete_trash_info( $form_id, $form->status );
1253 }//end if
1254
1255 return $actions;
1256 }
1257
1258 /**
1259 * @param int|object|string $data
1260 *
1261 * @return string
1262 */
1263 public static function edit_form_link( $data ) {
1264 $form_id = self::get_form_id_from_data( $data );
1265
1266 if ( ! $form_id ) {
1267 return '';
1268 }
1269
1270 $label = self::edit_form_link_label( $data );
1271 return '<a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html( $label ) . '</a>';
1272 }
1273
1274 /**
1275 * Returns a text used when no title is set.
1276 *
1277 * @since 6.16.1
1278 *
1279 * @return string
1280 */
1281 public static function get_no_title_text() {
1282 return __( '(no title)', 'formidable' );
1283 }
1284
1285 /**
1286 * @param int|object|string $data
1287 *
1288 * @return string
1289 */
1290 public static function edit_form_link_label( $data ) {
1291 $name = self::get_form_name_from_data( $data );
1292
1293 if ( ! $name ) {
1294 return self::get_no_title_text();
1295 }
1296
1297 return FrmAppHelper::truncate( $name, 40 );
1298 }
1299
1300 /**
1301 * @param int|object|string $data
1302 *
1303 * @return int|string
1304 */
1305 private static function get_form_id_from_data( $data ) {
1306 return is_object( $data ) ? $data->id : $data;
1307 }
1308
1309 /**
1310 * @param mixed $data
1311 *
1312 * @return string
1313 */
1314 private static function get_form_name_from_data( $data ) {
1315 if ( is_object( $data ) ) {
1316 return $data->name;
1317 }
1318
1319 $form_id = $data;
1320 return FrmForm::getName( $form_id );
1321 }
1322
1323 /**
1324 * @param int $id
1325 * @param string $status
1326 * @param string $length
1327 *
1328 * @return string
1329 */
1330 public static function delete_trash_link( $id, $status, $length = 'label' ) {
1331 $link_details = self::delete_trash_info( $id, $status );
1332 return self::format_link_html( $link_details, $length );
1333 }
1334
1335 /**
1336 * @since 3.0
1337 *
1338 * @param array $link_details
1339 * @param string $length
1340 *
1341 * @return string
1342 */
1343 public static function format_link_html( $link_details, $length = 'label' ) {
1344 $link = '';
1345
1346 if ( $link_details ) {
1347 $link = '<a href="' . esc_url( $link_details['url'] ) . '" class="frm-trash-link"';
1348
1349 if ( isset( $link_details['data'] ) ) {
1350 foreach ( $link_details['data'] as $data => $value ) {
1351 $link .= ' data-' . esc_attr( $data ) . '="' . esc_attr( $value ) . '"';
1352 }
1353 } elseif ( isset( $link_details['confirm'] ) ) {
1354 $link .= ' onclick="return confirm(\'' . esc_attr( $link_details['confirm'] ) . '\')"';
1355 }
1356
1357 $label = $link_details[ $length ] ?? $link_details['label'];
1358
1359 if ( $length === 'icon' && isset( $link_details[ $length ] ) ) {
1360 $label = '<span class="' . $label . '" title="' . esc_attr( $link_details['label'] ) . '" aria-hidden="true"></span>';
1361 $link .= ' aria-label="' . esc_attr( $link_details['label'] ) . '"';
1362 }
1363
1364 $link .= '>' . $label . '</a>';
1365 }
1366
1367 return $link;
1368 }
1369
1370 /**
1371 * @since 3.0
1372 *
1373 * @param int $id
1374 * @param string $status
1375 *
1376 * @return array
1377 */
1378 public static function delete_trash_info( $id, $status ) {
1379 $labels = self::delete_trash_links( $id );
1380
1381 if ( 'trash' === $status ) {
1382 $info = $labels['restore'];
1383 } elseif ( current_user_can( 'frm_delete_forms' ) ) {
1384 $info = EMPTY_TRASH_DAYS ? $labels['trash'] : $labels['delete'];
1385 } else {
1386 $info = array();
1387 }
1388
1389 return $info;
1390 }
1391
1392 /**
1393 * @since 3.0
1394 *
1395 * @param int $id
1396 *
1397 * @return array
1398 */
1399 public static function delete_trash_links( $id ) {
1400 $current_page = FrmAppHelper::get_simple_request( array( 'param' => 'form_type' ) );
1401 $base_url = '?page=formidable&form_type=' . $current_page . '&id=' . $id;
1402
1403 return array(
1404 'restore' => array(
1405 'label' => __( 'Restore from Trash', 'formidable' ),
1406 'short' => __( 'Restore', 'formidable' ),
1407 'url' => wp_nonce_url( $base_url . '&frm_action=untrash', 'untrash_form_' . absint( $id ) ),
1408 ),
1409 'trash' => array(
1410 'label' => __( 'Move Form to Trash', 'formidable' ),
1411 'short' => __( 'Trash', 'formidable' ),
1412 'url' => wp_nonce_url( $base_url . '&frm_action=trash', 'trash_form_' . absint( $id ) ),
1413 'icon' => 'frmfont frm_delete_icon',
1414 'data' => array(
1415 'frmverify' => __( 'Do you want to move this form to the trash?', 'formidable' ),
1416 'frmverify-btn' => 'frm-button-red',
1417 ),
1418 ),
1419 'delete' => array(
1420 'label' => __( 'Delete Permanently', 'formidable' ),
1421 'short' => __( 'Delete', 'formidable' ),
1422 'url' => wp_nonce_url( $base_url . '&frm_action=destroy', 'destroy_form_' . absint( $id ) ),
1423 'confirm' => __( 'Are you sure you want to delete this form and all its entries?', 'formidable' ),
1424 'icon' => 'frmfont frm_delete_icon',
1425 'data' => array(
1426 'frmverify' => __( 'This will permanently delete the form and all its entries. This is irreversible. Are you sure you want to continue?', 'formidable' ),
1427 'frmverify-btn' => 'frm-button-red',
1428 ),
1429 ),
1430 );
1431 }
1432
1433 /**
1434 * @since 3.0
1435 *
1436 * @return array
1437 */
1438 public static function css_classes() {
1439 $classes = array(
1440 'frm_total' => array(
1441 'label' => __( 'Total', 'formidable' ),
1442 'title' => __( 'Add this to a read-only field to display the text in bold without a border or background.', 'formidable' ),
1443 ),
1444 'frm_total_big' => array(
1445 'label' => __( 'Big Total', 'formidable' ),
1446 'title' => __( 'Add this to a read-only field to display the text in large, bold text without a border or background.', 'formidable' ),
1447 ),
1448 'frm_scroll_box' => array(
1449 'label' => __( 'Scroll Box', 'formidable' ),
1450 'title' => __( 'If you have many checkbox or radio button options, you may add this class to allow your user to easily scroll through the options. Or add a scrolling area around content in an HTML field.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
1451 ),
1452 'frm_first' => array(
1453 'label' => __( 'First', 'formidable' ),
1454 'title' => __( 'Add this to the first field in each row along with a width. ie frm_first frm4', 'formidable' ),
1455 ),
1456 'frm_alignright' => __( 'Right', 'formidable' ),
1457 'frm_grid_first' => __( 'First Grid Row', 'formidable' ),
1458 'frm_grid' => __( 'Even Grid Row', 'formidable' ),
1459 'frm_grid_odd' => __( 'Odd Grid Row', 'formidable' ),
1460 'frm_color_block' => array(
1461 'label' => __( 'Color Block', 'formidable' ),
1462 'title' => __( 'Add a background color to the field or section.', 'formidable' ),
1463 ),
1464 'frm_capitalize' => array(
1465 'label' => __( 'Capitalize', 'formidable' ),
1466 'title' => __( 'Automatically capitalize the first letter in each word.', 'formidable' ),
1467 ),
1468 );
1469
1470 return apply_filters( 'frm_layout_classes', $classes );
1471 }
1472
1473 /**
1474 * @return array
1475 */
1476 public static function grid_classes() {
1477 return array(
1478 'frm_half' => '1/2',
1479 'frm_third' => '1/3',
1480 'frm_two_thirds' => '2/3',
1481 'frm_fourth' => '1/4',
1482 'frm_three_fourths' => '3/4',
1483 'frm_sixth' => '1/6',
1484 'frm10' => '5/6',
1485 'frm12' => '100%',
1486 );
1487 }
1488
1489 /**
1490 * @since 3.0
1491 *
1492 * @param mixed $style
1493 * @param string $class
1494 *
1495 * @return string
1496 */
1497 public static function style_class_label( $style, $class ) {
1498 $label = '';
1499
1500 if ( ! $style ) {
1501 $label = $class;
1502 } elseif ( ! is_array( $style ) ) {
1503 $label = $style;
1504 } elseif ( isset( $style['label'] ) ) {
1505 $label = $style['label'];
1506 }
1507
1508 return $label;
1509 }
1510
1511 /**
1512 * @param string $status
1513 *
1514 * @return string
1515 */
1516 public static function status_nice_name( $status ) {
1517 $nice_names = array(
1518 'draft' => __( 'Draft', 'formidable' ),
1519 'trash' => __( 'Trash', 'formidable' ),
1520 'publish' => __( 'Published', 'formidable' ),
1521 );
1522
1523 if ( ! in_array( $status, array_keys( $nice_names ), true ) ) {
1524 $status = 'publish';
1525 }
1526
1527 return $nice_names[ $status ];
1528 }
1529
1530 /**
1531 * Renders a template icon based on the given categories.
1532 *
1533 * @param array $categories The categories to render the icon for.
1534 * @param array $atts {
1535 * Optional. An array of attributes for rendering.
1536 *
1537 * @type string $html 'span' or 'div'. Default 'span'.
1538 * @type bool $bg Whether to add a background color or not. Default false.
1539 * }
1540 *
1541 * @return void
1542 */
1543 public static function template_icon( $categories, $atts = array() ) {
1544 // Define defaults.
1545 $defaults = array(
1546 'bg' => true,
1547 );
1548 $atts = array_merge( $defaults, $atts );
1549
1550 // Filter out ignored categories.
1551 $ignore = self::get_license_types();
1552 $categories = array_diff( $categories, $ignore );
1553
1554 // Define icons mapping.
1555 $icons = array(
1556 'WooCommerce' => array( 'woocommerce', 'var(--purple)' ),
1557 'Post' => array( 'wordpress', 'rgb(0,160,210)' ),
1558 'User Registration' => array( 'register', 'var(--pink)' ),
1559 'Registration and Signup' => array( 'register', 'var(--pink)' ),
1560 'PayPal' => array( 'paypal' ),
1561 'Stripe' => array( 'credit_card', 'var(--green)' ),
1562 'Twilio' => array( 'sms' ),
1563 'Payment' => array( 'credit_card' ),
1564 'Order Form' => array( 'product' ),
1565 'Finance' => array( 'total' ),
1566 'Health and Wellness' => array( 'heart', 'var(--pink)' ),
1567 'Event Planning' => array( 'calendar', 'var(--orange)' ),
1568 'Real Estate' => array( 'house' ),
1569 'Nonprofit' => array( 'heart_solid' ),
1570 'Calculator' => array( 'calculator', 'var(--purple)' ),
1571 'Quiz' => array( 'percent' ),
1572 'Registrations' => array( 'address_card' ),
1573 'Customer Service' => array( 'users_solid' ),
1574 'Education' => array( 'pencil' ),
1575 'Marketing' => array( 'eye' ),
1576 'Feedback' => array( 'smile' ),
1577 'Business Operations' => array( 'case' ),
1578 'Contact Form' => array( 'email' ),
1579 'Conversational Forms' => array( 'chat_forms' ),
1580 'Survey' => array( 'chat_forms', 'var(--orange)' ),
1581 'Application' => array( 'align_right' ),
1582 'Signature' => array( 'signature' ),
1583 '' => array( 'align_right' ),
1584 );
1585
1586 // Determine the icon to be used.
1587 $icon = $icons[''];
1588
1589 if ( count( $categories ) === 1 ) {
1590 $category = reset( $categories );
1591 $icon = $icons[ $category ] ?? $icon;
1592 } elseif ( $categories ) {
1593 $icons = array_intersect_key( $icons, array_flip( $categories ) );
1594 $icon = reset( $icons );
1595 }
1596
1597 // Prepare variables for output.
1598 $icon_name = $icon[0];
1599 $bg_color = $icon[1] ?? '';
1600
1601 // Render the icon.
1602 echo '<span class="frm-category-icon frm-icon-wrapper"';
1603
1604 if ( $bg_color && $atts['bg'] ) {
1605 echo ' style="background-color:' . esc_attr( $bg_color ) . '"';
1606 }
1607 echo '>';
1608 FrmAppHelper::icon_by_class( 'frmfont frm_' . $icon_name . '_icon' );
1609 echo '</span>';
1610 }
1611
1612 /**
1613 * Get template install link.
1614 *
1615 * @since 4.02
1616 *
1617 * @param array $template Template details.
1618 * @param array $args Additional arguments.
1619 *
1620 * @return array The link attributes.
1621 */
1622 public static function get_template_install_link( $template, $args ) {
1623 $defaults = array(
1624 'class' => 'install-now',
1625 'href' => 'href',
1626 'atts' => true,
1627 );
1628
1629 if ( ! empty( $template['url'] ) ) {
1630 $link = array(
1631 'url' => $template['url'],
1632 'label' => __( 'Create Form', 'formidable' ),
1633 'class' => 'frm-install-template',
1634 'href' => 'rel',
1635 'atts' => '',
1636 );
1637 } else {
1638 $link = array(
1639 'url' => $args['pricing'],
1640 'label' => __( 'Upgrade', 'formidable' ),
1641 );
1642 }
1643
1644 return array_merge( $defaults, $link );
1645 }
1646
1647 /**
1648 * Is the template included with the license type?
1649 *
1650 * @since 4.02.02
1651 *
1652 * @param array $args
1653 *
1654 * @return bool
1655 */
1656 public static function plan_is_allowed( $args ) {
1657 if ( empty( $args['license_type'] ) ) {
1658 return false;
1659 }
1660
1661 $plans = array( 'free', 'personal', 'business', 'elite' );
1662 $license_type = strtolower( $args['license_type'] );
1663 $plan_required = strtolower( $args['plan_required'] );
1664 $included = $license_type === $plan_required;
1665
1666 if ( $included || ! in_array( $plan_required, $plans, true ) ) {
1667 return $included;
1668 }
1669
1670 foreach ( $plans as $plan ) {
1671 if ( $included || $plan === $license_type ) {
1672 break;
1673 }
1674
1675 $included = $plan === $plan_required;
1676 }
1677
1678 return $included;
1679 }
1680
1681 /**
1682 * If a template or add-on cannot be installed, show a message
1683 * about which plan is required.
1684 *
1685 * @since 4.0
1686 *
1687 * @param string $requires
1688 * @param string $link
1689 *
1690 * @return void
1691 */
1692 public static function show_plan_required( $requires, $link ) {
1693 if ( ! $requires ) {
1694 return;
1695 }
1696
1697 ?>
1698 <p class="frm_plan_required">
1699 <?php esc_html_e( 'Plan required:', 'formidable' ); ?>
1700 <a href="<?php echo esc_url( $link ); ?>" target="_blank" rel="noopener">
1701 <?php echo esc_html( $requires ); ?>
1702 </a>
1703 </p>
1704 <?php
1705 }
1706
1707 /**
1708 * @since 4.0
1709 *
1710 * @param array $item
1711 *
1712 * @return false|string
1713 */
1714 public static function get_plan_required( &$item ) {
1715 if ( ! isset( $item['categories'] ) || ! is_array( $item['categories'] ) || ! empty( $item['url'] ) ) {
1716 return false;
1717 }
1718
1719 $plans = self::get_license_types();
1720
1721 foreach ( $item['categories'] as $k => $category ) {
1722 if ( in_array( $category, $plans, true ) ) {
1723 unset( $item['categories'][ $k ] );
1724 return self::convert_legacy_package_names( $category );
1725 }
1726 }
1727
1728 return false;
1729 }
1730
1731 /**
1732 * Converts legacy package names to the current standard package name.
1733 *
1734 * @since 6.15
1735 *
1736 * @param string $package_name
1737 *
1738 * @return string The updated package name.
1739 */
1740 public static function convert_legacy_package_names( $package_name ) {
1741 if ( in_array( $package_name, array( 'Creator', 'Personal' ), true ) ) {
1742 $package_name = 'Plus';
1743 }
1744
1745 return $package_name;
1746 }
1747
1748 /**
1749 * Get the license types.
1750 *
1751 * @since 6.15
1752 *
1753 * @param array $args
1754 *
1755 * @return array
1756 */
1757 public static function get_license_types( $args = array() ) {
1758 $defaults = array(
1759 'include_all' => true,
1760 'case_lower' => false,
1761 );
1762 $args = wp_parse_args( $args, $defaults );
1763 $license_types = array( 'Basic', 'Plus', 'Business', 'Elite' );
1764
1765 if ( $args['include_all'] ) {
1766 $license_types = array_merge( array( 'free', 'Personal', 'Creator' ), $license_types );
1767 }
1768
1769 if ( $args['case_lower'] ) {
1770 $license_types = array_map( 'strtolower', $license_types );
1771 }
1772
1773 return $license_types;
1774 }
1775
1776 /**
1777 * Checks for warnings to be displayed after form settings are saved.
1778 *
1779 * @since 4.04
1780 *
1781 * @param array $values The $_POST array, which contains values submitted in a form.
1782 *
1783 * @return array An array of warnings or an empty array.
1784 */
1785 public static function check_for_warnings( $values ) {
1786 $warnings = array();
1787 $redirect_warning = self::check_redirect_url_for_unsafe_params( $values );
1788
1789 if ( $redirect_warning ) {
1790 $warnings[] = $redirect_warning;
1791 }
1792
1793 return apply_filters( 'frm_check_for_warnings', $warnings, $values );
1794 }
1795
1796 /**
1797 * Checks the redirect URL for params whose names are reserved words.
1798 *
1799 * @since 4.04
1800 *
1801 * @param array $values The $_POST array, which contains the values submitted in a form.
1802 *
1803 * @return bool|string A warning message about unsafe params or false.
1804 */
1805 private static function check_redirect_url_for_unsafe_params( $values ) {
1806 if ( ! isset( $values['options'] ) ) {
1807 return false;
1808 }
1809
1810 $options = $values['options'];
1811 FrmAppHelper::sanitize_with_html( $options );
1812
1813 if ( ! isset( $options['success_action'] ) || $options['success_action'] !== 'redirect' || ! isset( $options['success_url'] ) ) {
1814 return false;
1815 }
1816
1817 $unsafe_params_in_redirect = self::get_unsafe_params( $options['success_url'] );
1818
1819 return self::create_unsafe_param_warning( $unsafe_params_in_redirect );
1820 }
1821
1822 /**
1823 * Returns an array of params whose names are reserved words in the specified URL.
1824 *
1825 * @since 4.04
1826 *
1827 * @param string $url The URL whose params are being checked.
1828 *
1829 * @return array An array of params whose names are reserved words or an empty array.
1830 */
1831 private static function get_unsafe_params( $url ) {
1832 $redirect_components = parse_url( $url );
1833
1834 if ( empty( $redirect_components['query'] ) ) {
1835 return array();
1836 }
1837
1838 parse_str( $redirect_components['query'], $redirect_params );
1839 $redirect_param_names = array_keys( $redirect_params );
1840 $reserved_words = self::reserved_words();
1841 $unsafe_params_in_redirect = array_intersect( $redirect_param_names, $reserved_words );
1842
1843 return array_values( $unsafe_params_in_redirect );
1844 }
1845
1846 /**
1847 * Returns a warning if reserved words have been used as param names in the redirect URL.
1848 *
1849 * @since 4.04
1850 *
1851 * @param array $unsafe_params_in_redirect Array of params from the redirect URL whose names are reserved words.
1852 *
1853 * @return bool|string A string with an unsafe param message or false.
1854 */
1855 private static function create_unsafe_param_warning( $unsafe_params_in_redirect ) {
1856 $count = count( $unsafe_params_in_redirect );
1857 $caution = esc_html__( 'Is this intentional?', 'formidable' );
1858 $reserved_words_intro = esc_html__( 'See the list of reserved words in WordPress.', 'formidable' );
1859 $reserved_words_link = '<a href="https://codex.wordpress.org/WordPress_Query_Vars" target="_blank"> ' . $reserved_words_intro . '</a>';
1860
1861 if ( $count === 0 ) {
1862 return false;
1863 }
1864
1865 if ( $count === 1 ) {
1866 /* translators: %s: the name of a single parameter in the redirect URL */
1867 return sprintf( esc_html__( 'The redirect URL is using the parameter "%s", which is reserved by WordPress. ', 'formidable' ), $unsafe_params_in_redirect[0] ) . $caution . $reserved_words_link; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
1868 }
1869
1870 $unsafe_params_string = implode( '", "', $unsafe_params_in_redirect );
1871
1872 /* translators: %s: the names of two or more parameters in the redirect URL, separated by commas */
1873 return sprintf( esc_html__( 'The redirect URL is using the parameters "%s", which are reserved by WordPress. ', 'formidable' ), $unsafe_params_string ) . $caution . $reserved_words_link; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
1874 }
1875
1876 /**
1877 * Returns an array of common reserved words in WordPress.
1878 *
1879 * An edited list of reserved terms from the Codex.
1880 * https://codex.wordpress.org/Reserved_Terms
1881 *
1882 * @since 4.04
1883 *
1884 * @return array Array of WordPress reserved words.
1885 */
1886 public static function reserved_words() {
1887 return array(
1888 'id',
1889 'attachment',
1890 'author',
1891 'author_name',
1892 'calendar',
1893 'cat',
1894 'category',
1895 'category_name',
1896 'cpage',
1897 'custom',
1898 'day',
1899 'date',
1900 'error',
1901 'feed',
1902 'hour',
1903 'm',
1904 'minute',
1905 'more',
1906 'name',
1907 'order',
1908 'p',
1909 'page',
1910 'page_id',
1911 'paged',
1912 'pb',
1913 'post',
1914 'posts',
1915 'preview',
1916 's',
1917 'search',
1918 'second',
1919 'sentence',
1920 'tag',
1921 'taxonomy',
1922 'tb',
1923 'term',
1924 'terms',
1925 'theme',
1926 'title',
1927 'type',
1928 'w',
1929 'year',
1930 );
1931 }
1932
1933 /**
1934 * Make sure the field shortcodes in a url always add the sanitize_url=1 option if nothing is defined.
1935 * This is to prevent some field characters like ', @, and | from being stripped from the redirect URL.
1936 *
1937 * @since 5.0.16
1938 *
1939 * @param string $url
1940 * @param int $form_id
1941 *
1942 * @return string
1943 */
1944 public static function maybe_add_sanitize_url_attr( $url, $form_id ) {
1945 if ( ! str_contains( $url, '[' ) ) {
1946 // Do nothing if no shortcodes are detected.
1947 return $url;
1948 }
1949
1950 $parsed = wp_parse_url( $url );
1951
1952 if ( empty( $parsed['query'] ) ) {
1953 // Do nothing if no query can be detected in the url string.
1954 return $url;
1955 }
1956
1957 $original_query = $parsed['query'];
1958 $query = $parsed['query'];
1959 $shortcodes = FrmFieldsHelper::get_shortcodes( $query, $form_id );
1960
1961 if ( empty( $shortcodes[0] ) ) {
1962 // No shortcodes found, do nothing.
1963 return $url;
1964 }
1965
1966 foreach ( $shortcodes[0] as $key => $shortcode ) {
1967 $options = trim( $shortcodes[3][ $key ] );
1968
1969 if ( in_array( $shortcodes[1][ $key ], array( 'if ' ), true ) ) {
1970 // Skip if shortcodes.
1971 continue;
1972 }
1973
1974 if ( str_contains( $options, 'sanitize_url=' ) || str_contains( $options, 'sanitize=' ) ) {
1975 // A sanitize option is already set so leave it alone.
1976 continue;
1977 }
1978
1979 $new_shortcode = '[' . $shortcodes[2][ $key ];
1980
1981 if ( $options ) {
1982 $new_shortcode .= ' ' . $options;
1983 }
1984
1985 $new_shortcode .= ' sanitize_url=1]';
1986 $query = str_replace( $shortcode, $new_shortcode, $query );
1987 }//end foreach
1988
1989 if ( $query === $original_query ) {
1990 return $url;
1991 }
1992
1993 return str_replace( $original_query, $query, $url );
1994 }
1995
1996 /**
1997 * Outputs the appropriate button text in the publish box.
1998 *
1999 * @return void
2000 */
2001 public static function publish_box_button_text() {
2002 $is_new_template = FrmAppHelper::simple_get( 'new_template' );
2003 $action = FrmAppHelper::simple_get( 'frm_action' );
2004
2005 if ( ( 'edit' === $action || 'settings' === $action ) && $is_new_template ) {
2006 esc_html_e( 'Save', 'formidable' );
2007 } else {
2008 esc_html_e( 'Update', 'formidable' );
2009 }
2010 }
2011
2012 /**
2013 * Strip characters similar to the WordPress sanitize_html_class function, but allow for [ and ].
2014 * This allows shortcodes inside of the layout classes setting.
2015 *
2016 * @since 6.16
2017 *
2018 * @param string $classname
2019 *
2020 * @return string
2021 */
2022 public static function sanitize_layout_class( $classname ) {
2023 // Strip out any percent-encoded characters.
2024 $sanitized = preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $classname );
2025
2026 // Limit to A-Z, a-z, 0-9, '_', '-', '[', ']'.
2027 return preg_replace( '/[^A-Za-z0-9_\-\[\]]/', '', $sanitized );
2028 }
2029
2030 /**
2031 * Returns true if the preview should be blocked.
2032 *
2033 * @since 6.20
2034 *
2035 * @param string $form_key
2036 *
2037 * @return bool
2038 */
2039 public static function should_block_preview( $form_key ) {
2040 $should_block = in_array( $form_key, array( 'contact-form', 'contact-us' ), true ) && ! current_user_can( 'frm_view_forms' );
2041 /**
2042 * Filters whether the form preview should be blocked.
2043 *
2044 * @since 6.20
2045 *
2046 * @param bool $should_block
2047 * @param string $form_key
2048 */
2049 return (bool) apply_filters( 'frm_block_preview', $should_block, $form_key );
2050 }
2051
2052 /**
2053 * Checks if the form is loaded by API.
2054 *
2055 * @since 6.21
2056 *
2057 * @return bool
2058 */
2059 public static function form_is_loaded_by_api() {
2060 global $frm_vars;
2061
2062 if ( ! empty( $frm_vars['inplace_edit'] ) ) {
2063 return true;
2064 }
2065
2066 return self::is_formidable_api_form() || self::is_gutenberg_editor() || self::is_elementor_ajax() || self::is_visual_views_preview();
2067 }
2068
2069 /**
2070 * @since 6.21
2071 *
2072 * @return bool
2073 */
2074 private static function is_visual_views_preview() {
2075 return 'frm_views_process_box_preview' === FrmAppHelper::get_post_param( 'action' );
2076 }
2077
2078 /**
2079 * @since 6.21
2080 *
2081 * @return bool
2082 */
2083 private static function is_elementor_ajax() {
2084 return 'elementor_ajax' === FrmAppHelper::get_post_param( 'action' );
2085 }
2086
2087 /**
2088 * @since 6.21
2089 *
2090 * @return bool
2091 */
2092 private static function is_gutenberg_editor() {
2093 $url = FrmAppHelper::get_server_value( 'REQUEST_URI' );
2094
2095 if ( str_contains( $url, '/wp-json/wp/v2/block-renderer/formidable/simple-form' ) ) {
2096 return true;
2097 }
2098
2099 if ( str_contains( urldecode( $url ), 'rest_route=/wp/v2/block-renderer/formidable/' ) ) {
2100 return true;
2101 }
2102
2103 global $pagenow;
2104 return 'post.php' === $pagenow;
2105 }
2106
2107 /**
2108 * @since 6.21
2109 *
2110 * @return bool
2111 */
2112 private static function is_formidable_api_form() {
2113 if ( ! class_exists( 'FrmAPIAppController' ) ) {
2114 return false;
2115 }
2116
2117 $url = FrmAppHelper::get_server_value( 'REQUEST_URI' );
2118
2119 if ( str_contains( $url, '/wp-json/frm/v2/forms/' ) ) {
2120 // Prevent the honeypot from appearing for an API loaded form.
2121 // This is to prevent conflicts where the script is not working.
2122 return true;
2123 }
2124
2125 if ( is_callable( 'FrmProFormState::get_from_request' ) ) {
2126 $api = FrmProFormState::get_from_request( 'a', 0 );
2127
2128 if ( $api ) {
2129 return true;
2130 }
2131 }
2132
2133 return false;
2134 }
2135 }
2136