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

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