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

626 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 ( ! $this->form ) {
365 return;
366 }
367
368 $form = (array) $this->form;
369
370 // Replace [form_key].
371 $this->html = str_replace( '[form_key]', $form['form_key'], $this->html );
372
373 // Replace [form_name].
374 $this->html = str_replace( '[form_name]', $form['name'], $this->html );
375 }
376
377 /**
378 * @since 3.0
379 *
380 * @return void
381 */
382 public function replace_shortcodes_after_input() {
383 $this->html .= "\n";
384
385 // Stop html filtering on confirmation field to prevent loop
386 if ( $this->field_obj->get_field_column( 'conf_field' ) !== 'stop' ) {
387 $this->filter_for_more_shortcodes();
388 }
389 }
390
391 /**
392 * @since 3.0
393 *
394 * @return void
395 */
396 private function filter_for_more_shortcodes() {
397 $atts = $this->pass_args;
398
399 // If field is not in repeating section.
400 if ( empty( $atts['section_id'] ) ) {
401 $atts = array(
402 'errors' => $this->pass_args['errors'],
403 'form' => $this->form,
404 );
405 }
406 $this->html = apply_filters( 'frm_replace_shortcodes', $this->html, $this->field_obj->get_field(), $atts );
407 }
408
409 /**
410 * Remove [collapse_this] if it's still included after all processing
411 *
412 * @since 3.0
413 *
414 * @param string $html
415 *
416 * @return void
417 */
418 public function remove_collapse_shortcode( &$html ) {
419 if ( str_contains( $html, '[collapse_this]' ) ) {
420 $html = str_replace( '[collapse_this]', '', $html );
421 }
422 }
423
424 /**
425 * @since 3.0
426 *
427 * @return void
428 */
429 private function replace_shortcodes_with_atts() {
430 preg_match_all( "/\[(input|deletelink)\b(.*?)(?:(\/))?\]/s", $this->html, $shortcodes, PREG_PATTERN_ORDER );
431
432 foreach ( $shortcodes[0] as $short_key => $tag ) {
433 $shortcode_atts = FrmShortcodeHelper::get_shortcode_attribute_array( $shortcodes[2][ $short_key ] );
434 $tag = FrmShortcodeHelper::get_shortcode_tag( $shortcodes, $short_key );
435 $replace_with = '';
436
437 if ( $tag === 'deletelink' && FrmAppHelper::pro_is_installed() ) {
438 $replace_with = FrmProEntriesController::entry_delete_link( $shortcode_atts );
439 } elseif ( $tag === 'input' ) {
440 $replace_with = $this->replace_input_shortcode( $shortcode_atts );
441 }
442
443 $this->html = str_replace( $shortcodes[0][ $short_key ], $replace_with, $this->html );
444 }
445 }
446
447 /**
448 * @param array $shortcode_atts
449 *
450 * @return string
451 */
452 private function replace_input_shortcode( $shortcode_atts ) {
453 $shortcode_atts = $this->prepare_input_shortcode_atts( $shortcode_atts );
454 return $this->field_obj->include_front_field_input( $this->pass_args, $shortcode_atts );
455 }
456
457 /**
458 * @param array $shortcode_atts
459 *
460 * @return array
461 */
462 private function prepare_input_shortcode_atts( $shortcode_atts ) {
463 if ( isset( $shortcode_atts['opt'] ) ) {
464 --$shortcode_atts['opt'];
465 }
466
467 $field_class = $shortcode_atts['class'] ?? '';
468 $this->field_obj->set_field_column( 'input_class', $field_class );
469
470 if ( isset( $shortcode_atts['class'] ) ) {
471 unset( $shortcode_atts['class'] );
472 }
473
474 $this->field_obj->set_aria_invalid_error( $shortcode_atts, $this->pass_args );
475
476 $this->field_obj->set_field_column( 'shortcodes', $shortcode_atts );
477
478 return $shortcode_atts;
479 }
480
481 /**
482 * Add the label position class into the HTML
483 * If the label position is inside, add a class to show the label if the field has a value.
484 *
485 * @since 3.0
486 *
487 * @return void
488 */
489 private function add_class_to_label() {
490 $label_class = $this->field_obj->get_label_class();
491 $this->html = str_replace( '[label_position]', $label_class, $this->html );
492 }
493
494 /**
495 * Replace [entry_key]
496 *
497 * @since 3.0
498 *
499 * @return void
500 */
501 private function replace_entry_key() {
502 $entry_key = FrmAppHelper::simple_get( 'entry', 'sanitize_title' );
503 $this->html = str_replace( '[entry_key]', $entry_key, $this->html );
504 }
505
506 /**
507 * Add classes to a field div
508 *
509 * @since 3.0
510 *
511 * @return void
512 */
513 private function add_field_div_classes() {
514 $classes = $this->get_field_div_classes();
515
516 if ( in_array( $this->field_obj->get_field_column( 'type' ), array( 'html', 'summary' ), true ) && ! str_contains( $this->html, '[error_class]' ) ) {
517 // There is no error_class shortcode for HTML fields
518 $this->html = str_replace( 'class="frm_form_field', 'class="frm_form_field ' . esc_attr( $classes ), $this->html );
519 return;
520 }
521
522 $this->html = str_replace( '[error_class]', esc_attr( $classes ), $this->html );
523 }
524
525 /**
526 * Get the classes for a field div
527 *
528 * @since 3.0
529 *
530 * @return string Classes.
531 */
532 private function get_field_div_classes() {
533 // Add error class
534 $classes = isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ? ' frm_blank_field' : '';
535
536 // Add label position class
537 $settings = $this->field_obj->display_field_settings();
538
539 if ( ! empty( $settings['label_position'] ) ) {
540 $label_position = $this->field_obj->get_field_column( 'label' );
541 $classes .= ' frm_' . $label_position . '_container';
542
543 // Add class if field has value, to be used for floating label styling.
544 if ( 'inside' === $label_position && $this->field_obj->get_field_column( 'value' ) ) {
545 $classes .= ' frm_label_float_top';
546 }
547 }
548
549 // Add CSS layout classes
550 $extra_classes = $this->field_obj->get_field_column( 'classes' );
551
552 if ( $extra_classes ) {
553 if ( ! str_contains( $this->html, 'frm_form_field ' ) ) {
554 $classes .= ' frm_form_field';
555 }
556
557 $classes .= ' ' . $extra_classes;
558 }
559
560 $classes .= $this->field_obj->get_container_class();
561
562 // Get additional classes
563 $classes = apply_filters( 'frm_field_div_classes', $classes, $this->field_obj->get_field(), array( 'field_id' => $this->field_id ) );
564
565 // Remove unexpected characters from class.
566 return implode( ' ', array_map( 'FrmFormsHelper::sanitize_layout_class', explode( ' ', $classes ) ) );
567 }
568
569 /**
570 * This filters shortcodes in the field HTML
571 *
572 * @since 3.0
573 *
574 * @return void
575 */
576 private function process_wp_shortcodes() {
577 if ( apply_filters( 'frm_do_html_shortcodes', true ) ) {
578 $this->html = do_shortcode( $this->html );
579 }
580 }
581
582 /**
583 * Adds multiple input attributes.
584 *
585 * @since 6.4.1
586 *
587 * @return void
588 */
589 private function add_multiple_input_attributes() {
590 $field_type = $this->field_obj->get_field_column( 'type' );
591
592 // Check if the field type is one of the following.
593 if ( ! in_array( $field_type, array( 'radio', 'checkbox', 'data', 'product', 'scale' ), true ) ) {
594 return;
595 }
596
597 $field = (array) $this->field_obj->get_field();
598 $attributes = array();
599
600 // Check if the field type is 'data' or 'product'.
601 if ( in_array( $field_type, array( 'data', 'product' ), true ) ) {
602 $data_type = FrmField::get_option( $field, 'data_type' );
603 $is_radio = 'radio' === $data_type;
604 } else {
605 $is_radio = 'radio' === $field_type || 'scale' === $field_type;
606 }
607
608 // Add 'role' attribute to the field.
609 $attributes['role'] = $is_radio ? 'radiogroup' : 'group';
610
611 // Add 'aria-required' attribute to the field if required.
612 if ( $is_radio && '1' === $field['required'] ) {
613 $attributes['aria-required'] = 'true';
614 }
615
616 // Add 'aria-invalid' attribute to the group if there are errors.
617 if ( isset( $this->pass_args['errors'][ 'field' . $this->field_id ] ) ) {
618 $attributes['aria-invalid'] = 'true';
619 }
620
621 // Concatenate attributes into a string, and replace the role="group" in the HTML with the attributes string.
622 $html_attributes = FrmAppHelper::array_to_html_params( $attributes );
623 $this->html = str_replace( ' role="group"', $html_attributes, $this->html );
624 }
625 }
626