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

938 lines 26.4 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|bool
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 private static function get_classes_for_builder_field( $field, $display, $field_info ) {
195 $li_classes = $field_info->form_builder_classes( $display['type'] );
196 $li_classes .= ' frm_form_field frmstart ';
197
198 if ( isset( $field['classes'] ) ) {
199 $li_classes .= trim( $field['classes'] ) . ' ';
200 }
201
202 $li_classes .= 'frmend';
203
204 if ( $field ) {
205 $li_classes = apply_filters( 'frm_build_field_class', $li_classes, $field );
206 }
207
208 return $li_classes;
209 }
210
211 public static function destroy() {
212 FrmAppHelper::permission_check( 'frm_edit_forms' );
213 check_ajax_referer( 'frm_ajax', 'nonce' );
214
215 $field_id = FrmAppHelper::get_post_param( 'field_id', 0, 'absint' );
216 FrmField::destroy( $field_id );
217 wp_die();
218 }
219
220 /**
221 * Field Options.
222 */
223 public static function import_options() {
224 FrmAppHelper::permission_check( 'frm_edit_forms' );
225 check_ajax_referer( 'frm_ajax', 'nonce' );
226
227 if ( ! is_admin() || ! current_user_can( 'frm_edit_forms' ) ) {
228 return;
229 }
230
231 $field_id = FrmAppHelper::get_param( 'field_id', '', 'post', 'absint' );
232 $field = FrmField::getOne( $field_id );
233 $bulk_edit_types = array( 'radio', 'checkbox', 'select' );
234
235 /**
236 * Filter to add new fields that will support import_options/Bulk Edit Options.
237 *
238 * @since 6.8.4
239 *
240 * @param array $bulk_edit_types
241 * @return array
242 */
243 $bulk_edit_types = apply_filters( 'frm_bulk_edit_field_types', $bulk_edit_types );
244
245 if ( ! in_array( $field->type, $bulk_edit_types, true ) ) {
246 return;
247 }
248
249 $field = FrmFieldsHelper::setup_edit_vars( $field );
250 $opts = FrmAppHelper::get_param( 'opts', '', 'post', 'wp_kses_post' );
251 $opts = explode( "\n", rtrim( $opts, "\n" ) );
252 $opts = array_map( 'trim', $opts );
253
254 $separate = FrmAppHelper::get_param( 'separate', '', 'post', 'sanitize_text_field' );
255 $field['separate_value'] = ( $separate === 'true' );
256
257 if ( $field['separate_value'] ) {
258 foreach ( $opts as $opt_key => $opt ) {
259 if ( strpos( $opt, '|' ) !== false ) {
260 $vals = explode( '|', $opt );
261 $opts[ $opt_key ] = array(
262 'label' => trim( $vals[0] ),
263 'value' => trim( $vals[1] ),
264 );
265 unset( $vals );
266 }
267 unset( $opt_key, $opt );
268 }
269 }
270
271 // Keep other options after bulk update.
272 if ( isset( $field['field_options']['other'] ) && $field['field_options']['other'] == true ) {
273 $other_array = array();
274 foreach ( $field['options'] as $opt_key => $opt ) {
275 if ( FrmFieldsHelper::is_other_opt( $opt_key ) ) {
276 $other_array[ $opt_key ] = $opt;
277 }
278 unset( $opt_key, $opt );
279 }
280 if ( ! empty( $other_array ) ) {
281 $opts = array_merge( $opts, $other_array );
282 }
283 }
284
285 $field['options'] = $opts;
286
287 FrmFieldsHelper::show_single_option( $field );
288
289 wp_die();
290 }
291
292 /**
293 * @since 4.0
294 *
295 * @param array $atts - Includes field array, field_obj, display array, values array.
296 */
297 public static function load_single_field_settings( $atts ) {
298 $field = $atts['field'];
299 $field_obj = $atts['field_obj'];
300 $values = $atts['values'];
301 $display = $atts['display'];
302 unset( $atts );
303
304 if ( ! isset( $field['unique'] ) ) {
305 $field['unique'] = false;
306 }
307
308 if ( ! isset( $field['read_only'] ) ) {
309 $field['read_only'] = false;
310 }
311
312 $field_selection_data = self::maybe_define_field_selection_data();
313 $all_field_types = $field_selection_data->all_field_types;
314 $disabled_fields = $field_selection_data->disabled_fields;
315 $frm_settings = FrmAppHelper::get_settings();
316 $field_types = FrmFieldTypeOptionData::get_field_types( $field['type'] );
317
318 if ( ! isset( $all_field_types[ $field['type'] ] ) ) {
319 // Add fallback for an add-on field type that has been deactivated.
320 $all_field_types[ $field['type'] ] = array(
321 'name' => ucfirst( $field['type'] ),
322 'icon' => 'frm_icon_font frm_pencil_icon',
323 );
324 } elseif ( ! is_array( $all_field_types[ $field['type'] ] ) ) {
325 // Fallback for fields added in a more basic way.
326 FrmFormsHelper::prepare_field_type( $all_field_types[ $field['type'] ] );
327 }
328
329 $type_name = $all_field_types[ $field['type'] ]['name'];
330 if ( $field['type'] === 'divider' && FrmField::is_option_true( $field, 'repeat' ) ) {
331 $type_name = $all_field_types['divider|repeat']['name'];
332 }
333
334 if ( $display['default'] ) {
335 $default_value_types = self::default_value_types( $field, compact( 'display' ) );
336 }
337
338 if ( $display['clear_on_focus'] && is_array( $field['placeholder'] ) ) {
339 $field['placeholder'] = implode( ', ', $field['placeholder'] );
340 }
341
342 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/settings.php';
343 }
344
345 /**
346 * @since 6.9.1
347 *
348 * @return FrmFieldSelectionData
349 */
350 private static function maybe_define_field_selection_data() {
351 if ( ! isset( self::$field_selection_data ) ) {
352 self::$field_selection_data = new FrmFieldSelectionData();
353 }
354 return self::$field_selection_data;
355 }
356
357 /**
358 * Get the list of default value types that can be toggled in the builder.
359 *
360 * @since 4.0
361 *
362 * @param array $field
363 * @param array $atts
364 * @return array
365 */
366 private static function default_value_types( $field, $atts ) {
367 $types = array(
368 'default_value' => array(
369 'class' => '',
370 'icon' => 'frm_icon_font frm_text2_icon',
371 'title' => __( 'Default Value (Text)', 'formidable' ),
372 'data' => array(
373 'frmshow' => '#default-value-for-',
374 ),
375 ),
376 'calc' => array(
377 'class' => 'frm_show_upgrade frm_noallow',
378 'title' => __( 'Default Value (Calculation)', 'formidable' ),
379 'icon' => 'frm_icon_font frm_calculator_icon',
380 'data' => array(
381 'medium' => 'calculations',
382 'upgrade' => __( 'Calculator forms', 'formidable' ),
383 ),
384 ),
385 'get_values_field' => array(
386 'class' => 'frm_show_upgrade frm_noallow',
387 'title' => __( 'Default Value (Lookup)', 'formidable' ),
388 'icon' => 'frm_icon_font frm_search_icon',
389 'data' => array(
390 'medium' => 'lookup',
391 'upgrade' => __( 'Lookup fields', 'formidable' ),
392 ),
393 ),
394 );
395
396 $types = apply_filters( 'frm_default_value_types', $types, $atts );
397
398 // Set active class.
399 $settings = array_keys( $types );
400 $active = 'default_value';
401
402 foreach ( $settings as $type ) {
403 if ( ! empty( $field[ $type ] ) ) {
404 $active = $type;
405 }
406 }
407
408 $types[ $active ]['class'] .= ' current';
409 $types[ $active ]['current'] = true;
410
411 return $types;
412 }
413
414 public static function change_type( $type ) {
415 $type_switch = array(
416 'scale' => 'radio',
417 'star' => 'radio',
418 '10radio' => 'radio',
419 'rte' => 'textarea',
420 'website' => 'url',
421 'image' => 'url',
422 );
423 if ( isset( $type_switch[ $type ] ) ) {
424 $type = $type_switch[ $type ];
425 }
426
427 $pro_fields = FrmField::pro_field_selection();
428 // We want to keep credit_card types as credit card types for Stripe Lite.
429 // The credit_card key is set for backward compatibility.
430 unset( $pro_fields['credit_card'] );
431
432 if ( array_key_exists( $type, $pro_fields ) ) {
433 $type = 'text';
434 }
435
436 return $type;
437 }
438
439 /**
440 * @param array $settings
441 * @param object $field_info
442 *
443 * @return array
444 */
445 public static function display_field_options( $settings, $field_info = null ) {
446 if ( $field_info ) {
447 $settings = $field_info->display_field_settings();
448 $settings['field_data'] = $field_info->field;
449 }
450
451 return apply_filters( 'frm_display_field_options', $settings );
452 }
453
454 /**
455 * Display the format option
456 *
457 * @since 3.0
458 *
459 * @param array $field
460 */
461 public static function show_format_option( $field ) {
462 $attributes = array();
463 $attributes['class'] = 'frm-has-modal';
464
465 if ( 'phone' === $field['type'] ) {
466 $attributes['id'] = 'frm-phone-field-custom-format-' . $field['id'];
467 $attributes['class'] .= ' frm_hidden';
468 }
469
470 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/value-format.php';
471 }
472
473 public static function input_html( $field, $echo = true ) {
474 $class = array();
475 self::add_input_classes( $field, $class );
476
477 $add_html = array();
478 self::add_html_size( $field, $add_html );
479 self::add_html_length( $field, $add_html );
480 self::add_html_placeholder( $field, $add_html, $class );
481 self::add_validation_messages( $field, $add_html );
482
483 $class = apply_filters( 'frm_field_classes', implode( ' ', $class ), $field );
484
485 FrmFormsHelper::add_html_attr( $class, 'class', $add_html );
486
487 self::add_shortcodes_to_html( $field, $add_html );
488 self::add_pattern_attribute( $field, $add_html );
489
490 $add_html = apply_filters( 'frm_field_extra_html', $add_html, $field );
491 $add_html = ' ' . implode( ' ', $add_html ) . ' ';
492
493 if ( $echo ) {
494 echo $add_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
495 }
496
497 return $add_html;
498 }
499
500 private static function add_input_classes( $field, array &$class ) {
501 if ( ! empty( $field['input_class'] ) ) {
502 $class[] = $field['input_class'];
503 }
504
505 if ( $field['type'] === 'hidden' || $field['type'] === 'user_id' ) {
506 return;
507 }
508
509 if ( isset( $field['size'] ) && $field['size'] > 0 ) {
510 $class[] = 'auto_width';
511 }
512 }
513
514 private static function add_html_size( $field, array &$add_html ) {
515 $size_fields = array(
516 'select',
517 'data',
518 'time',
519 'hidden',
520 'file',
521 'lookup',
522 );
523
524 if ( ! isset( $field['size'] ) || $field['size'] <= 0 || in_array( $field['type'], $size_fields, true ) ) {
525 return;
526 }
527
528 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
529 return;
530 }
531
532 if ( is_numeric( $field['size'] ) ) {
533 $field['size'] .= 'px';
534 }
535
536 $important = apply_filters( 'frm_use_important_width', 1, $field );
537 // Note: This inline styling must stay since we cannot realistically set a class for every possible field size.
538 $add_html['style'] = 'style="width:' . esc_attr( $field['size'] ) . ( $important ? ' !important' : '' ) . '"';
539
540 self::add_html_cols( $field, $add_html );
541 }
542
543 private static function add_html_cols( $field, array &$add_html ) {
544 if ( ! in_array( $field['type'], array( 'textarea', 'rte' ), true ) ) {
545 return;
546 }
547
548 // Convert to cols for textareas.
549 $calc = array(
550 '' => 9,
551 'px' => 9,
552 'rem' => 0.444,
553 'em' => 0.544,
554 );
555
556 // include "col" for valid html
557 $unit = trim( preg_replace( '/[0-9]+/', '', $field['size'] ) );
558
559 if ( ! isset( $calc[ $unit ] ) ) {
560 return;
561 }
562
563 $size = (float) str_replace( $unit, '', $field['size'] ) / $calc[ $unit ];
564
565 $add_html['cols'] = 'cols="' . absint( $size ) . '"';
566 }
567
568 private static function add_html_length( $field, array &$add_html ) {
569 // Check for max setting and if this field accepts maxlength.
570 $fields = array(
571 'textarea',
572 'rte',
573 'hidden',
574 'file',
575 );
576
577 if ( FrmField::is_option_empty( $field, 'max' ) || in_array( $field['type'], $fields, true ) ) {
578 return;
579 }
580
581 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
582 // Don't load on form builder page.
583 return;
584 }
585
586 $add_html['maxlength'] = 'maxlength="' . esc_attr( $field['max'] ) . '"';
587 }
588
589 private static function add_html_placeholder( $field, array &$add_html, array &$class ) {
590 if ( $field['default_value'] != '' ) {
591 if ( is_array( $field['default_value'] ) ) {
592 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( json_encode( $field['default_value'] ) ) . '"';
593 } else {
594 self::add_frmval_to_input( $field, $add_html );
595 }
596 }
597
598 $field['placeholder'] = self::prepare_placeholder( $field );
599 if ( $field['placeholder'] == '' || is_array( $field['placeholder'] ) ) {
600 // don't include a json placeholder
601 return;
602 }
603
604 $frm_settings = FrmAppHelper::get_settings();
605
606 if ( $frm_settings->use_html ) {
607 self::add_placeholder_to_input( $field, $add_html );
608 } else {
609 self::add_frmval_to_input( $field, $add_html );
610
611 $class[] = 'frm_toggle_default';
612
613 if ( $field['value'] == $field['placeholder'] ) {
614 $class[] = 'frm_default';
615 }
616 }
617 }
618
619 /**
620 * Prepares field placeholder.
621 *
622 * @since 5.4 This doesn't call `FrmFieldsController::get_default_value_from_name()` anymore.
623 *
624 * @param array $field Field array.
625 * @return string
626 */
627 private static function prepare_placeholder( $field ) {
628 $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
629
630 return $placeholder;
631 }
632
633 /**
634 * If the label position is "inside",
635 * get the label to use as the placeholder
636 *
637 * @since 2.05
638 * @since 5.4 Remove the logic code for "inside" label position.
639 *
640 * @param array $field
641 *
642 * @return string
643 */
644 public static function get_default_value_from_name( $field ) {
645 return '';
646 }
647
648 /**
649 * Maybe add a blank placeholder option before any options
650 * in a dropdown.
651 *
652 * @since 4.04
653 * @return bool True if placeholder was added.
654 */
655 public static function add_placeholder_to_select( $field ) {
656 $placeholder = FrmField::get_option( $field, 'placeholder' );
657 if ( empty( $placeholder ) ) {
658 $placeholder = self::get_default_value_from_name( $field );
659 }
660
661 $use_placeholder = $placeholder;
662 $autocomplete = FrmField::get_option( $field, 'autocom' );
663
664 if ( $autocomplete ) {
665 $use_chosen = ! is_callable( 'FrmProAppHelper::use_chosen_js' ) || FrmProAppHelper::use_chosen_js();
666 if ( $use_chosen ) {
667 $use_placeholder = '';
668 }
669 }
670
671 if ( $placeholder !== '' ) {
672 $placeholder_attributes = array(
673 'class' => 'frm-select-placeholder',
674 'value' => '',
675 'data-placeholder' => 'true',
676 );
677
678 if ( $autocomplete && empty( $use_chosen ) ) {
679 // This is required for Slim Select.
680 $placeholder_attributes['data-placeholder'] = 'true';
681 }
682
683 FrmHtmlHelper::echo_dropdown_option( $use_placeholder, false, $placeholder_attributes );
684 return true;
685 }
686
687 return false;
688 }
689
690 /**
691 * Use HMTL5 placeholder with js fallback
692 *
693 * @param array $field
694 * @param array $add_html
695 */
696 private static function add_placeholder_to_input( $field, &$add_html ) {
697 if ( FrmFieldsHelper::is_placeholder_field_type( $field['type'] ) ) {
698 $add_html['placeholder'] = 'placeholder="' . esc_attr( $field['placeholder'] ) . '"';
699 }
700 }
701
702 private static function add_frmval_to_input( $field, &$add_html ) {
703 if ( $field['placeholder'] != '' ) {
704 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['placeholder'] ) . '"';
705
706 if ( 'select' === $field['type'] ) {
707 $add_html['data-frmplaceholder'] = 'data-frmplaceholder="' . esc_attr( $field['placeholder'] ) . '"';
708 }
709 }
710
711 if ( $field['default_value'] != '' ) {
712 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['default_value'] ) . '"';
713 }
714 }
715
716 private static function add_validation_messages( $field, array &$add_html ) {
717 $field_validation_messages_status = self::get_validation_data_attribute_visibility_info( $field );
718
719 if ( FrmField::is_required( $field ) && ! empty( $field_validation_messages_status['data-reqmsg'] ) ) {
720 $required_message = FrmFieldsHelper::get_error_msg( $field, 'blank' );
721 $add_html['data-reqmsg'] = 'data-reqmsg="' . esc_attr( $required_message ) . '"';
722 self::maybe_add_html_required( $field, $add_html );
723 }
724
725 if ( ! FrmField::is_option_empty( $field, 'invalid' ) && ! empty( $field_validation_messages_status['data-invmsg'] ) ) {
726 $invalid_message = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
727 $add_html['data-invmsg'] = 'data-invmsg="' . esc_attr( $invalid_message ) . '"';
728 }
729
730 if ( ! empty( $add_html['data-reqmsg'] ) || ! empty( $add_html['data-invmsg'] ) ) {
731 self::maybe_add_error_html_for_js_validation( $field, $add_html );
732 }
733 }
734
735 /**
736 * Returns an array that contains field validation messages status.
737 *
738 * @since 6.9
739 *
740 * @param array|object $field
741 * @return array
742 */
743 private static function get_validation_data_attribute_visibility_info( $field ) {
744 if ( FrmField::get_field_type( $field ) === 'hidden' ) {
745 $field_validation_data_attributes = array(
746 'data-invmsg' => false,
747 'data-reqmsg' => false,
748 );
749 } else {
750 $field_validation_data_attributes = array(
751 'data-invmsg' => true,
752 'data-reqmsg' => true,
753 );
754 }
755
756 /**
757 * Allows controlling which field validation messages would be included in the field html.
758 *
759 * @since 6.9
760 *
761 * @param array $field_validation_messages_status
762 * @param array|object $field
763 */
764 return apply_filters( 'frm_field_validation_include_data_attributes', $field_validation_data_attributes, $field );
765 }
766
767 /**
768 * @since 5.0.03
769 *
770 * @param array $field
771 * @param array $add_html
772 */
773 private static function maybe_add_error_html_for_js_validation( $field, array &$add_html ) {
774 $form = self::get_form_for_js_validation( $field );
775 if ( false === $form ) {
776 return;
777 }
778
779 $error_body = self::pull_custom_error_body_from_custom_html( $form, $field );
780 if ( false !== $error_body ) {
781 $error_body = urlencode( $error_body );
782 $add_html['data-error-html'] = 'data-error-html="' . esc_attr( $error_body ) . '"';
783 }
784 }
785
786 /**
787 * @since 5.0.03
788 *
789 * @param array $field
790 * @return false|stdClass false if there is no form object found with JS validation active.
791 */
792 private static function get_form_for_js_validation( $field ) {
793 global $frm_vars;
794 if ( ! empty( $frm_vars['js_validate_forms'] ) ) {
795 if ( isset( $frm_vars['js_validate_forms'][ $field['form_id'] ] ) ) {
796 return $frm_vars['js_validate_forms'][ $field['form_id'] ];
797 }
798 if ( ! empty( $field['parent_form_id'] ) && isset( $frm_vars['js_validate_forms'][ $field['parent_form_id'] ] ) ) {
799 return $frm_vars['js_validate_forms'][ $field['parent_form_id'] ];
800 }
801 }
802 return false;
803 }
804
805 /**
806 * @param stdClass $form
807 * @param array $field
808 * @param array $errors
809 * @return false|string
810 */
811 public static function pull_custom_error_body_from_custom_html( $form, $field, $errors = array() ) {
812 if ( empty( $field['custom_html'] ) ) {
813 return false;
814 }
815
816 $custom_html = $field['custom_html'];
817 $custom_html = apply_filters( 'frm_before_replace_shortcodes', $custom_html, $field, $errors, $form );
818
819 $start = strpos( $custom_html, '[if error]' );
820 if ( false === $start ) {
821 return false;
822 }
823
824 $end = strpos( $custom_html, '[/if error]' );
825 if ( false === $end || $end < $start ) {
826 return false;
827 }
828
829 $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
830 $default_html = array(
831 '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
832 '<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>',
833 '<div class="frm_error">[error]</div>',
834 '<div class="frm_error" role="alert">[error]</div>',
835 );
836
837 if ( in_array( $error_body, $default_html, true ) ) {
838 return false;
839 }
840
841 return $error_body;
842 }
843
844 /**
845 * If 'required' is added to a conditionally hidden field, the form won't
846 * submit in many browsers. Check to make sure the javascript to conditionally
847 * remove it is present if needed.
848 *
849 * @since 3.06.01
850 * @param array $field
851 * @param array $add_html
852 */
853 private static function maybe_add_html_required( $field, array &$add_html ) {
854 $excluded_field_types =
855 FrmField::is_radio( $field ) ||
856 FrmField::is_checkbox( $field ) ||
857 FrmField::is_field_type( $field, 'file' ) ||
858 FrmField::is_field_type( $field, 'nps' ) ||
859 FrmField::is_field_type( $field, 'scale' );
860
861 if ( $excluded_field_types ) {
862 return;
863 }
864
865 $add_html['aria-required'] = 'aria-required="true"';
866 }
867
868 private static function add_shortcodes_to_html( $field, array &$add_html ) {
869 if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
870 return;
871 }
872
873 if ( ! empty( $field['autocomplete'] ) ) {
874 unset( $field['shortcodes']['autocomplete'] );
875 }
876
877 foreach ( $field['shortcodes'] as $k => $v ) {
878 if ( 'opt' === $k ) {
879 continue;
880 }
881
882 if ( is_numeric( $k ) && strpos( $v, '=' ) ) {
883 $add_html[] = $v;
884 } elseif ( ! empty( $k ) && isset( $add_html[ $k ] ) ) {
885 $add_html[ $k ] = str_replace( $k . '="', $k . '="' . $v, $add_html[ $k ] );
886 } else {
887 $add_html[ $k ] = $k . '="' . esc_attr( $v ) . '"';
888 }
889
890 unset( $k, $v );
891 }
892 }
893
894 /**
895 * Add pattern attribute
896 *
897 * @since 3.0
898 *
899 * @param array $field
900 * @param array $add_html
901 */
902 private static function add_pattern_attribute( $field, array &$add_html ) {
903 $has_format = FrmField::is_option_true_in_array( $field, 'format' );
904 $format_field = FrmField::is_field_type( $field, 'text' );
905
906 if ( $field['type'] === 'phone' || ( $has_format && $format_field ) ) {
907 $frm_settings = FrmAppHelper::get_settings();
908
909 if ( $frm_settings->use_html ) {
910 $format = FrmEntryValidate::phone_format( $field );
911 $format = substr( $format, 2, - 1 );
912
913 $add_html['pattern'] = 'pattern="' . esc_attr( $format ) . '"';
914 }
915 }
916 }
917
918 public static function check_value( $opt, $opt_key, $field ) {
919 if ( is_array( $opt ) ) {
920 if ( FrmField::is_option_true( $field, 'separate_value' ) ) {
921 $opt = isset( $opt['value'] ) ? $opt['value'] : ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
922 } else {
923 $opt = isset( $opt['label'] ) ? $opt['label'] : reset( $opt );
924 }
925 }
926
927 return $opt;
928 }
929
930 public static function check_label( $opt ) {
931 if ( is_array( $opt ) ) {
932 $opt = ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
933 }
934
935 return $opt;
936 }
937 }
938