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

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