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

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