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

990 lines 27.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|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 if ( $autocomplete && empty( $use_chosen ) ) {
706 // This is required for Slim Select.
707 $placeholder_attributes['data-placeholder'] = 'true';
708 }
709
710 FrmHtmlHelper::echo_dropdown_option( $use_placeholder, false, $placeholder_attributes );
711 return true;
712 }
713
714 return false;
715 }
716
717 /**
718 * Use HTML5 placeholder with js fallback.
719 *
720 * @param array $field
721 * @param array $add_html
722 * @return void
723 */
724 private static function add_placeholder_to_input( $field, &$add_html ) {
725 if ( FrmFieldsHelper::is_placeholder_field_type( $field['type'] ) ) {
726 $add_html['placeholder'] = 'placeholder="' . esc_attr( $field['placeholder'] ) . '"';
727 }
728 }
729
730 /**
731 * @param array $field
732 * @param array $add_html
733 * @return void
734 */
735 private static function add_frmval_to_input( $field, &$add_html ) {
736 if ( $field['placeholder'] != '' ) {
737 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['placeholder'] ) . '"';
738
739 if ( 'select' === $field['type'] ) {
740 $add_html['data-frmplaceholder'] = 'data-frmplaceholder="' . esc_attr( $field['placeholder'] ) . '"';
741 }
742 }
743
744 if ( $field['default_value'] != '' ) {
745 $add_html['data-frmval'] = 'data-frmval="' . esc_attr( $field['default_value'] ) . '"';
746 }
747 }
748
749 private static function add_validation_messages( $field, array &$add_html ) {
750 $field_validation_messages_status = self::get_validation_data_attribute_visibility_info( $field );
751
752 if ( FrmField::is_required( $field ) && ! empty( $field_validation_messages_status['data-reqmsg'] ) ) {
753 $required_message = FrmFieldsHelper::get_error_msg( $field, 'blank' );
754 $add_html['data-reqmsg'] = 'data-reqmsg="' . esc_attr( $required_message ) . '"';
755 self::maybe_add_html_required( $field, $add_html );
756 }
757
758 if ( ! FrmField::is_option_empty( $field, 'invalid' ) && ! empty( $field_validation_messages_status['data-invmsg'] ) ) {
759 $invalid_message = FrmFieldsHelper::get_error_msg( $field, 'invalid' );
760 $add_html['data-invmsg'] = 'data-invmsg="' . esc_attr( $invalid_message ) . '"';
761 }
762
763 if ( ! empty( $add_html['data-reqmsg'] ) || ! empty( $add_html['data-invmsg'] ) ) {
764 self::maybe_add_error_html_for_js_validation( $field, $add_html );
765 }
766 }
767
768 /**
769 * Returns an array that contains field validation messages status.
770 *
771 * @since 6.9
772 *
773 * @param array|object $field
774 * @return array
775 */
776 private static function get_validation_data_attribute_visibility_info( $field ) {
777 if ( FrmField::get_field_type( $field ) === 'hidden' ) {
778 $field_validation_data_attributes = array(
779 'data-invmsg' => false,
780 'data-reqmsg' => false,
781 );
782 } else {
783 $field_validation_data_attributes = array(
784 'data-invmsg' => true,
785 'data-reqmsg' => true,
786 );
787 }
788
789 /**
790 * Allows controlling which field validation messages would be included in the field html.
791 *
792 * @since 6.9
793 *
794 * @param array $field_validation_messages_status
795 * @param array|object $field
796 */
797 return apply_filters( 'frm_field_validation_include_data_attributes', $field_validation_data_attributes, $field );
798 }
799
800 /**
801 * @since 5.0.03
802 *
803 * @param array $field
804 * @param array $add_html
805 * @return void
806 */
807 private static function maybe_add_error_html_for_js_validation( $field, array &$add_html ) {
808 $form = self::get_form_for_js_validation( $field );
809 if ( false === $form ) {
810 return;
811 }
812
813 $error_body = self::pull_custom_error_body_from_custom_html( $form, $field );
814 if ( false !== $error_body ) {
815 $error_body = urlencode( $error_body );
816 $add_html['data-error-html'] = 'data-error-html="' . esc_attr( $error_body ) . '"';
817 }
818 }
819
820 /**
821 * @since 5.0.03
822 *
823 * @param array $field
824 * @return false|stdClass false if there is no form object found with JS validation active.
825 */
826 private static function get_form_for_js_validation( $field ) {
827 global $frm_vars;
828 if ( ! empty( $frm_vars['js_validate_forms'] ) ) {
829 if ( isset( $frm_vars['js_validate_forms'][ $field['form_id'] ] ) ) {
830 return $frm_vars['js_validate_forms'][ $field['form_id'] ];
831 }
832 if ( ! empty( $field['parent_form_id'] ) && isset( $frm_vars['js_validate_forms'][ $field['parent_form_id'] ] ) ) {
833 return $frm_vars['js_validate_forms'][ $field['parent_form_id'] ];
834 }
835 }
836 return false;
837 }
838
839 /**
840 * @param stdClass $form
841 * @param array $field
842 * @param array $errors
843 * @return false|string
844 */
845 public static function pull_custom_error_body_from_custom_html( $form, $field, $errors = array() ) {
846 if ( empty( $field['custom_html'] ) ) {
847 return false;
848 }
849
850 $custom_html = $field['custom_html'];
851 $custom_html = apply_filters( 'frm_before_replace_shortcodes', $custom_html, $field, $errors, $form );
852
853 $start = strpos( $custom_html, '[if error]' );
854 if ( false === $start ) {
855 return false;
856 }
857
858 $end = strpos( $custom_html, '[/if error]' );
859 if ( false === $end || $end < $start ) {
860 return false;
861 }
862
863 $error_body = substr( $custom_html, $start + 10, $end - $start - 10 );
864 $default_html = array(
865 '<div class="frm_error" id="frm_error_field_[key]">[error]</div>',
866 '<div class="frm_error" role="alert" id="frm_error_field_[key]">[error]</div>',
867 '<div class="frm_error">[error]</div>',
868 '<div class="frm_error" role="alert">[error]</div>',
869 );
870
871 if ( in_array( $error_body, $default_html, true ) ) {
872 return false;
873 }
874
875 return $error_body;
876 }
877
878 /**
879 * If 'required' is added to a conditionally hidden field, the form won't
880 * submit in many browsers. Check to make sure the javascript to conditionally
881 * remove it is present if needed.
882 *
883 * @since 3.06.01
884 * @param array $field
885 * @param array $add_html
886 * @return void
887 */
888 private static function maybe_add_html_required( $field, array &$add_html ) {
889 $excluded_field_types =
890 FrmField::is_radio( $field ) ||
891 FrmField::is_checkbox( $field ) ||
892 FrmField::is_field_type( $field, 'file' ) ||
893 FrmField::is_field_type( $field, 'nps' ) ||
894 FrmField::is_field_type( $field, 'scale' );
895
896 if ( $excluded_field_types ) {
897 return;
898 }
899
900 $add_html['aria-required'] = 'aria-required="true"';
901 }
902
903 /**
904 * @param array $field
905 * @param array $add_html
906 * @return void
907 */
908 private static function add_shortcodes_to_html( $field, array &$add_html ) {
909 if ( FrmField::is_option_empty( $field, 'shortcodes' ) ) {
910 return;
911 }
912
913 if ( ! empty( $field['autocomplete'] ) ) {
914 unset( $field['shortcodes']['autocomplete'] );
915 }
916
917 foreach ( $field['shortcodes'] as $k => $v ) {
918 if ( 'opt' === $k || ! self::should_allow_input_attribute( $k ) ) {
919 continue;
920 }
921
922 if ( is_numeric( $k ) && strpos( $v, '=' ) ) {
923 $add_html[] = $v;
924 } elseif ( ! empty( $k ) && isset( $add_html[ $k ] ) ) {
925 $add_html[ $k ] = str_replace( $k . '="', $k . '="' . $v, $add_html[ $k ] );
926 } else {
927 $add_html[ $k ] = $k . '="' . esc_attr( $v ) . '"';
928 }
929
930 unset( $k, $v );
931 }
932 }
933
934 /**
935 * Disallow possibly unsafe attributees (that trigger JavaScript) when unasfe HTML is not allowed.
936 *
937 * @since 6.11.2
938 *
939 * @param string $key The option key.
940 * @return bool
941 */
942 private static function should_allow_input_attribute( $key ) {
943 if ( ! FrmAppHelper::should_never_allow_unfiltered_html() ) {
944 return true;
945 }
946 return FrmAppHelper::input_key_is_safe( $key );
947 }
948
949 /**
950 * Add pattern attribute.
951 *
952 * @since 3.0
953 *
954 * @param array $field
955 * @param array $add_html
956 * @return void
957 */
958 private static function add_pattern_attribute( $field, array &$add_html ) {
959 $has_format = FrmField::is_option_true_in_array( $field, 'format' );
960 $format_field = FrmField::is_field_type( $field, 'text' );
961
962 if ( $field['type'] === 'phone' || ( $has_format && $format_field ) ) {
963 $format = FrmEntryValidate::phone_format( $field );
964 $format = substr( $format, 2, - 1 );
965
966 $add_html['pattern'] = 'pattern="' . esc_attr( $format ) . '"';
967 }
968 }
969
970 public static function check_value( $opt, $opt_key, $field ) {
971 if ( is_array( $opt ) ) {
972 if ( FrmField::is_option_true( $field, 'separate_value' ) ) {
973 $opt = isset( $opt['value'] ) ? $opt['value'] : ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
974 } else {
975 $opt = isset( $opt['label'] ) ? $opt['label'] : reset( $opt );
976 }
977 }
978
979 return $opt;
980 }
981
982 public static function check_label( $opt ) {
983 if ( is_array( $opt ) ) {
984 $opt = isset( $opt['label'] ) ? $opt['label'] : reset( $opt );
985 }
986
987 return $opt;
988 }
989 }
990