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

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

1,090 lines 29.1 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 FrmFieldsController {
7
8 /**
9 * This is stored statically so we can re-use this data for every field.
10 *
11 * @var FrmFieldSelectionData|null
12 */
13 private static $field_selection_data;
14
15 public static function load_field() {
16 FrmAppHelper::permission_check( 'frm_edit_forms' );
17 check_ajax_referer( 'frm_ajax', 'nonce' );
18
19 // Javascript may be included in some field settings.
20 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
21 $fields = isset( $_POST['field'] ) ? wp_unslash( $_POST['field'] ) : array();
22
23 if ( empty( $fields ) ) {
24 wp_die();
25 }
26
27 $_GET['page'] = 'formidable';
28
29 $values = array(
30 'id' => FrmAppHelper::get_post_param( 'form_id', '', 'absint' ),
31 'doing_ajax' => true,
32 );
33 $field_html = array();
34
35 foreach ( $fields as $field ) {
36 $field = htmlspecialchars_decode( nl2br( $field ) );
37 $field = json_decode( $field );
38
39 if ( ! isset( $field->id ) || ! is_numeric( $field->id ) ) {
40 // this field may have already been loaded
41 continue;
42 }
43
44 if ( ! isset( $field->value ) ) {
45 $field->value = '';
46 }
47 $field->field_options = json_decode( json_encode( $field->field_options ), true );
48 $field->options = json_decode( json_encode( $field->options ), true );
49 $field->default_value = json_decode( json_encode( $field->default_value ), true );
50
51 ob_start();
52 self::load_single_field( $field, $values );
53 $field_html[ absint( $field->id ) ] = ob_get_contents();
54 ob_end_clean();
55 }//end foreach
56
57 echo json_encode( $field_html );
58
59 wp_die();
60 }
61
62 /**
63 * Create a new field with ajax
64 */
65 public static function create() {
66 FrmAppHelper::permission_check( 'frm_edit_forms' );
67 check_ajax_referer( 'frm_ajax', 'nonce' );
68
69 $field_type = FrmAppHelper::get_post_param( 'field_type', '', 'sanitize_text_field' );
70 $form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );
71 $field_options = FrmAppHelper::get_post_param( 'field_options', array(), 'wp_kses_post' );
72
73 do_action( 'frm_before_create_field', $field_type, $form_id );
74
75 $field = self::include_new_field( $field_type, $form_id, $field_options );
76
77 // this hook will allow for multiple fields to be added at once
78 do_action( 'frm_after_field_created', $field, $form_id );
79
80 wp_die();
81 }
82
83 /**
84 * Set up and create a new field
85 *
86 * @param string $field_type
87 * @param int $form_id
88 * @param array $field_options
89 *
90 * @return array|false
91 */
92 public static function include_new_field( $field_type, $form_id, $field_options = array() ) {
93 $field_values = FrmFieldsHelper::setup_new_vars( $field_type, $form_id );
94
95 if ( ! empty( $field_options ) ) {
96 $field_values['field_options'] = array_merge( $field_values['field_options'], $field_options );
97 }
98
99 // When a new field is added to the form, flag it as draft and hide it from the front-end.
100 $field_values['field_options']['draft'] = 1;
101
102 /**
103 * @param array $field_values
104 */
105 $field_values = apply_filters( 'frm_before_field_created', $field_values );
106 $field_id = FrmField::create( $field_values );
107
108 if ( ! $field_id ) {
109 return false;
110 }
111
112 $field = self::get_field_array_from_id( $field_id );
113
114 $values = array();
115
116 if ( FrmAppHelper::pro_is_installed() ) {
117 $values['post_type'] = FrmProFormsHelper::post_type( $form_id );
118
119 $parent_form_id = FrmDb::get_var( 'frm_forms', array( 'id' => $form_id ), 'parent_form_id' );
120
121 if ( $parent_form_id ) {
122 $field['parent_form_id'] = $parent_form_id;
123 }
124 }
125
126 self::load_single_field( $field, $values, $form_id );
127
128 return $field;
129 }
130
131 public static function duplicate() {
132 FrmAppHelper::permission_check( 'frm_edit_forms' );
133 check_ajax_referer( 'frm_ajax', 'nonce' );
134
135 $field_id = FrmAppHelper::get_post_param( 'field_id', 0, 'absint' );
136 $form_id = FrmAppHelper::get_post_param( 'form_id', 0, 'absint' );
137
138 $new_field = FrmField::duplicate_single_field( $field_id, $form_id );
139
140 if ( is_array( $new_field ) && ! empty( $new_field['field_id'] ) ) {
141 self::load_single_field( $new_field['field_id'], $new_field['values'] );
142 }
143
144 wp_die();
145 }
146
147 /**
148 * @since 3.0
149 *
150 * @param int $field_id
151 *
152 * @return array
153 */
154 public static function get_field_array_from_id( $field_id ) {
155 $field = FrmField::getOne( $field_id );
156
157 return FrmFieldsHelper::setup_edit_vars( $field );
158 }
159
160 /**
161 * @since 3.0
162 *
163 * @param array|int|object $field_object
164 * @param array $values
165 * @param int $form_id
166 *
167 * @return void
168 */
169 public static function load_single_field( $field_object, $values, $form_id = 0 ) {
170 global $frm_vars;
171 $frm_vars['is_admin'] = true;
172
173 if ( is_numeric( $field_object ) ) {
174 $field_object = FrmField::getOne( $field_object );
175 } elseif ( is_array( $field_object ) ) {
176 $field = $field_object;
177 $field_object = FrmField::getOne( $field['id'] );
178 }
179
180 $field_obj = FrmFieldFactory::get_field_factory( $field_object );
181 $display = self::display_field_options( array(), $field_obj );
182
183 $ajax_loading = ! empty( $values['ajax_load'] );
184 $ajax_this_field = isset( $values['count'] ) && $values['count'] > 10 && ! in_array( $field_object->type, array( 'divider', 'end_divider' ), true );
185
186 if ( $ajax_loading && $ajax_this_field ) {
187 $li_classes = self::get_classes_for_builder_field( array(), $display, $field_obj );
188 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/ajax-field-placeholder.php';
189 return;
190 }
191
192 if ( ! isset( $field ) && is_object( $field_object ) ) {
193 $field_object->parent_form_id = $values['id'] ?? $field_object->form_id;
194 $field = FrmFieldsHelper::setup_edit_vars( $field_object );
195 }
196
197 /**
198 * Filter for adding extra attributes to the field container.
199 *
200 * @since 6.23
201 *
202 * @param array $extra_field_attributes
203 * @param array $field
204 * @param array $display
205 */
206 $extra_field_attributes = apply_filters( 'frm_field_container_extra_attributes', array(), $field, $display );
207
208 $li_classes = self::get_classes_for_builder_field( $field, $display, $field_obj );
209 $li_classes .= ' ui-state-default widgets-holder-wrap';
210
211 require FrmAppHelper::plugin_path() . '/classes/views/frm-forms/add_field.php';
212 }
213
214 /**
215 * @since 3.0
216 *
217 * @param array $field
218 * @param array $display
219 * @param FrmFieldType $field_info
220 *
221 * @return string
222 */
223 private static function get_classes_for_builder_field( $field, $display, $field_info ) {
224 $li_classes = $field_info->form_builder_classes( $display['type'] );
225 $li_classes .= ' frm_form_field frmstart ';
226
227 if ( isset( $field['classes'] ) ) {
228 $li_classes .= trim( $field['classes'] ) . ' ';
229 }
230
231 $li_classes .= 'frmend';
232
233 if ( $field ) {
234 $li_classes = apply_filters( 'frm_build_field_class', $li_classes, $field );
235 }
236
237 return $li_classes;
238 }
239
240 public static function destroy() {
241 FrmAppHelper::permission_check( 'frm_edit_forms' );
242 check_ajax_referer( 'frm_ajax', 'nonce' );
243
244 $field_id = FrmAppHelper::get_post_param( 'field_id', 0, 'absint' );
245 FrmField::destroy( $field_id );
246 wp_die();
247 }
248
249 /**
250 * Field Options.
251 */
252 public static function import_options() {
253 FrmAppHelper::permission_check( 'frm_edit_forms' );
254 check_ajax_referer( 'frm_ajax', 'nonce' );
255
256 if ( ! is_admin() || ! current_user_can( 'frm_edit_forms' ) ) {
257 return;
258 }
259
260 $field_id = FrmAppHelper::get_param( 'field_id', '', 'post', 'absint' );
261 $field = FrmField::getOne( $field_id );
262 $bulk_edit_types = array( 'radio', 'checkbox', 'select' );
263
264 /**
265 * Filter to add new fields that will support import_options/Bulk Edit Options.
266 *
267 * @since 6.8.4
268 *
269 * @param array $bulk_edit_types
270 *
271 * @return array
272 */
273 $bulk_edit_types = apply_filters( 'frm_bulk_edit_field_types', $bulk_edit_types );
274
275 if ( ! in_array( $field->type, $bulk_edit_types, true ) ) {
276 return;
277 }
278
279 $field = FrmFieldsHelper::setup_edit_vars( $field );
280 $opts = FrmAppHelper::get_param( 'opts', '', 'post', 'wp_kses_post' );
281 $opts = explode( "\n", rtrim( $opts, "\n" ) );
282 $opts = array_map( 'trim', $opts );
283
284 $separate = FrmAppHelper::get_param( 'separate', '', 'post', 'sanitize_text_field' );
285 $field['separate_value'] = $separate === 'true';
286
287 if ( $field['separate_value'] ) {
288 foreach ( $opts as $opt_key => $opt ) {
289 if ( strpos( $opt, '|' ) !== false ) {
290 $vals = explode( '|', $opt );
291 $opts[ $opt_key ] = array(
292 'label' => trim( $vals[0] ),
293 'value' => trim( $vals[1] ),
294 );
295 unset( $vals );
296 }
297 unset( $opt_key, $opt );
298 }
299 }
300
301 // Keep other options after bulk update.
302 if ( isset( $field['field_options']['other'] ) && $field['field_options']['other'] == true ) {
303 $other_array = array();
304
305 foreach ( $field['options'] as $opt_key => $opt ) {
306 if ( FrmFieldsHelper::is_other_opt( $opt_key ) ) {
307 $other_array[ $opt_key ] = $opt;
308 }
309 unset( $opt_key, $opt );
310 }
311
312 if ( ! empty( $other_array ) ) {
313 $opts = array_merge( $opts, $other_array );
314 }
315 }
316
317 $field['options'] = $opts;
318
319 FrmFieldsHelper::show_single_option( $field );
320
321 wp_die();
322 }
323
324 /**
325 * @since 4.0
326 *
327 * @param array $atts - Includes field array, field_obj, display array, values array.
328 *
329 * @return void
330 */
331 public static function load_single_field_settings( $atts ) {
332 $field = $atts['field'];
333 $field_obj = $atts['field_obj'];
334 $values = $atts['values'];
335 $display = $atts['display'];
336 unset( $atts );
337
338 if ( ! isset( $field['unique'] ) ) {
339 $field['unique'] = false;
340 }
341
342 if ( ! isset( $field['read_only'] ) ) {
343 $field['read_only'] = false;
344 }
345
346 $field_selection_data = self::maybe_define_field_selection_data();
347 $all_field_types = $field_selection_data->all_field_types;
348 $disabled_fields = $field_selection_data->disabled_fields;
349 $frm_settings = FrmAppHelper::get_settings();
350 $field_types = FrmFieldTypeOptionData::get_field_types( $field['type'] );
351
352 if ( ! isset( $all_field_types[ $field['type'] ] ) ) {
353 // Add fallback for an add-on field type that has been deactivated.
354 $all_field_types[ $field['type'] ] = array(
355 'name' => ucfirst( $field['type'] ),
356 'icon' => 'frm_icon_font frm_pencil_icon',
357 );
358 } elseif ( ! is_array( $all_field_types[ $field['type'] ] ) ) {
359 // Fallback for fields added in a more basic way.
360 FrmFormsHelper::prepare_field_type( $all_field_types[ $field['type'] ] );
361 }
362
363 $type_name = $all_field_types[ $field['type'] ]['name'];
364
365 if ( $field['type'] === 'divider' && FrmField::is_option_true( $field, 'repeat' ) ) {
366 $type_name = $all_field_types['divider|repeat']['name'];
367 }
368
369 if ( $display['default'] ) {
370 $default_value_types = self::default_value_types( $field, compact( 'display' ) );
371 }
372
373 if ( $display['clear_on_focus'] && is_array( $field['placeholder'] ) ) {
374 $field['placeholder'] = implode( ', ', $field['placeholder'] );
375 }
376
377 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/settings.php';
378 }
379
380 /**
381 * @since 6.9.1
382 *
383 * @return FrmFieldSelectionData
384 */
385 private static function maybe_define_field_selection_data() {
386 if ( ! isset( self::$field_selection_data ) ) {
387 self::$field_selection_data = new FrmFieldSelectionData();
388 }
389 return self::$field_selection_data;
390 }
391
392 /**
393 * Get the list of default value types that can be toggled in the builder.
394 *
395 * @since 4.0
396 *
397 * @param array $field
398 * @param array $atts
399 *
400 * @return array
401 */
402 private static function default_value_types( $field, $atts ) {
403 $types = array(
404 'default_value' => array(
405 'class' => '',
406 'icon' => 'frm_icon_font frm_text2_icon',
407 'title' => __( 'Default Value', 'formidable' ),
408 'data' => array(
409 'frmshow' => '#default-value-for-',
410 ),
411 ),
412 'calc' => array(
413 'class' => 'frm_show_upgrade frm_noallow',
414 'title' => __( 'Calculate Value', 'formidable' ),
415 'icon' => 'frm_icon_font frm_calculator_icon',
416 'data' => array(
417 'medium' => 'calculations',
418 'upgrade' => __( 'Calculator forms', 'formidable' ),
419 ),
420 'tooltip' => __( 'Automatically calculate the value of this field based on values from other fields.', 'formidable' ),
421 ),
422 'get_values_field' => array(
423 'class' => 'frm_show_upgrade frm_noallow',
424 'title' => __( 'Lookup', 'formidable' ),
425 'icon' => 'frm_icon_font frm_search_icon',
426 'data' => array(
427 'medium' => 'lookup',
428 'upgrade' => __( 'Lookup fields', 'formidable' ),
429 ),
430 'tooltip' => __( 'Dynamically retrieve the value of this field from a lookup field.', 'formidable' ),
431 ),
432 );
433
434 $types = apply_filters( 'frm_default_value_types', $types, $atts );
435
436 // Set active class.
437 $settings = array_keys( $types );
438 $active = 'default_value';
439
440 if ( FrmAppHelper::pro_is_connected() ) {
441 foreach ( $settings as $type ) {
442 if ( ! empty( $field[ $type ] ) ) {
443 $active = $type;
444 }
445 }
446 }
447
448 $types[ $active ]['class'] .= ' current';
449 $types[ $active ]['current'] = true;
450
451 return $types;
452 }
453
454 /**
455 * @param string $type
456 *
457 * @return string
458 */
459 public static function change_type( $type ) {
460 $type_switch = array(
461 'scale' => 'radio',
462 'star' => 'radio',
463 '10radio' => 'radio',
464 'rte' => 'textarea',
465 'website' => 'url',
466 'image' => 'url',
467 );
468
469 if ( isset( $type_switch[ $type ] ) ) {
470 $type = $type_switch[ $type ];
471 }
472
473 $pro_fields = FrmField::pro_field_selection();
474 // We want to keep credit_card types as credit card types for Stripe Lite.
475 // The credit_card key is set for backward compatibility.
476 unset( $pro_fields['credit_card'] );
477
478 if ( array_key_exists( $type, $pro_fields ) ) {
479 $type = 'text';
480 }
481
482 return $type;
483 }
484
485 /**
486 * @param array $settings
487 * @param FrmFieldType|null $field_info
488 *
489 * @return array
490 */
491 public static function display_field_options( $settings, $field_info = null ) {
492 if ( $field_info ) {
493 $settings = $field_info->display_field_settings();
494
495 // Field appears to be protected but there is a __get function in FrmFieldType that makes this public.
496 $settings['field_data'] = $field_info->field;
497 }
498
499 return apply_filters( 'frm_display_field_options', $settings );
500 }
501
502 /**
503 * Display the format option
504 *
505 * @since 3.0
506 *
507 * @param array $field Field data.
508 * @param bool $is_hidden Whether the format option should be hidden.
509 *
510 * @return void
511 */
512 public static function show_format_option( $field, $is_hidden = false ) {
513 $attributes = array(
514 'class' => 'frm-has-modal',
515 'id' => 'frm-field-format-custom-' . $field['id'],
516 );
517
518 if ( $is_hidden ) {
519 $attributes['class'] .= ' frm_hidden';
520 }
521
522 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/value-format.php';
523 }
524
525 /**
526 * @param array $field
527 * @param bool $echo
528 *
529 * @return string
530 */
531 public static function input_html( $field, $echo = true ) {
532 $class = array();
533 self::add_input_classes( $field, $class );
534
535 $add_html = array();
536 self::add_html_size( $field, $add_html );
537 self::add_html_length( $field, $add_html );
538 self::add_html_placeholder( $field, $add_html, $class );
539 self::add_validation_messages( $field, $add_html );
540
541 $class = apply_filters( 'frm_field_classes', implode( ' ', $class ), $field );
542
543 FrmFormsHelper::add_html_attr( $class, 'class', $add_html );
544
545 self::add_shortcodes_to_html( $field, $add_html );
546 self::add_pattern_attribute( $field, $add_html );
547
548 $add_html = apply_filters( 'frm_field_extra_html', $add_html, $field );
549 $add_html = ' ' . implode( ' ', $add_html ) . ' ';
550
551 if ( $echo ) {
552 echo $add_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
553 }
554
555 return $add_html;
556 }
557
558 /**
559 * @param array $field
560 * @param array $class
561 *
562 * @return void
563 */
564 private static function add_input_classes( $field, array &$class ) {
565 if ( ! empty( $field['input_class'] ) ) {
566 $class[] = $field['input_class'];
567 }
568
569 if ( $field['type'] === 'hidden' || $field['type'] === 'user_id' ) {
570 return;
571 }
572
573 if ( isset( $field['size'] ) && $field['size'] > 0 ) {
574 $class[] = 'auto_width';
575 }
576 }
577
578 /**
579 * @param array $field
580 * @param array $add_html
581 *
582 * @return void
583 */
584 private static function add_html_size( $field, array &$add_html ) {
585 $size_fields = array(
586 'select',
587 'data',
588 'time',
589 'hidden',
590 'file',
591 'lookup',
592 );
593
594 if ( ! isset( $field['size'] ) || $field['size'] <= 0 || in_array( $field['type'], $size_fields, true ) ) {
595 return;
596 }
597
598 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
599 return;
600 }
601
602 if ( is_numeric( $field['size'] ) ) {
603 $field['size'] .= 'px';
604 }
605
606 $important = apply_filters( 'frm_use_important_width', 1, $field );
607 // Note: This inline styling must stay since we cannot realistically set a class for every possible field size.
608 $add_html['style'] = 'style="width:' . esc_attr( $field['size'] ) . ( $important ? ' !important' : '' ) . '"';
609
610 self::add_html_cols( $field, $add_html );
611 }
612
613 /**
614 * @param array $field
615 * @param array $add_html
616 *
617 * @return void
618 */
619 private static function add_html_cols( $field, array &$add_html ) {
620 if ( ! in_array( $field['type'], array( 'textarea', 'rte' ), true ) ) {
621 return;
622 }
623
624 // Convert to cols for textareas.
625 $calc = array(
626 '' => 9,
627 'px' => 9,
628 'rem' => 0.444,
629 'em' => 0.544,
630 );
631
632 // include "col" for valid html
633 $unit = trim( preg_replace( '/[0-9]+/', '', $field['size'] ) );
634
635 if ( ! isset( $calc[ $unit ] ) ) {
636 return;
637 }
638
639 $size = (float) str_replace( $unit, '', $field['size'] ) / $calc[ $unit ];
640
641 $add_html['cols'] = 'cols="' . absint( $size ) . '"';
642 }
643
644 /**
645 * @param array $field
646 * @param array $add_html
647 *
648 * @return void
649 */
650 private static function add_html_length( $field, array &$add_html ) {
651 // Check for max setting and if this field accepts maxlength.
652 $fields = array(
653 'textarea',
654 'rte',
655 'hidden',
656 'file',
657 );
658
659 if ( FrmField::is_option_empty( $field, 'max' ) || in_array( $field['type'], $fields, true ) ) {
660 return;
661 }
662
663 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
664 // Don't load on form builder page.
665 return;
666 }
667
668 $add_html['maxlength'] = 'maxlength="' . esc_attr( $field['max'] ) . '"';
669 }
670
671 /**
672 * @param array $field
673 * @param array $add_html
674 * @param array $class
675 *
676 * @return void
677 */
678 private static function add_html_placeholder( $field, array &$add_html, array &$class ) {
679 if ( $field['default_value'] != '' ) {
680 if ( is_array( $field['default_value'] ) ) {
681 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( json_encode( $field['default_value'] ) ) . '"';
682 } else {
683 self::add_frmval_to_input( $field, $add_html );
684 }
685 }
686
687 $field['placeholder'] = self::prepare_placeholder( $field );
688
689 if ( $field['placeholder'] == '' || is_array( $field['placeholder'] ) ) {
690 // don't include a json placeholder
691 return;
692 }
693
694 self::add_placeholder_to_input( $field, $add_html );
695 }
696
697 /**
698 * Prepares field placeholder.
699 *
700 * @since 5.4 This doesn't call `FrmFieldsController::get_default_value_from_name()` anymore.
701 *
702 * @param array $field Field array.
703 *
704 * @return string
705 */
706 private static function prepare_placeholder( $field ) {
707 $placeholder = $field['placeholder'] ?? '';
708
709 return $placeholder;
710 }
711
712 /**
713 * If the label position is "inside",
714 * get the label to use as the placeholder
715 *
716 * @since 2.05
717 * @since 5.4 Remove the logic code for "inside" label position.
718 *
719 * @param array $field
720 *
721 * @return string
722 */
723 public static function get_default_value_from_name( $field ) {
724 return '';
725 }
726
727 /**
728 * Maybe add a blank placeholder option before any options
729 * in a dropdown.
730 *
731 * @since 4.04
732 *
733 * @param array|object $field
734 *
735 * @return bool True if placeholder was added.
736 */
737 public static function add_placeholder_to_select( $field ) {
738 $placeholder = FrmField::get_option( $field, 'placeholder' );
739
740 if ( empty( $placeholder ) ) {
741 $placeholder = self::get_default_value_from_name( $field );
742 }
743
744 $use_placeholder = $placeholder;
745 $autocomplete = FrmField::get_option( $field, 'autocom' );
746
747 if ( $autocomplete ) {
748 $use_chosen = ! is_callable( 'FrmProAppHelper::use_chosen_js' ) || FrmProAppHelper::use_chosen_js();
749
750 if ( $use_chosen ) {
751 $use_placeholder = '';
752 }
753 }
754
755 if ( $placeholder !== '' ) {
756 $placeholder_attributes = array(
757 'class' => 'frm-select-placeholder',
758 'value' => '',
759 'data-placeholder' => 'true',
760 );
761
762 FrmHtmlHelper::echo_dropdown_option( $use_placeholder, false, $placeholder_attributes );
763 return true;
764 }
765
766 return false;
767 }
768
769 /**
770 * Use HTML5 placeholder with js fallback.
771 *
772 * @param array $field
773 * @param array $add_html
774 *
775 * @return void
776 */
777 private static function add_placeholder_to_input( $field, &$add_html ) {
778 if ( FrmFieldsHelper::is_placeholder_field_type( $field['type'] ) ) {
779 $add_html['placeholder'] = 'placeholder="' . esc_attr( $field['placeholder'] ) . '"';
780 }
781 }
782
783 /**
784 * @param array $field
785 * @param array $add_html
786 *
787 * @return void
788 */
789 private static function add_frmval_to_input( $field, &$add_html ) {
790 if ( $field['placeholder'] != '' ) {
791 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['placeholder'] ) . '"';
792
793 if ( 'select' === $field['type'] ) {
794 $add_html['data-frmplaceholder'] = 'data-frmplaceholder="' . esc_attr( $field['placeholder'] ) . '"';
795 }
796 }
797
798 if ( $field['default_value'] != '' ) {
799 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['default_value'] ) . '"';
800 }
801 }
802
803 /**
804 * @param array $field
805 * @param array $add_html
806 *
807 * @return void
808 */
809 private static function add_validation_messages( $field, array &$add_html ) {
810 $field_validation_messages_status = self::get_validation_data_attribute_visibility_info( $field );
811
812 if ( FrmField::is_required( $field ) && ! empty( $field_validation_messages_status['data-reqmsg'] ) ) {
813 $required_message = FrmFieldsHelper::get_error_msg( $field, 'blank' );
814 $add_html['data-reqmsg'] = 'data-reqmsg="' . esc_attr( $required_message ) . '"';
815 self::maybe_add_html_required( $field, $add_html );
816 }
817
818 if ( ! FrmField::is_option_empty( $field, 'invalid' ) && ! empty( $field_validation_messages_status['data-invmsg'] ) ) {
819 $invalid_message = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
820 $add_html['data-invmsg'] = 'data-invmsg="' . esc_attr( $invalid_message ) . '"';
821 }
822
823 if ( ! empty( $add_html['data-reqmsg'] ) || ! empty( $add_html['data-invmsg'] ) ) {
824 self::maybe_add_error_html_for_js_validation( $field, $add_html );
825 }
826 }
827
828 /**
829 * Returns an array that contains field validation messages status.
830 *
831 * @since 6.9
832 *
833 * @param array|object $field
834 *
835 * @return array
836 */
837 private static function get_validation_data_attribute_visibility_info( $field ) {
838 if ( FrmField::get_field_type( $field ) === 'hidden' ) {
839 $field_validation_data_attributes = array(
840 'data-invmsg' => false,
841 'data-reqmsg' => false,
842 );
843 } else {
844 $field_validation_data_attributes = array(
845 'data-invmsg' => true,
846 'data-reqmsg' => true,
847 );
848 }
849
850 /**
851 * Allows controlling which field validation messages would be included in the field html.
852 *
853 * @since 6.9
854 *
855 * @param array $field_validation_messages_status
856 * @param array|object $field
857 */
858 return apply_filters( 'frm_field_validation_include_data_attributes', $field_validation_data_attributes, $field );
859 }
860
861 /**
862 * @since 5.0.03
863 *
864 * @param array $field
865 * @param array $add_html
866 *
867 * @return void
868 */
869 private static function maybe_add_error_html_for_js_validation( $field, array &$add_html ) {
870 $form = self::get_form_for_js_validation( $field );
871
872 if ( false === $form ) {
873 return;
874 }
875
876 $error_body = self::pull_custom_error_body_from_custom_html( $form, $field );
877
878 if ( false !== $error_body ) {
879 $error_body = urlencode( $error_body );
880 $add_html['data-error-html'] = 'data-error-html="' . esc_attr( $error_body ) . '"';
881 }
882 }
883
884 /**
885 * @since 5.0.03
886 *
887 * @param array $field
888 *
889 * @return false|stdClass false if there is no form object found with JS validation active.
890 */
891 private static function get_form_for_js_validation( $field ) {
892 global $frm_vars;
893
894 if ( ! empty( $frm_vars['js_validate_forms'] ) ) {
895 if ( isset( $frm_vars['js_validate_forms'][ $field['form_id'] ] ) ) {
896 return $frm_vars['js_validate_forms'][ $field['form_id'] ];
897 }
898
899 if ( ! empty( $field['parent_form_id'] ) && isset( $frm_vars['js_validate_forms'][ $field['parent_form_id'] ] ) ) {
900 return $frm_vars['js_validate_forms'][ $field['parent_form_id'] ];
901 }
902 }
903 return false;
904 }
905
906 /**
907 * @param stdClass $form
908 * @param array $field
909 * @param array $errors
910 *
911 * @return false|string
912 */
913 public static function pull_custom_error_body_from_custom_html( $form, $field, $errors = array() ) {
914 if ( empty( $field['custom_html'] ) ) {
915 return false;
916 }
917
918 $custom_html = $field['custom_html'];
919 $custom_html = apply_filters( 'frm_before_replace_shortcodes', $custom_html, $field, $errors, $form );
920
921 $start = strpos( $custom_html, '[if error]' );
922
923 if ( false === $start ) {
924 return false;
925 }
926
927 $end = strpos( $custom_html, '[/if error]' );
928
929 if ( false === $end || $end < $start ) {
930 return false;
931 }
932
933 $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
934 $default_html = array(
935 '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
936 '<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>',
937 '<div class="frm_error">[error]</div>',
938 '<div class="frm_error" role="alert">[error]</div>',
939 );
940
941 if ( in_array( $error_body, $default_html, true ) ) {
942 return false;
943 }
944
945 return $error_body;
946 }
947
948 /**
949 * If 'required' is added to a conditionally hidden field, the form won't
950 * submit in many browsers. Check to make sure the javascript to conditionally
951 * remove it is present if needed.
952 *
953 * @since 3.06.01
954 *
955 * @param array $field
956 * @param array $add_html
957 *
958 * @return void
959 */
960 private static function maybe_add_html_required( $field, array &$add_html ) {
961 $excluded_field_types =
962 FrmField::is_radio( $field ) ||
963 FrmField::is_checkbox( $field ) ||
964 FrmField::is_field_type( $field, 'file' ) ||
965 FrmField::is_field_type( $field, 'nps' ) ||
966 FrmField::is_field_type( $field, 'scale' );
967
968 if ( $excluded_field_types ) {
969 return;
970 }
971
972 $add_html['aria-required'] = 'aria-required="true"';
973 }
974
975 /**
976 * @param array $field
977 * @param array $add_html
978 *
979 * @return void
980 */
981 private static function add_shortcodes_to_html( $field, array &$add_html ) {
982 if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
983 return;
984 }
985
986 if ( ! empty( $field['autocomplete'] ) ) {
987 unset( $field['shortcodes']['autocomplete'] );
988 }
989
990 foreach ( $field['shortcodes'] as $k => $v ) {
991 if ( isset( $field['subfield_name'] ) && 0 === strpos( $k, 'aria-invalid' ) ) {
992 $subfield_name = $field['subfield_name'];
993
994 if ( ! isset( $field['shortcodes'][ 'aria-invalid-' . $subfield_name ] ) ) {
995 continue;
996 }
997 // Change the key to the correct aria-invalid value so that $add_html is set correctly for the current subfield of a combo field.
998 $k = 'aria-invalid';
999 $v = $field['shortcodes'][ 'aria-invalid-' . $subfield_name ];
1000 unset( $field['shortcodes'][ 'aria-invalid-' . $subfield_name ] );
1001 }
1002
1003 if ( 'opt' === $k || ! self::should_allow_input_attribute( $k ) ) {
1004 continue;
1005 }
1006
1007 if ( is_numeric( $k ) && strpos( $v, '=' ) ) {
1008 $add_html[] = $v;
1009 } elseif ( ! empty( $k ) && isset( $add_html[ $k ] ) ) {
1010 $add_html[ $k ] = str_replace( $k . '="', $k . '="' . $v, $add_html[ $k ] );
1011 } else {
1012 $add_html[ $k ] = $k . '="' . esc_attr( $v ) . '"';
1013 }
1014
1015 unset( $k, $v );
1016 }//end foreach
1017 }
1018
1019 /**
1020 * Disallow possibly unsafe attributees (that trigger JavaScript) when unasfe HTML is not allowed.
1021 *
1022 * @since 6.11.2
1023 *
1024 * @param string $key The option key.
1025 *
1026 * @return bool
1027 */
1028 private static function should_allow_input_attribute( $key ) {
1029 if ( ! FrmAppHelper::should_never_allow_unfiltered_html() ) {
1030 return true;
1031 }
1032 return FrmAppHelper::input_key_is_safe( $key );
1033 }
1034
1035 /**
1036 * Add pattern attribute.
1037 *
1038 * @since 3.0
1039 *
1040 * @param array $field
1041 * @param array $add_html
1042 *
1043 * @return void
1044 */
1045 private static function add_pattern_attribute( $field, array &$add_html ) {
1046 $format_value = FrmField::get_option( $field, 'format' );
1047 $has_format = $format_value && ! FrmCurrencyHelper::is_currency_format( $format_value );
1048 $format_field = FrmField::is_field_type( $field, 'text' );
1049
1050 if ( $field['type'] === 'phone' || ( $has_format && $format_field ) ) {
1051 $format = FrmEntryValidate::phone_format( $field );
1052 $format = substr( $format, 2, - 1 );
1053
1054 $add_html['pattern'] = 'pattern="' . esc_attr( $format ) . '"';
1055 }
1056 }
1057
1058 /**
1059 * @param mixed $opt
1060 * @param mixed $opt_key
1061 * @param array|object $field
1062 *
1063 * @return mixed
1064 */
1065 public static function check_value( $opt, $opt_key, $field ) {
1066 if ( is_array( $opt ) ) {
1067 if ( FrmField::is_option_true( $field, 'separate_value' ) ) {
1068 $opt = $opt['value'] ?? $opt['label'] ?? reset( $opt );
1069 } else {
1070 $opt = $opt['label'] ?? reset( $opt );
1071 }
1072 }
1073
1074 return $opt;
1075 }
1076
1077 /**
1078 * @param mixed $opt
1079 *
1080 * @return mixed
1081 */
1082 public static function check_label( $opt ) {
1083 if ( is_array( $opt ) ) {
1084 $opt = $opt['label'] ?? reset( $opt );
1085 }
1086
1087 return $opt;
1088 }
1089 }
1090