PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.2.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.2.2
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 2.10.2 All 137 releases
bit-form / includes / Frontend / Form / View / FieldHelpers.php

FieldHelpers.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.2.2, at includes/Frontend/Form/View/FieldHelpers.php

559 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\Frontend\Form\View;
4
5 use BitCode\BitForm\Admin\Form\Helpers;
6 use BitCode\BitForm\Core\Util\EscapingHelper;
7 use BitCode\BitForm\Core\Util\FieldValueHandler;
8 use BitCode\BitForm\Core\Util\FrontendHelpers;
9
10 class FieldHelpers
11 {
12 private $_fld;
13 private $_fk;
14 private $_form_atomic_Cls_map;
15 private $_formID;
16
17 public function __construct($field, $fk, $form_atomic_Cls_map = null, $formID = null)
18 {
19 $this->_fld = $field;
20 $this->_fk = $fk;
21 $this->_form_atomic_Cls_map = $form_atomic_Cls_map;
22 $this->_formID = $formID;
23 }
24
25 public function getCustomAttributes($element)
26 {
27 if ($this->property_exists_nested($this->_fld, "customAttributes->{$element}")) {
28 $attrString = '';
29 $customAttributs = $this->_fld->customAttributes->{$element};
30
31 foreach ($customAttributs as $attr) {
32 if (!isset($attr->key) || !EscapingHelper::isSafeAttributeKey((string) $attr->key)) {
33 continue;
34 }
35 $attrString .= " {$this->esc_attr($attr->key)}='{$this->esc_attr($attr->value)}'";
36 }
37 return $attrString;
38 }
39 return '';
40 }
41
42 public function getCustomClasses($element)
43 {
44 if ($this->property_exists_nested($this->_fld, "customClasses->{$element}")) {
45 return $this->esc_attr($this->_fld->customClasses->{$element});
46 }
47 return '';
48 }
49
50 public function renderHTMR($content)
51 {
52 $safeContent = Helpers::sanitizeUserHTML($content);
53 return $safeContent;
54 }
55
56 public function getAtomicCls($element)
57 {
58 $meaningfulElementClass = "bf-{$this->getMeaningfullElementClass($element)}";
59 if (empty($this->_fld) && empty($this->_fk)) {
60 if (is_object($this->_form_atomic_Cls_map) && property_exists($this->_form_atomic_Cls_map, ".$element")) {
61 $getAtomicCls = $this->_form_atomic_Cls_map->{".$element"};
62 return implode(' ', $getAtomicCls) . " {$element} {$meaningfulElementClass}";
63 }
64 return "$element {$meaningfulElementClass}";
65 }
66 if (isset($this->_fld->typ) && 'advanced-file-up' === $this->_fld->typ && isset($this->_form_atomic_Cls_map->{".$element"})) {
67 $getAtomicCls = $this->_form_atomic_Cls_map->{".$element"};
68 return implode(' ', $getAtomicCls) . " {$this->_fk}-{$element} {$meaningfulElementClass}";
69 }
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}";
83 }
84 return "{$this->_fk}-{$element} {$formId}-{$element} bf-{$element} {$meaningfulElementClass}";
85 }
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
179 public function replaceToBackSlash($str)
180 {
181 $phReplaceToBackslash = str_replace('$_bf_$', '\\', $str); // Replace React inputs value "$_bf_$" to '\\'
182 return FieldValueHandler::replaceSmartTagWithValue($phReplaceToBackslash);
183 }
184
185 /**
186 * @param $element @exmp $element = 'fld-wrp'
187 */
188 public function icon($icnPropName, $element)
189 {
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 }
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 }
236 return '';
237 }
238
239 public function property_exists_nested($obj, $path = '', $valToCheck = null, $checkNegativeVal = 0)
240 {
241 $path = explode('->', $path);
242 $current = $obj;
243 foreach ($path as $key) {
244 if (is_object($current)) {
245 if (property_exists($current, $key)) {
246 $current = $current->{$key};
247 } else {
248 return false;
249 }
250 } else {
251 break;
252 }
253 }
254 if (isset($valToCheck)) {
255 if ($checkNegativeVal) {
256 return $current !== $valToCheck;
257 }
258 return $current === $valToCheck;
259 }
260 return true;
261 }
262
263 public function required()
264 {
265 if ($this->property_exists_nested($this->_fld, 'valid->req', true) && $this->property_exists_nested($this->_fld, 'err->req->show', true)) {
266 return 'required';
267 }
268
269 return '';
270 }
271
272 public function disabled()
273 {
274 if ($this->property_exists_nested($this->_fld, 'valid->disabled', true)) {
275 return 'disabled';
276 }
277 return '';
278 }
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
336 public function readonly()
337 {
338 if ($this->property_exists_nested($this->_fld, 'readonly', true)) {
339 return 'readonly';
340 }
341 if ($this->property_exists_nested($this->_fld, 'valid->readonly', true)) {
342 return 'readonly';
343 }
344 return '';
345 }
346
347 public function placeholder()
348 {
349 if ($this->property_exists_nested($this->_fld, 'ph', '', 1)) {
350 return "placeholder='{$this->esc_attr($this->replaceToBackSlash($this->_fld->ph))}'";
351 }
352 return '';
353 }
354
355 /**
356 * @param $str
357 * for attribute value
358 * @return string
359 */
360 public function esc_attr($str)
361 {
362 return esc_attr($str);
363 }
364
365 /**
366 * @param $str
367 * for html content that don't need any html markup
368 * @return string
369 */
370 public function esc_html($str)
371 {
372 return esc_html($str);
373 }
374
375 /**
376 * @param $str
377 * for textarea content
378 * @return string
379 */
380 public function esc_textarea($str)
381 {
382 return esc_textarea($str);
383 }
384
385 /**
386 * @param $str
387 * for url
388 * @return string
389 */
390 public function esc_url($str)
391 {
392 return esc_url($str);
393 }
394
395 /**
396 * @param $str
397 * for content that needs html markup but escapes all scripts
398 * @return string
399 */
400 public function kses_post($str)
401 {
402 return wp_kses_post($str);
403 }
404
405 public function name()
406 {
407 if ($this->property_exists_nested($this->_fld, 'fieldName', '', 1)) {
408 if ($this->property_exists_nested($this->_fld, 'typ', 'check')) {
409 return "name='{$this->esc_attr($this->_fld->fieldName)}[]'";
410 }
411 if ($this->property_exists_nested($this->_fld, 'typ', 'file-up') && $this->property_exists_nested($this->_fld, 'config->multiple', true)) {
412 return "name='{$this->esc_attr($this->_fld->fieldName)}[]'";
413 }
414 if (
415 $this->property_exists_nested($this->_fld, 'typ', 'image-select')
416 && $this->property_exists_nested($this->_fld, 'inpType', 'checkbox')
417 ) {
418 return "name='{$this->esc_attr($this->_fld->fieldName)}[]'";
419 }
420 return "name='{$this->esc_attr($this->_fld->fieldName)}'";
421 }
422 return '';
423 }
424
425 public function value()
426 {
427 $val = '';
428 if ($this->property_exists_nested($this->_fld, 'val', '', 1)) {
429 $val = $this->_fld->val;
430 } elseif ($this->property_exists_nested($this->_fld, 'defaultValue', '', 1)) {
431 $val = FieldValueHandler::replaceSmartTagWithValue($this->_fld->defaultValue);
432 }
433 if (isset($val) && '' !== $val && 'textarea' !== $this->_fld->typ) {
434 return "value='{$this->esc_attr($val)}'";
435 }
436 return $val;
437 }
438
439 public function autoComplete()
440 {
441 if ($this->property_exists_nested($this->_fld, 'ac', '', 1)) {
442 return "autocomplete='{$this->esc_attr($this->_fld->ac)}'";
443 }
444 return '';
445 }
446
447 // getter & setter function for $_fk
448 public function getFk()
449 {
450 return $this->_fk;
451 }
452
453 public function setFk($fk)
454 {
455 $this->_fk = $fk;
456 }
457
458 public function getFieldKeyWithContentCount()
459 {
460 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
461 $contentCount = count($bfFrontendFormIds);
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;
557 }
558 }
559