PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
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 / models / FrmFieldFormHtml.php

FrmFieldFormHtml.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.28, at classes/models/FrmFieldFormHtml.php

624 lines 15.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 /**
7 * @since 3.0
8 */
9 class FrmFieldFormHtml {
10
11 /**
12 * @var string
13 */
14 private $html;
15
16 /**
17 * @var string
18 */
19 private $html_id;
20
21 /**
22 * @var FrmFieldType
23 */
24 private $field_obj;
25
26 /**
27 * @var int|string
28 */
29 private $field_id;
30
31 /**
32 * @var array|object
33 */
34 private $form = array();
35
36 /**
37 * @var array
38 */
39 private $pass_args = array();
40
41 /**
42 * @since 3.0
43 *
44 * @param array $atts
45 */
46 public function __construct( $atts ) {
47 $this->_set( 'field_obj', $atts );
48 $this->set_field_id( $atts );
49 $this->_set( 'form', $atts );
50 $this->_set( 'html_id', $atts );
51 $this->set_html( $atts );
52 $this->set_pass_args( $atts );
53 }
54
55 /**
56 * @since 3.0
57 *
58 * @param string $param
59 * @param array $atts
60 *
61 * @return void
62 */
63 private function _set( $param, $atts ) {
64 if ( isset( $atts[ $param ] ) ) {
65 $this->{$param} = $atts[ $param ];
66 }
67 }
68
69 /**
70 * @since 3.0
71 *
72 * @param array $atts
73 *
74 * @return void
75 */
76 private function set_html( $atts ) {
77 $this->set_from_field(
78 $atts,
79 array(
80 'param' => 'html',
81 'default' => 'custom_html',
82 )
83 );
84 }
85
86 /**
87 * @since 3.0
88 *
89 * @param array $atts
90 *
91 * @return void
92 */
93 private function set_field_id( $atts ) {
94 $this->set_from_field(
95 $atts,
96 array(
97 'param' => 'field_id',
98 'default' => 'id',
99 )
100 );
101 }
102
103 /**
104 * @since 3.0
105 *
106 * @param array $atts
107 *
108 * @return void
109 */
110 private function set_pass_args( $atts ) {
111 $this->pass_args = $atts;
112
113 $exclude = array( 'field_obj', 'html' );
114
115 foreach ( $exclude as $ex ) {
116 if ( isset( $atts[ $ex ] ) ) {
117 unset( $this->pass_args[ $ex ] );
118 }
119 }
120 }
121
122 /**
123 * @since 3.0
124 *
125 * @param array $atts
126 * @param array $set
127 *
128 * @return void
129 */
130 private function set_from_field( $atts, $set ) {
131 if ( isset( $atts[ $set['param'] ] ) ) {
132 $this->{$set['param']} = $atts[ $set['param'] ];
133 } else {
134 $this->{$set['param']} = $this->field_obj->get_field_column( $set['default'] );
135 }
136 }
137
138 /**
139 * @return string
140 */
141 public function get_html() {
142 $this->replace_shortcodes_before_input();
143 $this->replace_shortcodes_with_atts();
144 $this->replace_shortcodes_after_input();
145
146 return $this->html;
147 }
148
149 /**
150 * @since 3.0
151 *
152 * @return void
153 */
154 private function replace_shortcodes_before_input() {
155 $this->html = apply_filters( 'frm_before_replace_shortcodes', $this->html, $this->field_obj->get_field(), $this->pass_args['errors'], $this->form );
156
157 $this->replace_field_values();
158
159 $this->replace_required_label_shortcode();
160 $this->replace_required_class();
161 $this->maybe_replace_description_shortcode( false );
162 $this->replace_error_shortcode();
163 $this->add_class_to_label();
164 $this->add_field_div_classes();
165
166 $this->replace_entry_key();
167 $this->replace_form_shortcodes();
168 $this->process_wp_shortcodes();
169 $this->maybe_replace_description_shortcode( true );
170
171 $this->add_multiple_input_attributes();
172 }
173
174 /**
175 * @since 3.0
176 *
177 * @return void
178 */
179 private function replace_field_values() {
180 // Replace [id].
181 $this->html = str_replace( '[id]', $this->field_id, $this->html );
182
183 // Set the label for
184 $this->html = str_replace( 'field_[key]', $this->html_id, $this->html );
185
186 // Replace [key].
187 $this->html = str_replace( '[key]', $this->field_obj->get_field_column( 'field_key' ), $this->html );
188
189 // Replace [field_name].
190 $this->html = str_replace( '[field_name]', FrmAppHelper::maybe_kses( $this->field_obj->get_field_column( 'name' ) ), $this->html );
191 }
192
193 /**
194 * @since 3.0
195 *
196 * @return void
197 */
198 private function replace_required_label_shortcode() {
199 $required = FrmField::is_required( $this->field_obj->get_field() ) ? $this->field_obj->get_field_column( 'required_indicator' ) : '';
200 FrmShortcodeHelper::remove_inline_conditions( ! empty( $required ), 'required_label', $required, $this->html );
201 }
202
203 /**
204 * If this is an HTML field, the values are included in the description.
205 * In this case, we don't want to run the wp shortcodes with the description included.
206 *
207 * @since 3.0
208 *
209 * @param bool $wp_processed
210 *
211 * @return void
212 */
213 private function maybe_replace_description_shortcode( $wp_processed = false ) {
214 $is_html = 'html' === $this->field_obj->get_field_column( 'type' );
215 $should_replace = $is_html ? $wp_processed : ! $wp_processed;
216
217 if ( $should_replace ) {
218 $this->replace_description_shortcode();
219 }
220 }
221
222 /**
223 * @since 3.0
224 *
225 * @return void
226 */
227 private function replace_description_shortcode() {
228 $this->maybe_add_description_id();
229 $description = FrmAppHelper::maybe_kses( $this->field_obj->get_field_column( 'description' ) );
230 // phpcs:ignore Universal.Operators.StrictComparisons
231 FrmShortcodeHelper::remove_inline_conditions( $description && $description != '', 'description', $description, $this->html );
232 }
233
234 /**
235 * Add an ID to the description for aria-describedby.
236 * This ID was added to the HTML in v3.0.
237 *
238 * @since 3.0
239 *
240 * @return void
241 */
242 private function maybe_add_description_id() {
243 $description = $this->field_obj->get_field_column( 'description' );
244
245 // phpcs:ignore Universal.Operators.StrictComparisons
246 if ( $description != '' ) {
247 $this->add_element_id( 'description', 'desc' );
248 }
249 }
250
251 /**
252 * Insert an ID if it doesn't exist.
253 *
254 * @since 3.06.02
255 *
256 * @param string $param
257 * @param string $id
258 *
259 * @return void
260 */
261 private function add_element_id( $param, $id ) {
262 preg_match_all( '/(\[if\s+' . $param . '\])(.*?)(\[\/if\s+' . $param . '\])/mis', $this->html, $inner_html );
263
264 if ( ! isset( $inner_html[2] ) ) {
265 return;
266 }
267
268 if ( ! is_string( $inner_html[2] ) && count( $inner_html[2] ) === 1 ) {
269 $inner_html[2] = $inner_html[2][0];
270 }
271
272 if ( ! is_string( $inner_html[2] ) ) {
273 return;
274 }
275
276 $has_id = str_contains( $inner_html[2], ' id=' );
277
278 if ( $has_id ) {
279 return;
280 }
281
282 $id = 'frm_' . $id . '_' . $this->html_id;
283 $this->html = str_replace( 'class="frm_' . $param, 'id="' . esc_attr( $id ) . '" class="frm_' . esc_attr( $param ), $this->html );
284 }
285
286 /**
287 * @since 3.0
288 *
289 * @return void
290 */
291 private function replace_error_shortcode() {
292 $this->maybe_add_error_id();
293 $error = $this->pass_args['errors'][ 'field' . $this->field_id ] ?? false;
294
295 if ( $error && ! str_contains( $this->html, 'role="alert"' ) && FrmAppHelper::should_include_alert_role_on_field_errors() ) {
296 $error_body = self::get_error_body( $this->html );
297
298 if ( is_string( $error_body ) && ! str_contains( $error_body, 'role=' ) ) {
299 $new_error_body = preg_replace( '/class="frm_error/', 'role="alert" class="frm_error', $error_body, 1 );
300 $this->html = str_replace( '[if error]' . $error_body . '[/if error]', '[if error]' . $new_error_body . '[/if error]', $this->html );
301 }
302 }
303
304 FrmShortcodeHelper::remove_inline_conditions( ! empty( $error ), 'error', $error, $this->html );
305 }
306
307 /**
308 * Pull the HTML between [if error] and [/if error] shortcodes.
309 *
310 * @param string $html
311 *
312 * @return false|string
313 */
314 private static function get_error_body( $html ) {
315 $start = strpos( $html, '[if error]' );
316
317 if ( false === $start ) {
318 return false;
319 }
320
321 $end = strpos( $html, '[/if error]', $start );
322
323 if ( false === $end ) {
324 return false;
325 }
326
327 return substr( $html, $start + 10, $end - $start - 10 );
328 }
329
330 /**
331 * Add an ID to the error message for aria-describedby.
332 * This ID was added to the HTML in v3.06.02.
333 *
334 * @since 3.06.02
335 *
336 * @return void
337 */
338 private function maybe_add_error_id() {
339 if ( ! isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ) {
340 return;
341 }
342
343 $this->add_element_id( 'error', 'error' );
344 }
345
346 /**
347 * Replace [required_class]
348 *
349 * @since 3.0
350 *
351 * @return void
352 */
353 private function replace_required_class() {
354 $required_class = FrmField::is_required( $this->field_obj->get_field() ) ? ' frm_required_field' : '';
355 $this->html = str_replace( '[required_class]', $required_class, $this->html );
356 }
357
358 /**
359 * @since 3.0
360 *
361 * @return void
362 */
363 private function replace_form_shortcodes() {
364 if ( ! empty( $this->form ) ) {
365 $form = (array) $this->form;
366
367 // Replace [form_key].
368 $this->html = str_replace( '[form_key]', $form['form_key'], $this->html );
369
370 // Replace [form_name].
371 $this->html = str_replace( '[form_name]', $form['name'], $this->html );
372 }
373 }
374
375 /**
376 * @since 3.0
377 *
378 * @return void
379 */
380 public function replace_shortcodes_after_input() {
381 $this->html .= "\n";
382
383 // Stop html filtering on confirmation field to prevent loop
384 if ( $this->field_obj->get_field_column( 'conf_field' ) !== 'stop' ) {
385 $this->filter_for_more_shortcodes();
386 }
387 }
388
389 /**
390 * @since 3.0
391 *
392 * @return void
393 */
394 private function filter_for_more_shortcodes() {
395 $atts = $this->pass_args;
396
397 // If field is not in repeating section.
398 if ( empty( $atts['section_id'] ) ) {
399 $atts = array(
400 'errors' => $this->pass_args['errors'],
401 'form' => $this->form,
402 );
403 }
404 $this->html = apply_filters( 'frm_replace_shortcodes', $this->html, $this->field_obj->get_field(), $atts );
405 }
406
407 /**
408 * Remove [collapse_this] if it's still included after all processing
409 *
410 * @since 3.0
411 *
412 * @param string $html
413 *
414 * @return void
415 */
416 public function remove_collapse_shortcode( &$html ) {
417 if ( str_contains( $html, '[collapse_this]' ) ) {
418 $html = str_replace( '[collapse_this]', '', $html );
419 }
420 }
421
422 /**
423 * @since 3.0
424 *
425 * @return void
426 */
427 private function replace_shortcodes_with_atts() {
428 preg_match_all( "/\[(input|deletelink)\b(.*?)(?:(\/))?\]/s", $this->html, $shortcodes, PREG_PATTERN_ORDER );
429
430 foreach ( $shortcodes[0] as $short_key => $tag ) {
431 $shortcode_atts = FrmShortcodeHelper::get_shortcode_attribute_array( $shortcodes[2][ $short_key ] );
432 $tag = FrmShortcodeHelper::get_shortcode_tag( $shortcodes, $short_key );
433 $replace_with = '';
434
435 if ( $tag === 'deletelink' && FrmAppHelper::pro_is_installed() ) {
436 $replace_with = FrmProEntriesController::entry_delete_link( $shortcode_atts );
437 } elseif ( $tag === 'input' ) {
438 $replace_with = $this->replace_input_shortcode( $shortcode_atts );
439 }
440
441 $this->html = str_replace( $shortcodes[0][ $short_key ], $replace_with, $this->html );
442 }
443 }
444
445 /**
446 * @param array $shortcode_atts
447 *
448 * @return string
449 */
450 private function replace_input_shortcode( $shortcode_atts ) {
451 $shortcode_atts = $this->prepare_input_shortcode_atts( $shortcode_atts );
452 return $this->field_obj->include_front_field_input( $this->pass_args, $shortcode_atts );
453 }
454
455 /**
456 * @param array $shortcode_atts
457 *
458 * @return array
459 */
460 private function prepare_input_shortcode_atts( $shortcode_atts ) {
461 if ( isset( $shortcode_atts['opt'] ) ) {
462 --$shortcode_atts['opt'];
463 }
464
465 $field_class = $shortcode_atts['class'] ?? '';
466 $this->field_obj->set_field_column( 'input_class', $field_class );
467
468 if ( isset( $shortcode_atts['class'] ) ) {
469 unset( $shortcode_atts['class'] );
470 }
471
472 $this->field_obj->set_aria_invalid_error( $shortcode_atts, $this->pass_args );
473
474 $this->field_obj->set_field_column( 'shortcodes', $shortcode_atts );
475
476 return $shortcode_atts;
477 }
478
479 /**
480 * Add the label position class into the HTML
481 * If the label position is inside, add a class to show the label if the field has a value.
482 *
483 * @since 3.0
484 *
485 * @return void
486 */
487 private function add_class_to_label() {
488 $label_class = $this->field_obj->get_label_class();
489 $this->html = str_replace( '[label_position]', $label_class, $this->html );
490 }
491
492 /**
493 * Replace [entry_key]
494 *
495 * @since 3.0
496 *
497 * @return void
498 */
499 private function replace_entry_key() {
500 $entry_key = FrmAppHelper::simple_get( 'entry', 'sanitize_title' );
501 $this->html = str_replace( '[entry_key]', $entry_key, $this->html );
502 }
503
504 /**
505 * Add classes to a field div
506 *
507 * @since 3.0
508 *
509 * @return void
510 */
511 private function add_field_div_classes() {
512 $classes = $this->get_field_div_classes();
513
514 if ( in_array( $this->field_obj->get_field_column( 'type' ), array( 'html', 'summary' ), true ) && ! str_contains( $this->html, '[error_class]' ) ) {
515 // There is no error_class shortcode for HTML fields
516 $this->html = str_replace( 'class="frm_form_field', 'class="frm_form_field ' . esc_attr( $classes ), $this->html );
517 return;
518 }
519
520 $this->html = str_replace( '[error_class]', esc_attr( $classes ), $this->html );
521 }
522
523 /**
524 * Get the classes for a field div
525 *
526 * @since 3.0
527 *
528 * @return string Classes.
529 */
530 private function get_field_div_classes() {
531 // Add error class
532 $classes = isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ? ' frm_blank_field' : '';
533
534 // Add label position class
535 $settings = $this->field_obj->display_field_settings();
536
537 if ( ! empty( $settings['label_position'] ) ) {
538 $label_position = $this->field_obj->get_field_column( 'label' );
539 $classes .= ' frm_' . $label_position . '_container';
540
541 // Add class if field has value, to be used for floating label styling.
542 if ( 'inside' === $label_position && $this->field_obj->get_field_column( 'value' ) ) {
543 $classes .= ' frm_label_float_top';
544 }
545 }
546
547 // Add CSS layout classes
548 $extra_classes = $this->field_obj->get_field_column( 'classes' );
549
550 if ( $extra_classes ) {
551 if ( ! str_contains( $this->html, 'frm_form_field ' ) ) {
552 $classes .= ' frm_form_field';
553 }
554
555 $classes .= ' ' . $extra_classes;
556 }
557
558 $classes .= $this->field_obj->get_container_class();
559
560 // Get additional classes
561 $classes = apply_filters( 'frm_field_div_classes', $classes, $this->field_obj->get_field(), array( 'field_id' => $this->field_id ) );
562
563 // Remove unexpected characters from class.
564 return implode( ' ', array_map( 'FrmFormsHelper::sanitize_layout_class', explode( ' ', $classes ) ) );
565 }
566
567 /**
568 * This filters shortcodes in the field HTML
569 *
570 * @since 3.0
571 *
572 * @return void
573 */
574 private function process_wp_shortcodes() {
575 if ( apply_filters( 'frm_do_html_shortcodes', true ) ) {
576 $this->html = do_shortcode( $this->html );
577 }
578 }
579
580 /**
581 * Adds multiple input attributes.
582 *
583 * @since 6.4.1
584 *
585 * @return void
586 */
587 private function add_multiple_input_attributes() {
588 $field_type = $this->field_obj->get_field_column( 'type' );
589
590 // Check if the field type is one of the following.
591 if ( ! in_array( $field_type, array( 'radio', 'checkbox', 'data', 'product', 'scale' ), true ) ) {
592 return;
593 }
594
595 $field = (array) $this->field_obj->get_field();
596 $attributes = array();
597
598 // Check if the field type is 'data' or 'product'.
599 if ( in_array( $field_type, array( 'data', 'product' ), true ) ) {
600 $data_type = FrmField::get_option( $field, 'data_type' );
601 $is_radio = 'radio' === $data_type;
602 } else {
603 $is_radio = 'radio' === $field_type || 'scale' === $field_type;
604 }
605
606 // Add 'role' attribute to the field.
607 $attributes['role'] = $is_radio ? 'radiogroup' : 'group';
608
609 // Add 'aria-required' attribute to the field if required.
610 if ( $is_radio && '1' === $field['required'] ) {
611 $attributes['aria-required'] = 'true';
612 }
613
614 // Add 'aria-invalid' attribute to the group if there are errors.
615 if ( isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ) {
616 $attributes['aria-invalid'] = 'true';
617 }
618
619 // Concatenate attributes into a string, and replace the role="group" in the HTML with the attributes string.
620 $html_attributes = FrmAppHelper::array_to_html_params( $attributes );
621 $this->html = str_replace( ' role="group"', $html_attributes, $this->html );
622 }
623 }
624