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

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