PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V3.3.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV3.3.2
V3.3.2 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 All 139 releases
← All changes | includes/Frontend/Form/View/FormViewer.php +128 -63 2.10.0 → V3.3.2 View file →
@@ -2,8 +2,9 @@
2 2
3 3 namespace BitCode\BitForm\Frontend\Form\View;
4 4
5 5 use BitCode\BitForm\Admin\Form\Helpers;
6 +use BitCode\BitForm\Core\Util\Translation\TranslationManager;
6 7 use BitCode\BitForm\Frontend\Form\FrontendFormManager;
7 8 use BitCode\BitForm\Frontend\Form\View\Conversational\ConversationalHelpers;
8 9 use BitCode\BitForm\Frontend\Form\View\Conversational\DefaultConversationalTheme;
9 10
@@ -56,17 +57,49 @@
56 57 $honeypodFldName = Helpers::honeypotEncryptedToken("_bitforms_{$this->getFormID()}_{$time}_");
57 58 $honeypotInput = '';
58 59 if (Helpers::property_exists_nested($this->_formContents, 'additional->enabled->honeypot', true)) {
59 60 $honeypotInput =
60 - <<<HTMLa
61 - <input type="text" class="d-none" name="b_h_t" value="{$honeypodFldName}">
62 - <input type="text" class="d-none" name="{$honeypodFldName}" required>
63 -HTMLa;
61 + '
62 + <input type="text" class="d-none" name="b_h_t" value="' . $honeypodFldName . '">
63 + <input type="text" class="d-none" name="' . $honeypodFldName . '" required>
64 + ';
64 65 return $honeypotInput;
65 66 }
66 67 return $honeypotInput;
67 68 }
68 69
70 + /**
71 + * Opt-out markers for HTML-layer translators (Google/GTranslate, TranslatePress)
72 + * when a form disables translation. Empty otherwise, so markup is untouched.
73 + *
74 + * @return array{attrs:string, cls:string}
75 + */
76 + private function noTranslateMarkup()
77 + {
78 + // Pass the row we already hold; otherwise the lookup re-queries form_content per render.
79 + $rawContent = $this->_form->isExist() ? $this->_form->getFieldsContent() : null;
80 + $enabled = TranslationManager::isFormTranslationEnabled($this->getFormID(), $rawContent);
81 + if ($enabled) {
82 + return ['attrs' => '', 'cls' => ''];
83 + }
84 + return ['attrs' => ' translate="no" data-no-translation', 'cls' => ' notranslate'];
85 + }
86 +
87 + /**
88 + * Carries the page language into the AJAX submission so validation and
89 + * confirmation strings match it. Empty when no provider resolves one.
90 + *
91 + * @return string
92 + */
93 + private function languageField()
94 + {
95 + $lang = (string) apply_filters('bitform_current_language', '', $this->getFormID());
96 + if ('' === $lang) {
97 + return '';
98 + }
99 + return '<input type="text" class="d-none" name="bf_lang" value="' . esc_attr($lang) . '">';
100 + }
101 +
69 102 public function setTheme($theme = 'Default')
70 103 {
71 104 $this->_theme = $theme;
72 105 }
@@ -122,9 +155,9 @@
122 155 }
123 156
124 157 public function fields($rowID)
125 158 {
126 - return $this->_fields->{$rowID};
159 + return $this->_fields->{$rowID} ?? null;
127 160 }
128 161
129 162 public function getNestedLayout($rowID)
130 163 {
@@ -147,8 +180,9 @@
147 180 }
148 181
149 182 public function getConversationalView($hasFile, $msg = null)
150 183 {
184 + $noTranslate = $this->noTranslateMarkup();
151 185 $conversationSettings = $this->getFormInfo()->conversationalSettings;
152 186 $this->_themeDetails = new DefaultConversationalTheme($conversationSettings);
153 187 $file_upload_tag = null;
154 188 $restrictionMsg = '';
@@ -178,9 +212,9 @@
178 212 }
179 213 }
180 214 $conversationNavigationHtml = $conversationalHelper->getNavigationView();
181 215
182 - $confMsg = $this->_form->getSuccessMessageMarkups();
216 + // $confMsg = $this->_form->getSuccessMessageMarkups();
183 217 $abandonmentMsg = $this->_form->getFormAbandonmentMessage();
184 218
185 219 // if (!empty($this->_buttons)) {
186 220 // $this->_buttons->name = $formIdentifier;
@@ -188,41 +222,56 @@
188 222 // $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
189 223 // }
190 224
191 225 if (!empty($msg)) {
192 - $restrictionMsg = <<<MSG
193 - <div class="{$this->_form->getAtomicCls("_frm-ovrly-b{$formID}")}">
194 - <p class="{$this->_form->getAtomicCls("_frm-ovrly-msg-b{$formID}")}">
195 - $msg
196 - </p>
197 - </div>
198 -MSG;
226 + $restrictionMsg = $this->restrictionOverlay($msg, $formID);
199 227 }
200 228 $formHTML =
201 - <<<HTMLa
202 - <div id="{$formIdentifier}" class="{$this->_form->getAtomicCls("_frm-bg-b{$formID}")}">
203 - $restrictionMsg
204 - <form novalidate id="form-{$formIdentifier}" class="_frm-bc{$formID}" $file_upload_tag method='post'>
205 - <input type="text" class="d-none" name="csrf" value="{$this->_tokens['csrf']}">
206 - <input type="text" class="d-none" name="t_identity" value="{$this->_tokens['t_identity']}">
207 - {$this->honeypotField()}
208 - <input type="text" class="d-none" name="bitforms_id" value="bitforms_{$this->_form->getFormID()}">
209 - <div class="bc{$formID}-steps-container">
210 - $welcomePageHtml
211 - $fieldHtml
229 + '
230 + <div id="' . $formIdentifier . '"' . $noTranslate['attrs'] . ' class="b' . $formID . '-bit-form ' . $this->_form->getAtomicCls("_frm-bg-b{$formID}") . ' bit-form bf-form-wrapper' . $noTranslate['cls'] . '">
231 + ' . $restrictionMsg . '
232 + <form novalidate id="form-' . $formIdentifier . '" class="_frm-bc' . $formID . ' bf-form" ' . $file_upload_tag . ' method=\'post\'>
233 + <input type="text" class="d-none" name="csrf" value="' . $this->_tokens['csrf'] . '">
234 + <input type="text" class="d-none" name="t_identity" value="' . $this->_tokens['t_identity'] . '">
235 + ' . $this->honeypotField() . '
236 + <input type="text" class="d-none" name="bitforms_id" value="bitforms_' . $this->_form->getFormID() . '">
237 + ' . $this->languageField() . '
238 + <div class="bc' . $formID . '-steps-container">
239 + ' . $welcomePageHtml . '
240 + ' . $fieldHtml . '
241 + ' . $conversationNavigationHtml . '
212 242 </div>
213 243 </form>
214 - <div id='bc-form-msg-wrp-{$formIdentifier}' class="bc-form-msg-wrp"></div>
215 - $abandonmentMsg
216 - $conversationNavigationHtml
244 + <div id=\'bc-form-msg-wrp-' . $formIdentifier . '\' class="bc-form-msg-wrp"></div>
245 + ' . $abandonmentMsg . '
217 246 </div>
218 -HTMLa;
247 +';
219 248
220 249 return $formHTML;
221 250 }
222 251
252 + /**
253 + * Restriction overlay; message may hold the visitor IP, so it is kses-filtered.
254 + *
255 + * @param string $msg Restriction message
256 + * @param int $formID Form ID
257 + *
258 + * @return string Overlay markup
259 + */
260 + private function restrictionOverlay($msg, $formID)
261 + {
262 + return '
263 + <div class="' . $this->_form->getAtomicCls("_frm-ovrly-b{$formID}") . '">
264 + <p class="' . $this->_form->getAtomicCls("_frm-ovrly-msg-b{$formID}") . '">
265 + ' . wp_kses_post($msg) . '
266 + </p>
267 + </div>
268 +';
269 + }
270 +
223 271 private function setView($hasFile, $msg)
224 272 {
273 + $noTranslate = $this->noTranslateMarkup();
225 274 $file_upload_tag = null;
226 275 $restrictionMsg = '';
227 276 $formID = $this->_form->getFormID();
228 277 if ($hasFile) {
@@ -242,32 +291,43 @@
242 291 $fieldHtml .= $this->getLayoutHtml($layouts);
243 292 } else {
244 293 $formViewHelper = new FormViewHelper($this->_form, $this->_formContents);
245 294
246 - $fieldHtml .= <<<STEPWRPR
247 - <div class='{$this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnr")}'>
248 - {$formViewHelper->getStepHeaderHtml()}
249 - <div class='{$this->_form->getAtomicCls("_frm-b{$formID}-stp-wrpr")}'>
250 - {$formViewHelper->getProgressBarMarkup()}
251 - <div class='{$this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt-wrpr")}'>
252 -STEPWRPR;
295 + $fieldHtml .=
296 + '
297 + <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnr") . ' _frm-b-stp-cntnr\'>
298 + ' . $formViewHelper->getStepHeaderHtml() . '
299 + <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-wrpr") . ' _frm-b-stp-wrpr\'>
300 + ' . $formViewHelper->getProgressBarMarkup() . '
301 + <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt-wrpr") . ' _frm-b-stp-cntnt-wrpr\'>
302 +';
253 303 foreach ($layouts as $key => $lay) {
254 304 $hideOtherSteps = $key > 0 ? 'deactive' : '';
255 305 $step = $key + 1;
256 - $fieldHtml .= <<<HTMLa
257 - <div class="{$this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt")} $hideOtherSteps" data-step="{$step}">
258 - <div class="_frm-b{$formID}">
259 - {$this->getLayoutHtml($lay->layout)}
306 + $ariaHidden = $key > 0 ? 'aria-hidden="true"' : '';
307 + $fieldHtml .=
308 + '
309 + <div
310 + class="' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt") . ' _frm-b-stp-cntnt ' . $hideOtherSteps . '"
311 + data-step="' . $step . '"
312 + role="tabpanel"
313 + id="step-' . $formID . '-' . $step . '-panel"
314 + aria-labelledby="step-' . $formID . '-' . $step . '-tab"
315 + ' . $ariaHidden . '
316 + >
317 + <div class="_frm-b' . $formID . ' _frm-b">
318 + ' . $this->getLayoutHtml($lay->layout) . '
260 319 </div>
261 - {$formViewHelper->getStepButtonMarkup()}
320 + ' . $formViewHelper->getStepButtonMarkup() . '
262 321 </div>
263 -HTMLa;
322 +';
264 323 }
265 - $fieldHtml .= <<<CLOSINGTAG
324 + $fieldHtml .=
325 + '
266 326 </div>
267 327 </div>
268 328 </div>
269 -CLOSINGTAG;
329 +';
270 330 }
271 331
272 332 $confMsg = $this->_form->getSuccessMessageMarkups();
273 333 $abandonmentMsg = $this->_form->getFormAbandonmentMessage();
@@ -278,31 +338,36 @@
278 338 $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
279 339 }
280 340
281 341 if (!empty($msg)) {
282 - $restrictionMsg = <<<MSG
283 - <div class="{$this->_form->getAtomicCls("_frm-ovrly-b{$formID}")}">
284 - <p class="{$this->_form->getAtomicCls("_frm-ovrly-msg-b{$formID}")}">
285 - $msg
286 - </p>
287 - </div>
288 -MSG;
342 + $restrictionMsg = $this->restrictionOverlay($msg, $formID);
289 343 }
290 344 $formHTML =
291 - <<<HTMLa
292 - <div id="{$formIdentifier}" class="{$this->_form->getAtomicCls("_frm-bg-b{$formID}")}">
293 - $restrictionMsg
294 - <form novalidate id="form-{$formIdentifier}" class="{$this->_form->getAtomicCls("_frm-b{$formID}")}" $file_upload_tag method='post'>
295 - <input type="text" class="d-none" name="csrf" value="{$this->_tokens['csrf']}">
296 - <input type="text" class="d-none" name="t_identity" value="{$this->_tokens['t_identity']}">
297 - {$this->honeypotField()}
298 - <input type="text" class="d-none" name="bitforms_id" value="bitforms_{$this->_form->getFormID()}">
299 - $fieldHtml
345 + '
346 + <div id="' . $formIdentifier . '"' . $noTranslate['attrs'] . ' class="b' . $formID . '-bit-form ' . $this->_form->getAtomicCls("_frm-bg-b{$formID}") . ' bit-form bf-form-wrapper' . $noTranslate['cls'] . '">
347 + ' . $restrictionMsg . '
348 + <form novalidate id="form-' . $formIdentifier . '" class="' . $this->_form->getAtomicCls("_frm-b{$formID}") . ' bf-form" ' . $file_upload_tag . ' method=\'post\'>
349 + <input type="text" class="d-none" name="csrf" value="' . $this->_tokens['csrf'] . '">
350 + <input type="text" class="d-none" name="t_identity" value="' . $this->_tokens['t_identity'] . '">
351 + ' . $this->honeypotField() . '
352 + <input type="text" class="d-none" name="bitforms_id" value="bitforms_' . $this->_form->getFormID() . '">
353 + ' . $this->languageField() . '
354 + ' . $fieldHtml . '
300 355 </form>
301 - $abandonmentMsg
302 - <div id='bf-form-msg-wrp-{$formIdentifier}'></div>
303 - $confMsg
356 + ' . $abandonmentMsg . '
357 + <div id=\'bf-form-msg-wrp-' . $formIdentifier . '\'></div>
358 + <!--
359 + Live Region for Screen Reader Announcements (Accessibility)
360 + -->
361 + <div
362 + class="bf-live-region"
363 + role="status"
364 + aria-live="polite"
365 + aria-atomic="true"
366 + style="position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;"
367 + ></div>
368 + ' . $confMsg . '
304 369 </div>
305 -HTMLa;
370 +';
306 371 return $formHTML;
307 372 }
308 373 }