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

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