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

998 lines 27.9 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 Field data.
472 * @param bool $is_hidden Whether the format option should be hidden.
473 * @return void
474 */
475 public static function show_format_option( $field, $is_hidden = false ) {
476 $attributes = array(
477 'class' => 'frm-has-modal',
478 'id' => 'frm-field-format-custom-' . $field['id'],
479 );
480
481 if ( $is_hidden ) {
482 $attributes['class'] .= ' frm_hidden';
483 }
484
485 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/value-format.php';
486 }
487
488 public static function input_html( $field, $echo = true ) {
489 $class = array();
490 self::add_input_classes( $field, $class );
491
492 $add_html = array();
493 self::add_html_size( $field, $add_html );
494 self::add_html_length( $field, $add_html );
495 self::add_html_placeholder( $field, $add_html, $class );
496 self::add_validation_messages( $field, $add_html );
497
498 $class = apply_filters( 'frm_field_classes', implode( ' ', $class ), $field );
499
500 FrmFormsHelper::add_html_attr( $class, 'class', $add_html );
501
502 self::add_shortcodes_to_html( $field, $add_html );
503 self::add_pattern_attribute( $field, $add_html );
504
505 $add_html = apply_filters( 'frm_field_extra_html', $add_html, $field );
506 $add_html = ' ' . implode( ' ', $add_html ) . ' ';
507
508 if ( $echo ) {
509 echo $add_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
510 }
511
512 return $add_html;
513 }
514
515 /**
516 * @param array $field
517 * @param array $class
518 * @return void
519 */
520 private static function add_input_classes( $field, array &$class ) {
521 if ( ! empty( $field['input_class'] ) ) {
522 $class[] = $field['input_class'];
523 }
524
525 if ( $field['type'] === 'hidden' || $field['type'] === 'user_id' ) {
526 return;
527 }
528
529 if ( isset( $field['size'] ) && $field['size'] > 0 ) {
530 $class[] = 'auto_width';
531 }
532 }
533
534 /**
535 * @param array $field
536 * @param array $add_html
537 * @return void
538 */
539 private static function add_html_size( $field, array &$add_html ) {
540 $size_fields = array(
541 'select',
542 'data',
543 'time',
544 'hidden',
545 'file',
546 'lookup',
547 );
548
549 if ( ! isset( $field['size'] ) || $field['size'] <= 0 || in_array( $field['type'], $size_fields, true ) ) {
550 return;
551 }
552
553 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
554 return;
555 }
556
557 if ( is_numeric( $field['size'] ) ) {
558 $field['size'] .= 'px';
559 }
560
561 $important = apply_filters( 'frm_use_important_width', 1, $field );
562 // Note: This inline styling must stay since we cannot realistically set a class for every possible field size.
563 $add_html['style'] = 'style="width:' . esc_attr( $field['size'] ) . ( $important ? ' !important' : '' ) . '"';
564
565 self::add_html_cols( $field, $add_html );
566 }
567
568 /**
569 * @param array $field
570 * @param array $add_html
571 * @return void
572 */
573 private static function add_html_cols( $field, array &$add_html ) {
574 if ( ! in_array( $field['type'], array( 'textarea', 'rte' ), true ) ) {
575 return;
576 }
577
578 // Convert to cols for textareas.
579 $calc = array(
580 '' => 9,
581 'px' => 9,
582 'rem' => 0.444,
583 'em' => 0.544,
584 );
585
586 // include "col" for valid html
587 $unit = trim( preg_replace( '/[0-9]+/', '', $field['size'] ) );
588
589 if ( ! isset( $calc[ $unit ] ) ) {
590 return;
591 }
592
593 $size = (float) str_replace( $unit, '', $field['size'] ) / $calc[ $unit ];
594
595 $add_html['cols'] = 'cols="' . absint( $size ) . '"';
596 }
597
598 /**
599 * @param array $field
600 * @param array $add_html
601 * @return void
602 */
603 private static function add_html_length( $field, array &$add_html ) {
604 // Check for max setting and if this field accepts maxlength.
605 $fields = array(
606 'textarea',
607 'rte',
608 'hidden',
609 'file',
610 );
611
612 if ( FrmField::is_option_empty( $field, 'max' ) || in_array( $field['type'], $fields, true ) ) {
613 return;
614 }
615
616 if ( FrmAppHelper::is_admin_page( 'formidable' ) ) {
617 // Don't load on form builder page.
618 return;
619 }
620
621 $add_html['maxlength'] = 'maxlength="' . esc_attr( $field['max'] ) . '"';
622 }
623
624 /**
625 * @param array $field
626 * @param array $add_html
627 * @param array $class
628 * @return void
629 */
630 private static function add_html_placeholder( $field, array &$add_html, array &$class ) {
631 if ( $field['default_value'] != '' ) {
632 if ( is_array( $field['default_value'] ) ) {
633 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( json_encode( $field['default_value'] ) ) . '"';
634 } else {
635 self::add_frmval_to_input( $field, $add_html );
636 }
637 }
638
639 $field['placeholder'] = self::prepare_placeholder( $field );
640 if ( $field['placeholder'] == '' || is_array( $field['placeholder'] ) ) {
641 // don't include a json placeholder
642 return;
643 }
644
645 self::add_placeholder_to_input( $field, $add_html );
646 }
647
648 /**
649 * Prepares field placeholder.
650 *
651 * @since 5.4 This doesn't call `FrmFieldsController::get_default_value_from_name()` anymore.
652 *
653 * @param array $field Field array.
654 * @return string
655 */
656 private static function prepare_placeholder( $field ) {
657 $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : '';
658
659 return $placeholder;
660 }
661
662 /**
663 * If the label position is "inside",
664 * get the label to use as the placeholder
665 *
666 * @since 2.05
667 * @since 5.4 Remove the logic code for "inside" label position.
668 *
669 * @param array $field
670 *
671 * @return string
672 */
673 public static function get_default_value_from_name( $field ) {
674 return '';
675 }
676
677 /**
678 * Maybe add a blank placeholder option before any options
679 * in a dropdown.
680 *
681 * @since 4.04
682 * @return bool True if placeholder was added.
683 */
684 public static function add_placeholder_to_select( $field ) {
685 $placeholder = FrmField::get_option( $field, 'placeholder' );
686 if ( empty( $placeholder ) ) {
687 $placeholder = self::get_default_value_from_name( $field );
688 }
689
690 $use_placeholder = $placeholder;
691 $autocomplete = FrmField::get_option( $field, 'autocom' );
692
693 if ( $autocomplete ) {
694 $use_chosen = ! is_callable( 'FrmProAppHelper::use_chosen_js' ) || FrmProAppHelper::use_chosen_js();
695 if ( $use_chosen ) {
696 $use_placeholder = '';
697 }
698 }
699
700 if ( $placeholder !== '' ) {
701 $placeholder_attributes = array(
702 'class' => 'frm-select-placeholder',
703 'value' => '',
704 'data-placeholder' => 'true',
705 );
706
707 FrmHtmlHelper::echo_dropdown_option( $use_placeholder, false, $placeholder_attributes );
708 return true;
709 }
710
711 return false;
712 }
713
714 /**
715 * Use HTML5 placeholder with js fallback.
716 *
717 * @param array $field
718 * @param array $add_html
719 * @return void
720 */
721 private static function add_placeholder_to_input( $field, &$add_html ) {
722 if ( FrmFieldsHelper::is_placeholder_field_type( $field['type'] ) ) {
723 $add_html['placeholder'] = 'placeholder="' . esc_attr( $field['placeholder'] ) . '"';
724 }
725 }
726
727 /**
728 * @param array $field
729 * @param array $add_html
730 * @return void
731 */
732 private static function add_frmval_to_input( $field, &$add_html ) {
733 if ( $field['placeholder'] != '' ) {
734 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['placeholder'] ) . '"';
735
736 if ( 'select' === $field['type'] ) {
737 $add_html['data-frmplaceholder'] = 'data-frmplaceholder="' . esc_attr( $field['placeholder'] ) . '"';
738 }
739 }
740
741 if ( $field['default_value'] != '' ) {
742 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['default_value'] ) . '"';
743 }
744 }
745
746 private static function add_validation_messages( $field, array &$add_html ) {
747 $field_validation_messages_status = self::get_validation_data_attribute_visibility_info( $field );
748
749 if ( FrmField::is_required( $field ) && ! empty( $field_validation_messages_status['data-reqmsg'] ) ) {
750 $required_message = FrmFieldsHelper::get_error_msg( $field, 'blank' );
751 $add_html['data-reqmsg'] = 'data-reqmsg="' . esc_attr( $required_message ) . '"';
752 self::maybe_add_html_required( $field, $add_html );
753 }
754
755 if ( ! FrmField::is_option_empty( $field, 'invalid' ) && ! empty( $field_validation_messages_status['data-invmsg'] ) ) {
756 $invalid_message = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
757 $add_html['data-invmsg'] = 'data-invmsg="' . esc_attr( $invalid_message ) . '"';
758 }
759
760 if ( ! empty( $add_html['data-reqmsg'] ) || ! empty( $add_html['data-invmsg'] ) ) {
761 self::maybe_add_error_html_for_js_validation( $field, $add_html );
762 }
763 }
764
765 /**
766 * Returns an array that contains field validation messages status.
767 *
768 * @since 6.9
769 *
770 * @param array|object $field
771 * @return array
772 */
773 private static function get_validation_data_attribute_visibility_info( $field ) {
774 if ( FrmField::get_field_type( $field ) === 'hidden' ) {
775 $field_validation_data_attributes = array(
776 'data-invmsg' => false,
777 'data-reqmsg' => false,
778 );
779 } else {
780 $field_validation_data_attributes = array(
781 'data-invmsg' => true,
782 'data-reqmsg' => true,
783 );
784 }
785
786 /**
787 * Allows controlling which field validation messages would be included in the field html.
788 *
789 * @since 6.9
790 *
791 * @param array $field_validation_messages_status
792 * @param array|object $field
793 */
794 return apply_filters( 'frm_field_validation_include_data_attributes', $field_validation_data_attributes, $field );
795 }
796
797 /**
798 * @since 5.0.03
799 *
800 * @param array $field
801 * @param array $add_html
802 * @return void
803 */
804 private static function maybe_add_error_html_for_js_validation( $field, array &$add_html ) {
805 $form = self::get_form_for_js_validation( $field );
806 if ( false === $form ) {
807 return;
808 }
809
810 $error_body = self::pull_custom_error_body_from_custom_html( $form, $field );
811 if ( false !== $error_body ) {
812 $error_body = urlencode( $error_body );
813 $add_html['data-error-html'] = 'data-error-html="' . esc_attr( $error_body ) . '"';
814 }
815 }
816
817 /**
818 * @since 5.0.03
819 *
820 * @param array $field
821 * @return false|stdClass false if there is no form object found with JS validation active.
822 */
823 private static function get_form_for_js_validation( $field ) {
824 global $frm_vars;
825 if ( ! empty( $frm_vars['js_validate_forms'] ) ) {
826 if ( isset( $frm_vars['js_validate_forms'][ $field['form_id'] ] ) ) {
827 return $frm_vars['js_validate_forms'][ $field['form_id'] ];
828 }
829 if ( ! empty( $field['parent_form_id'] ) && isset( $frm_vars['js_validate_forms'][ $field['parent_form_id'] ] ) ) {
830 return $frm_vars['js_validate_forms'][ $field['parent_form_id'] ];
831 }
832 }
833 return false;
834 }
835
836 /**
837 * @param stdClass $form
838 * @param array $field
839 * @param array $errors
840 * @return false|string
841 */
842 public static function pull_custom_error_body_from_custom_html( $form, $field, $errors = array() ) {
843 if ( empty( $field['custom_html'] ) ) {
844 return false;
845 }
846
847 $custom_html = $field['custom_html'];
848 $custom_html = apply_filters( 'frm_before_replace_shortcodes', $custom_html, $field, $errors, $form );
849
850 $start = strpos( $custom_html, '[if error]' );
851 if ( false === $start ) {
852 return false;
853 }
854
855 $end = strpos( $custom_html, '[/if error]' );
856 if ( false === $end || $end < $start ) {
857 return false;
858 }
859
860 $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
861 $default_html = array(
862 '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
863 '<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>',
864 '<div class="frm_error">[error]</div>',
865 '<div class="frm_error" role="alert">[error]</div>',
866 );
867
868 if ( in_array( $error_body, $default_html, true ) ) {
869 return false;
870 }
871
872 return $error_body;
873 }
874
875 /**
876 * If 'required' is added to a conditionally hidden field, the form won't
877 * submit in many browsers. Check to make sure the javascript to conditionally
878 * remove it is present if needed.
879 *
880 * @since 3.06.01
881 * @param array $field
882 * @param array $add_html
883 * @return void
884 */
885 private static function maybe_add_html_required( $field, array &$add_html ) {
886 $excluded_field_types =
887 FrmField::is_radio( $field ) ||
888 FrmField::is_checkbox( $field ) ||
889 FrmField::is_field_type( $field, 'file' ) ||
890 FrmField::is_field_type( $field, 'nps' ) ||
891 FrmField::is_field_type( $field, 'scale' );
892
893 if ( $excluded_field_types ) {
894 return;
895 }
896
897 $add_html['aria-required'] = 'aria-required="true"';
898 }
899
900 /**
901 * @param array $field
902 * @param array $add_html
903 * @return void
904 */
905 private static function add_shortcodes_to_html( $field, array &$add_html ) {
906 if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
907 return;
908 }
909
910 if ( ! empty( $field['autocomplete'] ) ) {
911 unset( $field['shortcodes']['autocomplete'] );
912 }
913
914 foreach ( $field['shortcodes'] as $k => $v ) {
915 if ( isset( $field['subfield_name'] ) && 0 === strpos( $k, 'aria-invalid' ) ) {
916 $subfield_name = $field['subfield_name'];
917 if ( ! isset( $field['shortcodes'][ 'aria-invalid-' . $subfield_name ] ) ) {
918 continue;
919 }
920 // Change the key to the correct aria-invalid value so that $add_html is set correctly for the current subfield of a combo field.
921 $k = 'aria-invalid';
922 $v = $field['shortcodes'][ 'aria-invalid-' . $subfield_name ];
923 unset( $field['shortcodes'][ 'aria-invalid-' . $subfield_name ] );
924 }
925 if ( 'opt' === $k || ! self::should_allow_input_attribute( $k ) ) {
926 continue;
927 }
928
929 if ( is_numeric( $k ) && strpos( $v, '=' ) ) {
930 $add_html[] = $v;
931 } elseif ( ! empty( $k ) && isset( $add_html[ $k ] ) ) {
932 $add_html[ $k ] = str_replace( $k . '="', $k . '="' . $v, $add_html[ $k ] );
933 } else {
934 $add_html[ $k ] = $k . '="' . esc_attr( $v ) . '"';
935 }
936
937 unset( $k, $v );
938 }//end foreach
939 }
940
941 /**
942 * Disallow possibly unsafe attributees (that trigger JavaScript) when unasfe HTML is not allowed.
943 *
944 * @since 6.11.2
945 *
946 * @param string $key The option key.
947 * @return bool
948 */
949 private static function should_allow_input_attribute( $key ) {
950 if ( ! FrmAppHelper::should_never_allow_unfiltered_html() ) {
951 return true;
952 }
953 return FrmAppHelper::input_key_is_safe( $key );
954 }
955
956 /**
957 * Add pattern attribute.
958 *
959 * @since 3.0
960 *
961 * @param array $field
962 * @param array $add_html
963 * @return void
964 */
965 private static function add_pattern_attribute( $field, array &$add_html ) {
966 $format_value = FrmField::get_option( $field, 'format' );
967 $has_format = $format_value && ! FrmCurrencyHelper::is_currency_format( $format_value );
968 $format_field = FrmField::is_field_type( $field, 'text' );
969
970 if ( $field['type'] === 'phone' || ( $has_format && $format_field ) ) {
971 $format = FrmEntryValidate::phone_format( $field );
972 $format = substr( $format, 2, - 1 );
973
974 $add_html['pattern'] = 'pattern="' . esc_attr( $format ) . '"';
975 }
976 }
977
978 public static function check_value( $opt, $opt_key, $field ) {
979 if ( is_array( $opt ) ) {
980 if ( FrmField::is_option_true( $field, 'separate_value' ) ) {
981 $opt = isset( $opt['value'] ) ? $opt['value'] : ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
982 } else {
983 $opt = isset( $opt['label'] ) ? $opt['label'] : reset( $opt );
984 }
985 }
986
987 return $opt;
988 }
989
990 public static function check_label( $opt ) {
991 if ( is_array( $opt ) ) {
992 $opt = isset( $opt['label'] ) ? $opt['label'] : reset( $opt );
993 }
994
995 return $opt;
996 }
997 }
998