| @@ -15,8 +15,11 @@ | ||
| 15 | 15 | private $_fieldHelpers; |
| 16 | 16 | |
| 17 | 17 | public function __construct($field, $rowID, $field_name, $form_atomic_Cls_map, $formID, $error = null, $value = null) |
| 18 | 18 | { |
| 19 | + // Corrupt/partial form JSON can hand us a null field; normalize so | |
| 20 | + // property_exists() calls never receive null (PHP 8 TypeError). | |
| 21 | + $field = is_object($field) ? $field : new \stdClass(); | |
| 19 | 22 | $this->_fieldData = $field; |
| 20 | 23 | $this->_fieldKey = $rowID; |
| 21 | 24 | $this->_fieldName = $field_name; |
| 22 | 25 | $this->_formID = $formID; |
| @@ -29,26 +32,31 @@ | ||
| 29 | 32 | { |
| 30 | 33 | $labelWrapper = !$noLabel ? $this->labelWrapper() : ''; |
| 31 | 34 | $helperText = $this->helperText(); |
| 32 | 35 | $error = !$noErrMsg ? $this->errorMessages() : ''; |
| 33 | - $inputWrapper = <<<INPUTWRAPPER | |
| 34 | - <div | |
| 35 | - {$this->_fieldHelpers->getCustomAttributes('fld-wrp')} | |
| 36 | - class="{$this->_fieldHelpers->getAtomicCls('fld-wrp')} {$this->_fieldHelpers->getCustomClasses('fld-wrp')}" | |
| 37 | - > | |
| 38 | - {$labelWrapper} | |
| 39 | - <div | |
| 40 | - {$this->_fieldHelpers->getCustomAttributes('inp-wrp')} | |
| 41 | - class="{$this->_fieldHelpers->getAtomicCls('inp-wrp')} {$this->_fieldHelpers->getCustomClasses('inp-wrp')}" | |
| 42 | - > | |
| 43 | - {$field} | |
| 44 | - {$helperText} | |
| 45 | - {$error} | |
| 46 | - </div> | |
| 47 | - </div> | |
| 48 | -INPUTWRAPPER; | |
| 36 | + $fieldTypeClass = empty($this->_fieldData->typ) ? 'bf-default-field' : 'bf-' . $this->_fieldData->typ . '-field'; | |
| 49 | 37 | |
| 50 | - return $inputWrapper; | |
| 38 | + return sprintf( | |
| 39 | + '<div %1$s class="%2$s %3$s %4$s"> | |
| 40 | + %5$s | |
| 41 | + <div %6$s class="%7$s %8$s"> | |
| 42 | + %9$s | |
| 43 | + %10$s | |
| 44 | + %11$s | |
| 45 | + </div> | |
| 46 | + </div>', | |
| 47 | + $this->_fieldHelpers->getCustomAttributes('fld-wrp'), | |
| 48 | + $fieldTypeClass, | |
| 49 | + $this->_fieldHelpers->getAtomicCls('fld-wrp'), | |
| 50 | + $this->_fieldHelpers->getCustomClasses('fld-wrp'), | |
| 51 | + $labelWrapper, | |
| 52 | + $this->_fieldHelpers->getCustomAttributes('inp-wrp'), | |
| 53 | + $this->_fieldHelpers->getAtomicCls('inp-wrp'), | |
| 54 | + $this->_fieldHelpers->getCustomClasses('inp-wrp'), | |
| 55 | + $field, | |
| 56 | + $helperText, | |
| 57 | + $error | |
| 58 | + ); | |
| 51 | 59 | } |
| 52 | 60 | |
| 53 | 61 | private function labelWrapper() |
| 54 | 62 | { |
| @@ -72,36 +80,37 @@ | ||
| 72 | 80 | $_subtitleText = wp_kses_post($this->_fieldHelpers->renderHTMR($replaceToBackSlash)); |
| 73 | 81 | } |
| 74 | 82 | |
| 75 | 83 | // for pre required icon |
| 84 | + // aria-hidden="true" is used because the asterisk is purely decorative/visual. | |
| 85 | + // Screen readers will announce the field as required via aria-required="true" on the input element. | |
| 86 | + // This prevents redundant "asterisk" or "star" announcements. | |
| 76 | 87 | if ( |
| 77 | 88 | $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->req') |
| 78 | 89 | && $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->reqShow', true) |
| 79 | 90 | && $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->reqPos', 'before') |
| 80 | 91 | ) { |
| 81 | - $_reqPre = <<<REQPRE | |
| 82 | - <span | |
| 83 | - {$this->_fieldHelpers->getCustomAttributes('req-smbl')} | |
| 84 | - class="{$this->_fieldHelpers->getAtomicCls('req-smbl')} {$this->_fieldHelpers->getCustomClasses('req-smbl')}" | |
| 85 | - > | |
| 86 | - * | |
| 87 | - </span> | |
| 88 | -REQPRE; | |
| 92 | + $_reqPre = sprintf( | |
| 93 | + '<span %1$s class="%2$s %3$s" aria-hidden="true">*</span>', | |
| 94 | + $this->_fieldHelpers->getCustomAttributes('req-smbl'), | |
| 95 | + $this->_fieldHelpers->getAtomicCls('req-smbl'), | |
| 96 | + $this->_fieldHelpers->getCustomClasses('req-smbl') | |
| 97 | + ); | |
| 89 | 98 | } |
| 90 | 99 | // for post required icon |
| 100 | + // Same accessibility reasoning as pre required icon - hide from screen readers | |
| 101 | + // to avoid redundant announcements since aria-required is on the input. | |
| 91 | 102 | if ( |
| 92 | 103 | $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->req') |
| 93 | 104 | && $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->reqShow', true) |
| 94 | 105 | && $this->_fieldHelpers->property_exists_nested($this->_fieldData, 'valid->reqPos', 'before', 1) |
| 95 | 106 | ) { |
| 96 | - $_reqPost = <<<REQPOST | |
| 97 | - <span | |
| 98 | - {$this->_fieldHelpers->getCustomAttributes('req-smbl')} | |
| 99 | - class="{$this->_fieldHelpers->getAtomicCls('req-smbl')} {$this->_fieldHelpers->getCustomClasses('req-smbl')}" | |
| 100 | - > | |
| 101 | - * | |
| 102 | - </span> | |
| 103 | -REQPOST; | |
| 107 | + $_reqPost = sprintf( | |
| 108 | + '<span %1$s class="%2$s %3$s" aria-hidden="true">*</span>', | |
| 109 | + $this->_fieldHelpers->getCustomAttributes('req-smbl'), | |
| 110 | + $this->_fieldHelpers->getAtomicCls('req-smbl'), | |
| 111 | + $this->_fieldHelpers->getCustomClasses('req-smbl') | |
| 112 | + ); | |
| 104 | 113 | } |
| 105 | 114 | |
| 106 | 115 | // for Label |
| 107 | 116 | if ( |
| @@ -110,44 +119,56 @@ | ||
| 110 | 119 | // && $this->_fieldData->valid->hideLbl !== true |
| 111 | 120 | ) { |
| 112 | 121 | $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds; |
| 113 | 122 | $contentCount = count($bfFrontendFormIds); |
| 114 | - $_label = <<<LABEL | |
| 115 | - <label | |
| 116 | - {$this->_fieldHelpers->getCustomAttributes('lbl')} | |
| 117 | - class="{$this->_fieldHelpers->getAtomicCls('lbl')} {$this->_fieldHelpers->getCustomClasses('lbl')}" | |
| 118 | - for="{$this->_fieldKey}-{$contentCount}"> | |
| 119 | - {$_reqPre} | |
| 120 | - {$_lblPreIcn} | |
| 121 | - {$_lbl} | |
| 122 | - {$_lblSufIcn} | |
| 123 | - {$_reqPost} | |
| 124 | - </label> | |
| 125 | -LABEL; | |
| 123 | + $labelId = $this->_fieldHelpers->getLabelId(); | |
| 124 | + $_label = sprintf( | |
| 125 | + '<label | |
| 126 | + id="%1$s" | |
| 127 | + %2$s | |
| 128 | + class="%3$s %4$s" | |
| 129 | + for="%5$s-%6$d"> | |
| 130 | + %7$s | |
| 131 | + %8$s | |
| 132 | + %9$s | |
| 133 | + %10$s | |
| 134 | + %11$s | |
| 135 | + </label>', | |
| 136 | + $labelId, | |
| 137 | + $this->_fieldHelpers->getCustomAttributes('lbl'), | |
| 138 | + $this->_fieldHelpers->getAtomicCls('lbl'), | |
| 139 | + $this->_fieldHelpers->getCustomClasses('lbl'), | |
| 140 | + $this->_fieldKey, | |
| 141 | + $contentCount, | |
| 142 | + $_reqPre, | |
| 143 | + $_lblPreIcn, | |
| 144 | + $_lbl, | |
| 145 | + $_lblSufIcn, | |
| 146 | + $_reqPost | |
| 147 | + ); | |
| 126 | 148 | } |
| 127 | 149 | |
| 128 | 150 | // for subtitle |
| 129 | - if (property_exists($this->_fieldData, 'subtitle') && $this->_fieldData->subtitleHide) { | |
| 130 | - $_subtitle = <<<SUBTITLE | |
| 131 | - <div | |
| 132 | - {$this->_fieldHelpers->getCustomAttributes('sub-titl')} | |
| 133 | - class="{$this->_fieldHelpers->getAtomicCls('sub-titl')} {$this->_fieldHelpers->getCustomClasses('sub-titl')}" | |
| 134 | - > | |
| 135 | - {$_subtitlePreIcn} | |
| 136 | - {$_subtitleText} | |
| 137 | - {$_subtitlePostIcn} | |
| 138 | - </div> | |
| 139 | -SUBTITLE; | |
| 151 | + if (property_exists($this->_fieldData, 'subtitle') && !empty($this->_fieldData->subtitleHide)) { | |
| 152 | + $_subtitle = sprintf( | |
| 153 | + '<div %1$s class="%2$s %3$s">%4$s%5$s%6$s</div>', | |
| 154 | + $this->_fieldHelpers->getCustomAttributes('sub-titl'), | |
| 155 | + $this->_fieldHelpers->getAtomicCls('sub-titl'), | |
| 156 | + $this->_fieldHelpers->getCustomClasses('sub-titl'), | |
| 157 | + $_subtitlePreIcn, | |
| 158 | + $_subtitleText, | |
| 159 | + $_subtitlePostIcn | |
| 160 | + ); | |
| 140 | 161 | } |
| 141 | - return <<<LABELWRAPPER | |
| 142 | - <div | |
| 143 | - {$this->_fieldHelpers->getCustomAttributes('lbl-wrp')} | |
| 144 | - class="{$this->_fieldHelpers->getAtomicCls('lbl-wrp')} {$this->_fieldHelpers->getCustomClasses('lbl-wrp')}" | |
| 145 | - > | |
| 146 | - {$_label} | |
| 147 | - {$_subtitle} | |
| 148 | - </div> | |
| 149 | -LABELWRAPPER; | |
| 162 | + | |
| 163 | + return sprintf( | |
| 164 | + '<div %1$s class="%2$s %3$s">%4$s%5$s</div>', | |
| 165 | + $this->_fieldHelpers->getCustomAttributes('lbl-wrp'), | |
| 166 | + $this->_fieldHelpers->getAtomicCls('lbl-wrp'), | |
| 167 | + $this->_fieldHelpers->getCustomClasses('lbl-wrp'), | |
| 168 | + $_label, | |
| 169 | + $_subtitle | |
| 170 | + ); | |
| 150 | 171 | } |
| 151 | 172 | |
| 152 | 173 | private function helperText() |
| 153 | 174 | { |
| @@ -159,18 +180,27 @@ | ||
| 159 | 180 | $_helperTxt = wp_kses_post($this->_fieldHelpers->renderHTMR($this->_fieldData->helperTxt)); |
| 160 | 181 | } |
| 161 | 182 | |
| 162 | 183 | if (property_exists($this->_fieldData, 'helperTxt') && $this->_fieldData->helperTxt) { |
| 163 | - $_helperTxt = <<<HELPERTEXT | |
| 164 | - <div | |
| 165 | - {$this->_fieldHelpers->getCustomAttributes('hlp-txt')} | |
| 166 | - class="{$this->_fieldHelpers->getAtomicCls('hlp-txt')} {$this->_fieldHelpers->getCustomClasses('hlp-txt')}" | |
| 167 | - > | |
| 168 | - {$_helperTxtPreIcn} | |
| 169 | - {$_helperTxt} | |
| 170 | - {$_helperTxtSufIcn} | |
| 171 | - </div> | |
| 172 | -HELPERTEXT; | |
| 184 | + $helperTextId = $this->_fieldHelpers->getHelperTextId(); | |
| 185 | + $_helperTxt = sprintf( | |
| 186 | + '<div | |
| 187 | + id="%1$s" | |
| 188 | + %2$s | |
| 189 | + class="%3$s %4$s" | |
| 190 | + > | |
| 191 | + %5$s | |
| 192 | + %6$s | |
| 193 | + %7$s | |
| 194 | + </div>', | |
| 195 | + $helperTextId, | |
| 196 | + $this->_fieldHelpers->getCustomAttributes('hlp-txt'), | |
| 197 | + $this->_fieldHelpers->getAtomicCls('hlp-txt'), | |
| 198 | + $this->_fieldHelpers->getCustomClasses('hlp-txt'), | |
| 199 | + $_helperTxtPreIcn, | |
| 200 | + $_helperTxt, | |
| 201 | + $_helperTxtSufIcn | |
| 202 | + ); | |
| 173 | 203 | } |
| 174 | 204 | |
| 175 | 205 | // for helper text |
| 176 | 206 | return $_helperTxt; |
| @@ -187,8 +217,9 @@ | ||
| 187 | 217 | $_error = wp_kses_post($this->_error); |
| 188 | 218 | $_style = 'margin-top: 5px !important; height: 9px !important;'; |
| 189 | 219 | } |
| 190 | 220 | |
| 221 | + $errorId = $this->_fieldHelpers->getErrorId(); | |
| 191 | 222 | $errMsgStart = ''; |
| 192 | 223 | $errMsgEnd = ''; |
| 193 | 224 | $msgStyle = "style='display: none !important;'"; |
| 194 | 225 | $errMsgAtomicCls = $this->_fieldHelpers->getAtomicCls('err-msg'); |
| @@ -202,17 +233,75 @@ | ||
| 202 | 233 | $errMsgCustomCls = ''; |
| 203 | 234 | $errMsgCustomAttr = ''; |
| 204 | 235 | } |
| 205 | 236 | |
| 206 | - return <<<ERRORMESSAGES | |
| 207 | - <div class='{$this->_fieldHelpers->getAtomicCls('err-wrp')}' style="{$_style}"> | |
| 208 | - <div class='{$this->_fieldHelpers->getAtomicCls('err-inner')}'> | |
| 209 | - {$errMsgStart} | |
| 210 | - {$_errorPreIcn} | |
| 211 | - <div {$this->_fieldHelpers->getCustomAttributes('err-txt')} {$errMsgCustomAttr} class="{$errMsgAtomicCls} {$errMsgCustomCls} {$this->_fieldHelpers->getAtomicCls('err-txt')} {$this->_fieldHelpers->getCustomClasses('err-txt')}" {$msgStyle}>{$_error}</div> | |
| 212 | - {$_errorSufIcn} | |
| 213 | - {$errMsgEnd} | |
| 214 | - </div> | |
| 215 | - </div> | |
| 216 | -ERRORMESSAGES; | |
| 237 | + return sprintf( | |
| 238 | + '<div class="%1$s" role="alert" aria-atomic="true" aria-live="assertive" style="%2$s"> | |
| 239 | + <div class="%3$s"> | |
| 240 | + %4$s | |
| 241 | + %5$s | |
| 242 | + <div | |
| 243 | + id="%6$s" | |
| 244 | + %7$s | |
| 245 | + %8$s | |
| 246 | + class="%9$s %10$s %11$s %12$s " | |
| 247 | + %13$s | |
| 248 | + > | |
| 249 | + %14$s | |
| 250 | + </div> | |
| 251 | + %15$s | |
| 252 | + %16$s | |
| 253 | + </div> | |
| 254 | + </div>', | |
| 255 | + $this->_fieldHelpers->getAtomicCls('err-wrp'), // 1 | |
| 256 | + $_style, // 2 | |
| 257 | + $this->_fieldHelpers->getAtomicCls('err-inner'), // 3 | |
| 258 | + $errMsgStart, // 4 | |
| 259 | + $_errorPreIcn, // 5 | |
| 260 | + $errorId, // 6 | |
| 261 | + $this->_fieldHelpers->getCustomAttributes('err-txt'), // 7 | |
| 262 | + $errMsgCustomAttr, // 8 | |
| 263 | + $errMsgAtomicCls, // 9 | |
| 264 | + $errMsgCustomCls, // 10 | |
| 265 | + $this->_fieldHelpers->getAtomicCls('err-txt'), // 11 | |
| 266 | + $this->_fieldHelpers->getCustomClasses('err-txt'), // 12 | |
| 267 | + $msgStyle, // 13 | |
| 268 | + $_error, // 14 | |
| 269 | + $_errorSufIcn, // 15 | |
| 270 | + $errMsgEnd // 16 | |
| 271 | + ); | |
| 272 | + } | |
| 273 | + | |
| 274 | + /** | |
| 275 | + * Get the field helpers instance for external access | |
| 276 | + * @return FieldHelpers | |
| 277 | + */ | |
| 278 | + public function getFieldHelpers() | |
| 279 | + { | |
| 280 | + return $this->_fieldHelpers; | |
| 281 | + } | |
| 282 | + | |
| 283 | + public function parentFieldWrapper($childFieldsInput) | |
| 284 | + { | |
| 285 | + return '<div ' | |
| 286 | + . $this->_fieldHelpers->getCustomAttributes('parent-fld-wrp') | |
| 287 | + . ' class="' . $this->_fieldHelpers->getAtomicCls('parent-fld-wrp') . ' ' . $this->_fieldHelpers->getCustomClasses('parent-fld-wrp') . '">' | |
| 288 | + . $childFieldsInput | |
| 289 | + . '</div>'; | |
| 290 | + } | |
| 291 | + | |
| 292 | + public function childFieldWrapper($fieldInput, $subFieldName = '', $colSpan = null, $childKey = '') | |
| 293 | + { | |
| 294 | + $dataAttr = $subFieldName ? ' data-bf-subfield="' . esc_attr($subFieldName) . '"' : ''; | |
| 295 | + $styleAttr = null !== $colSpan ? ' style="--bfv-addr-col:' . (int) $colSpan . '"' : ''; | |
| 296 | + // Per-child class so conditional actions (show/hide) can collapse the whole column. | |
| 297 | + $childKeyCls = $childKey ? ' ' . esc_attr($childKey) . '-sub-fld-wrp' : ''; | |
| 298 | + return '<div' | |
| 299 | + . $dataAttr | |
| 300 | + . $styleAttr | |
| 301 | + . ' ' | |
| 302 | + . $this->_fieldHelpers->getCustomAttributes('child-fld-wrp') | |
| 303 | + . ' class="' . $this->_fieldHelpers->getAtomicCls('child-fld-wrp') . ' ' . $this->_fieldHelpers->getCustomClasses('child-fld-wrp') . $childKeyCls . '">' | |
| 304 | + . $fieldInput | |
| 305 | + . '</div>'; | |
| 217 | 306 | } |
| 218 | 307 | } |