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

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