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

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