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

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