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

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