PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.3.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.3.1
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
← All changes | includes/Frontend/Form/View/FieldHelpers.php +324 -26 2.10.03.3.1 View file →
@@ -1,8 +1,10 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Frontend\Form\View;
4 4
5 +use BitCode\BitForm\Admin\Form\Helpers;
6 +use BitCode\BitForm\Core\Util\EscapingHelper;
5 7 use BitCode\BitForm\Core\Util\FieldValueHandler;
6 8 use BitCode\BitForm\Core\Util\FrontendHelpers;
7 9
8 10 class FieldHelpers
@@ -9,14 +11,16 @@
9 11 {
10 12 private $_fld;
11 13 private $_fk;
12 14 private $_form_atomic_Cls_map;
15 + private $_formID;
13 16
14 - public function __construct($field, $fk, $form_atomic_Cls_map = null)
17 + public function __construct($field, $fk, $form_atomic_Cls_map = null, $formID = null)
15 18 {
16 19 $this->_fld = $field;
17 20 $this->_fk = $fk;
18 21 $this->_form_atomic_Cls_map = $form_atomic_Cls_map;
22 + $this->_formID = $formID;
19 23 }
20 24
21 25 public function getCustomAttributes($element)
22 26 {
@@ -24,8 +28,11 @@
24 28 $attrString = '';
25 29 $customAttributs = $this->_fld->customAttributes->{$element};
26 30
27 31 foreach ($customAttributs as $attr) {
32 + if (!isset($attr->key) || !EscapingHelper::isSafeAttributeKey((string) $attr->key)) {
33 + continue;
34 + }
28 35 $attrString .= " {$this->esc_attr($attr->key)}='{$this->esc_attr($attr->value)}'";
29 36 }
30 37 return $attrString;
31 38 }
@@ -39,34 +46,137 @@
39 46 }
40 47 return '';
41 48 }
42 49
43 - public function renderHTMR($title)
50 + public function renderHTMR($content)
44 51 {
45 - return $title;
52 + $safeContent = Helpers::sanitizeUserHTML($content);
53 + return $safeContent;
46 54 }
47 55
48 56 public function getAtomicCls($element)
49 57 {
58 + $meaningfulElementClass = "bf-{$this->getMeaningfullElementClass($element)}";
50 59 if (empty($this->_fld) && empty($this->_fk)) {
51 - if (property_exists($this->_form_atomic_Cls_map, ".$element")) {
60 + if (is_object($this->_form_atomic_Cls_map) && property_exists($this->_form_atomic_Cls_map, ".$element")) {
52 61 $getAtomicCls = $this->_form_atomic_Cls_map->{".$element"};
53 - return implode(' ', $getAtomicCls) . " $element";
62 + return implode(' ', $getAtomicCls) . " {$element} {$meaningfulElementClass}";
54 63 }
55 - return $element;
64 + return "$element {$meaningfulElementClass}";
56 65 }
57 - if ('advanced-file-up' === $this->_fld->typ && isset($this->_form_atomic_Cls_map->{".$element"})) {
66 + if (isset($this->_fld->typ) && 'advanced-file-up' === $this->_fld->typ && isset($this->_form_atomic_Cls_map->{".$element"})) {
58 67 $getAtomicCls = $this->_form_atomic_Cls_map->{".$element"};
59 - return implode(' ', $getAtomicCls) . " {$this->_fk}-{$element}";
68 + return implode(' ', $getAtomicCls) . " {$this->_fk}-{$element} {$meaningfulElementClass}";
60 69 }
61 - $cls = ".$this->_fk-$element";
62 - if (isset($this->_form_atomic_Cls_map->{$cls})) {
63 - $getAtomicCls = $this->_form_atomic_Cls_map->{$cls};
64 - return implode(' ', $getAtomicCls) . " {$this->_fk}-{$element}";
70 + $fieldElementCls = ".{$this->_fk}-{$element}";
71 + $formId = explode('-', $this->_fk)[0]; // exmp: b12-10 => $formId = b12
72 + // $formElementCls = ".{$element}-" . $formId;
73 + $formElementCls = ".{$formId}-" . $element;
74 +
75 + if (isset($this->_form_atomic_Cls_map->{$fieldElementCls})) {
76 + $formElementAtomicClsString = '';
77 + if (property_exists($this->_form_atomic_Cls_map, $formElementCls)) {
78 + $formElementAtomicClsArray = $this->_form_atomic_Cls_map->{$formElementCls};
79 + $formElementAtomicClsString = implode(' ', $formElementAtomicClsArray);
80 + }
81 + $getAtomicCls = $this->_form_atomic_Cls_map->{$fieldElementCls};
82 + return implode(' ', $getAtomicCls) . " {$formElementAtomicClsString} {$this->_fk}-{$element} {$formId}-{$element} bf-{$element} {$meaningfulElementClass}";
65 83 }
66 - return "{$this->_fk}-{$element}";
84 + return "{$this->_fk}-{$element} {$formId}-{$element} bf-{$element} {$meaningfulElementClass}";
67 85 }
68 86
87 + public function getMeaningfullElementClass($element)
88 + {
89 + $elementTitleMap = [
90 + 'fld-wrp' => 'field-wrapper',
91 + 'lbl-wrp' => 'label-wrapper',
92 + 'lbl' => 'label',
93 + 'lbl-pre-i' => 'label-prefix-icon',
94 + 'lbl-suf-i' => 'label-suffix-icon',
95 + 'sub-titl' => 'subtitle',
96 + 'sub-titl-pre-i' => 'subtitle-prefix-icon',
97 + 'sub-titl-suf-i' => 'subtitle-suffix-icon',
98 + 'fld' => 'field',
99 + 'pre-i' => 'field-prefix-icon',
100 + 'suf-i' => 'field-suffix-icon',
101 + 'hlp-txt' => 'helper-text-container',
102 + 'hlp-txt-pre-i' => 'helper-text-prefix-icon',
103 + 'hlp-txt-suf-i' => 'helper-text-suffix-icon',
104 + 'err-msg' => 'error-messages-container',
105 + 'err-txt-pre-i' => 'error-text-prefix-icon',
106 + 'err-txt-suf-i' => 'error-text-suffix-icon',
107 + 'currency-fld-wrp' => 'currency-field-wrapper',
108 + 'btn' => 'button',
109 + 'btn-pre-i' => 'button-prefix-icon',
110 + 'btn-suf-i' => 'button-suffix-icon',
111 + 'other-inp' => 'other-option-input',
112 + 'inp-wrp' => 'input-wrapper',
113 + 'inp-wrp .filepond--panel-root' => 'item-panel-wrapper',
114 + 'inp-wrp .filepond--item-panel' => 'item-panel',
115 + 'inp-wrp .filepond--file-action-button' => 'file-action-button',
116 + 'inp-wrp .filepond--file' => 'file',
117 + 'inp-wrp .filepond--drop-label' => 'drop-label',
118 + 'inp-wrp .filepond--label-action' => 'label-action',
119 + 'option-list' => 'option-list',
120 + 'option-list .option' => 'option',
121 + 'option-list .opt-lbl-wrp' => 'option-label-container',
122 + 'option-list .opt-icn' => 'option-icon',
123 + 'option-list .opt-lbl' => 'option-label',
124 + 'option-list .opt-suffix' => 'option-suffix',
125 + 'option-list .opt-prefix' => 'option-prefix',
126 + 'divider' => 'divider',
127 + 'bx' => 'box',
128 + 'stripe-btn' => 'stripe-button',
129 + 'stripe-icn' => 'stripe-icon',
130 + 'stripe-pay-btn' => 'stripe-pay-button',
131 + 'rating-img' => 'rating-image',
132 + 'rating-msg' => 'rating-message',
133 + 'req-smbl' => 'asterisk-symbol',
134 + '_frm-bg' => 'form-wrapper',
135 + '_frm' => 'form-container',
136 + ];
137 +
138 + if (isset($elementTitleMap[$element])) {
139 + return $elementTitleMap[$element];
140 + }
141 +
142 + // Fallback: convert hyphenated element key to meaningful class
143 + // e.g., 'fld-wrp' => 'fld wrp' => join with '-' after expanding abbreviations
144 + $abbreviations = [
145 + 'fld' => 'field',
146 + 'wrp' => 'wrapper',
147 + 'lbl' => 'label',
148 + 'pre' => 'prefix',
149 + 'suf' => 'suffix',
150 + 'i' => 'icon',
151 + 'titl' => 'title',
152 + 'sub' => 'sub',
153 + 'hlp' => 'helper',
154 + 'txt' => 'text',
155 + 'err' => 'error',
156 + 'msg' => 'message',
157 + 'btn' => 'button',
158 + 'inp' => 'input',
159 + 'opt' => 'option',
160 + 'icn' => 'icon',
161 + 'img' => 'image',
162 + 'smbl' => 'symbol',
163 + 'req' => 'required',
164 + 'frm' => 'form',
165 + 'bg' => 'background',
166 + ];
167 +
168 + $splitElements = explode('-', $element);
169 + $meaningfulParts = [];
170 +
171 + foreach ($splitElements as $part) {
172 + $cleanPart = ltrim($part, '_');
173 + $meaningfulParts[] = isset($abbreviations[$cleanPart]) ? $abbreviations[$cleanPart] : $cleanPart;
174 + }
175 +
176 + return implode('-', $meaningfulParts);
177 + }
178 +
69 179 public function replaceToBackSlash($str)
70 180 {
71 181 $phReplaceToBackslash = str_replace('$_bf_$', '\\', $str); // Replace React inputs value "$_bf_$" to '\\'
72 182 return FieldValueHandler::replaceSmartTagWithValue($phReplaceToBackslash);
@@ -76,18 +186,54 @@
76 186 * @param $element @exmp $element = 'fld-wrp'
77 187 */
78 188 public function icon($icnPropName, $element)
79 189 {
80 - if ($this->property_exists_nested($this->_fld, $icnPropName, '', 1)) {
81 - return <<<ICON
82 -<img
83 - {$this->getCustomAttributes($element)}
84 - class="{$this->getAtomicCls($element)} {$this->getCustomClasses($element)}"
85 - src="{$this->esc_url($this->_fld->{$icnPropName})}"
86 - alt=""
87 -/>
88 -ICON;
190 + $hasIconImg = $this->property_exists_nested($this->_fld, $icnPropName, '', 1);
191 + $showPasswordIcon = $this->property_exists_nested($this->_fld, 'typ', 'password')
192 + && $this->property_exists_nested($this->_fld, 'config->showPasswordIcon', true);
193 + // For password suffix, render a shared suffix wrapper that can contain both
194 + // show/hide SVG and the configured suffix image (in this order).
195 + if ('suf-i' === $element && ($hasIconImg || $showPasswordIcon)) {
196 + $toggleButton = '';
197 + $suffixIconImg = '';
198 + if ($showPasswordIcon) {
199 + $fieldKey = $this->esc_attr($this->_fk);
200 + $toggleBtnId = "bf-pw-showhide-btn-{$fieldKey}";
201 + $toggleSlashId = "bf-pw-eye-{$fieldKey}";
202 + $toggleButton = '<button id="' . $toggleBtnId . '" class="show-hide-password-btn" data-fldkey="' . $fieldKey . '" type="button" title="Show password" aria-label="Show password" aria-pressed="false" style="height: 20px; width: 20px; cursor: pointer; border: none; background: none; padding: 0; margin: 0; outline: none;">'
203 + . '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="20">'
204 + . '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />'
205 + . '<circle cx="12" cy="12" r="3" />'
206 + . '<path d="M20 4l-16 16" id="' . $toggleSlashId . '" style="display: none;"/>'
207 + . '</svg>'
208 + . '</button>';
209 + }
210 +
211 + if ($hasIconImg) {
212 + $suffixIconImg = '<img'
213 + . $this->getCustomAttributes($element)
214 + . ' src="' . $this->esc_url($this->_fld->{$icnPropName}) . '"'
215 + . ' alt=""'
216 + . ' aria-hidden="true"'
217 + . '/>';
218 + }
219 + if ($toggleButton) {
220 + return '<div class="' . $this->getAtomicCls($element) . ' ' . $this->getCustomClasses($element) . '">'
221 + . $toggleButton
222 + . $suffixIconImg
223 + . '</div>';
224 + }
89 225 }
226 +
227 + if ($hasIconImg) {
228 + return '<img'
229 + . $this->getCustomAttributes($element)
230 + . ' class="' . $this->getAtomicCls($element) . ' ' . $this->getCustomClasses($element) . '"'
231 + . ' src="' . $this->esc_url($this->_fld->{$icnPropName}) . '"'
232 + . ' alt=""'
233 + . ' aria-hidden="true"'
234 + . '/>';
235 + }
90 236 return '';
91 237 }
92 238
93 239 public function property_exists_nested($obj, $path = '', $valToCheck = null, $checkNegativeVal = 0)
@@ -130,8 +276,64 @@
130 276 }
131 277 return '';
132 278 }
133 279
280 + public function mn()
281 + {
282 + if ($this->property_exists_nested($this->_fld, 'mn', '', 1)) {
283 + return "min='{$this->esc_attr($this->_fld->mn)}'";
284 + }
285 + return '';
286 + }
287 +
288 + public function mx()
289 + {
290 + if ($this->property_exists_nested($this->_fld, 'mx', '', 1)) {
291 + return "max='{$this->esc_attr($this->_fld->mx)}'";
292 + }
293 + return '';
294 + }
295 +
296 + public function minlength()
297 + {
298 + if ($this->property_exists_nested($this->_fld, 'valid->minlength', '', 1)) {
299 + return "minlength='{$this->esc_attr($this->_fld->valid->minlength)}'";
300 + }
301 + return '';
302 + }
303 +
304 + public function maxlength()
305 + {
306 + if ($this->property_exists_nested($this->_fld, 'valid->maxlength', '', 1)) {
307 + return "maxlength='{$this->esc_attr($this->_fld->valid->maxlength)}'";
308 + }
309 + return '';
310 + }
311 +
312 + public function maxword()
313 + {
314 + if ($this->property_exists_nested($this->_fld, 'valid->maxword', '', 1)) {
315 + return "data-maxword='{$this->esc_attr($this->_fld->valid->maxword)}'";
316 + }
317 + return '';
318 + }
319 +
320 + public function minword()
321 + {
322 + if ($this->property_exists_nested($this->_fld, 'valid->minword', '', 1)) {
323 + return "data-minword='{$this->esc_attr($this->_fld->valid->minword)}'";
324 + }
325 + return '';
326 + }
327 +
328 + public function inputMode()
329 + {
330 + if ($this->property_exists_nested($this->_fld, 'inputMode', '', 1)) {
331 + return "inputMode='{$this->esc_attr($this->_fld->inputMode)}'";
332 + }
333 + return '';
334 + }
335 +
134 336 public function readonly()
135 337 {
136 338 if ($this->property_exists_nested($this->_fld, 'readonly', true)) {
137 339 return 'readonly';
@@ -208,10 +410,12 @@
208 410 }
209 411 if ($this->property_exists_nested($this->_fld, 'typ', 'file-up') && $this->property_exists_nested($this->_fld, 'config->multiple', true)) {
210 412 return "name='{$this->esc_attr($this->_fld->fieldName)}[]'";
211 413 }
212 - if ($this->property_exists_nested($this->_fld, 'typ', 'image-select')
213 - && $this->property_exists_nested($this->_fld, 'inpType', 'checkbox')) {
414 + if (
415 + $this->property_exists_nested($this->_fld, 'typ', 'image-select')
416 + && $this->property_exists_nested($this->_fld, 'inpType', 'checkbox')
417 + ) {
214 418 return "name='{$this->esc_attr($this->_fld->fieldName)}[]'";
215 419 }
216 420 return "name='{$this->esc_attr($this->_fld->fieldName)}'";
217 421 }
@@ -223,11 +427,11 @@
223 427 $val = '';
224 428 if ($this->property_exists_nested($this->_fld, 'val', '', 1)) {
225 429 $val = $this->_fld->val;
226 430 } elseif ($this->property_exists_nested($this->_fld, 'defaultValue', '', 1)) {
227 - $val = $this->_fld->defaultValue;
431 + $val = FieldValueHandler::replaceSmartTagWithValue($this->_fld->defaultValue);
228 432 }
229 - if (!empty($val) && 'textarea' !== $this->_fld->typ) {
433 + if (isset($val) && '' !== $val && 'textarea' !== $this->_fld->typ) {
230 434 return "value='{$this->esc_attr($val)}'";
231 435 }
232 436 return $val;
233 437 }
@@ -255,6 +459,100 @@
255 459 {
256 460 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
257 461 $contentCount = count($bfFrontendFormIds);
258 462 return "{$this->_fk}-{$contentCount}";
463 + }
464 +
465 + public function isChecked()
466 + {
467 + if (!isset($this->_fld->val)) {
468 + return false;
469 + }
470 + $checked = false;
471 + $msg = $this->_fld->msg;
472 + foreach ($msg as $status => $value) {
473 + if ($value === $this->_fld->val) {
474 + if ('checked' === $status) {
475 + $checked = true;
476 + break;
477 + }
478 + }
479 + }
480 +
481 + return $checked;
482 + }
483 +
484 + /**
485 + * Get error message container ID for accessibility
486 + * @return string
487 + */
488 + public function getErrorId()
489 + {
490 + return "{$this->_fk}-err-txt";
491 + }
492 +
493 + /**
494 + * Get helper text container ID for accessibility
495 + * @return string
496 + */
497 + public function getHelperTextId()
498 + {
499 + return "{$this->_fk}-hlp-txt";
500 + }
501 +
502 + /**
503 + * Get label ID for accessibility (aria-labelledby)
504 + * @return string
505 + */
506 + public function getLabelId()
507 + {
508 + $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
509 + $contentCount = count($bfFrontendFormIds);
510 + return "{$this->_fk}-{$contentCount}-lbl";
511 + }
512 +
513 + /**
514 + * Check if field has helper text
515 + * @return bool
516 + */
517 + public function hasHelperText()
518 + {
519 + return $this->property_exists_nested($this->_fld, 'helperTxt', '', 1);
520 + }
521 +
522 + /**
523 + * Generate aria-describedby attribute linking to helper text and error message
524 + * @return string
525 + */
526 + public function ariaDescribedBy()
527 + {
528 + $ids = [];
529 + if ($this->hasHelperText()) {
530 + $ids[] = $this->getHelperTextId();
531 + }
532 + // Always include error ID so it can be announced when errors appear
533 + $ids[] = $this->getErrorId();
534 +
535 + return !empty($ids) ? 'aria-describedby="' . implode(' ', $ids) . '"' : '';
536 + }
537 +
538 + /**
539 + * Generate aria-required attribute for required fields
540 + * @return string
541 + */
542 + public function ariaRequired()
543 + {
544 + if ($this->property_exists_nested($this->_fld, 'valid->req', true)) {
545 + return 'aria-required="true"';
546 + }
547 + return '';
548 + }
549 +
550 + /**
551 + * Get the field data object
552 + * @return object
553 + */
554 + public function getFieldData()
555 + {
556 + return $this->_fld;
259 557 }
260 558 }