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

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