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
bit-form / includes / Frontend / Form / View / FormViewer.php

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

369 lines 13.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\Translation\TranslationManager;
7 use BitCode\BitForm\Frontend\Form\FrontendFormManager;
8 use BitCode\BitForm\Frontend\Form\View\Conversational\ConversationalHelpers;
9 use BitCode\BitForm\Frontend\Form\View\Conversational\DefaultConversationalTheme;
10
11 class FormViewer
12 {
13 private $_theme = 'Default';
14 private $_themeDetails;
15 public $_fields;
16 private $_layout;
17 public $_nestedLayout;
18 private $_buttons;
19 public $_form;
20 public $_error;
21 private $_previousValue;
22 public $_formAtomicClsMap;
23 public $_tokens;
24 public $_formContents;
25
26 public function __construct(FrontendFormManager $formManager, $form_contents, $formAtomicClsMap, $errorMessages = null, $previousValue = null)
27 {
28 $this->_tokens = Helpers::csrfEecrypted();
29 $this->_fields = $form_contents->fields;
30 $this->_layout = $form_contents->layout;
31 $this->_nestedLayout = isset($form_contents->nestedLayout) ? $form_contents->nestedLayout : '';
32 $this->_buttons = isset($form_contents->buttons) ? $form_contents->buttons : '';
33 $this->_form = $formManager;
34 $this->_error = $errorMessages;
35 $this->_formContents = $form_contents;
36 $this->_previousValue = $previousValue;
37 $this->_formAtomicClsMap = $formAtomicClsMap;
38 }
39
40 public function getView($hasFile, $msg = null)
41 {
42 $name = str_replace(' ', '', $this->_theme);
43 $file = false !== strpos($name, 'Theme') ? $name . '.php' : $name . 'Theme.php';
44 if (file_exists(__DIR__ . '/' . $file)) {
45 $className = __NAMESPACE__ . '\\Theme\\' . $name . 'Theme';
46 $this->_themeDetails = new $className();
47 } else {
48 $className = __NAMESPACE__ . '\\Theme\\' . 'DefaultTheme';
49 $this->_themeDetails = new $className();
50 }
51 return $this->setView($hasFile, $msg);
52 }
53
54 public function honeypotField()
55 {
56 $time = time();
57 $honeypodFldName = Helpers::honeypotEncryptedToken("_bitforms_{$this->getFormID()}_{$time}_");
58 $honeypotInput = '';
59 if (Helpers::property_exists_nested($this->_formContents, 'additional->enabled->honeypot', true)) {
60 $honeypotInput =
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 ';
65 return $honeypotInput;
66 }
67 return $honeypotInput;
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
102 public function setTheme($theme = 'Default')
103 {
104 $this->_theme = $theme;
105 }
106
107 public function getError($field_name)
108 {
109 return isset($this->_error[$field_name]) ? $this->_error[$field_name] : null;
110 }
111
112 public function getFieldName($rowId)
113 {
114 $formIdentifier = $this->_form->getFormIdentifier();
115 $field_name = $rowId;
116 if ('button' === $this->_fields->{$rowId}->typ && 'submit' === $this->_fields->{$rowId}->btnTyp) {
117 $field_name = $formIdentifier;
118 }
119 $field_name .= isset($this->_fields->{$rowId}->lbl) ? preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\\!]/', '_', $this->_fields->{$rowId}->lbl) : '';
120 return $field_name;
121 }
122
123 public function getAtomicClaMap()
124 {
125 return $this->_formAtomicClsMap;
126 }
127
128 public function getValue($field_name, $rowId)
129 {
130 $valueToEsc = isset($this->_previousValue[$field_name]) ?
131 $this->_previousValue[$field_name] : (isset($this->_fields->{$rowId}->val) ? $this->_fields->{$rowId}->val : null);
132 if (empty($valueToEsc)) {
133 $value = null;
134 } else {
135 if ((isset($this->_fields->{$rowId}->mul) || 'check' === $this->_fields->{$rowId}->typ || 'select' === $this->_fields->{$rowId}->typ)) {
136 if ('select' === $this->_fields->{$rowId}->typ && is_string($valueToEsc)) {
137 $valueToEsc = explode(',', $valueToEsc);
138 }
139 if (is_array($valueToEsc)) {
140 $value = array_map('esc_attr', $valueToEsc);
141 } else {
142 $value = esc_attr($valueToEsc);
143 }
144 } else {
145 $value = !is_array($valueToEsc) ? esc_attr($valueToEsc) :
146 esc_attr($valueToEsc[count($valueToEsc) - 1]);
147 }
148 }
149 return $value;
150 }
151
152 public function getFormID()
153 {
154 return $this->_form->getFormID();
155 }
156
157 public function fields($rowID)
158 {
159 return $this->_fields->{$rowID} ?? null;
160 }
161
162 public function getNestedLayout($rowID)
163 {
164 // return null;
165 return isset($this->_nestedLayout->{$rowID}) ? $this->_nestedLayout->{$rowID} : null;
166 }
167
168 public function getFormInfo()
169 {
170 return $this->_formContents->formInfo;
171 }
172
173 private function getLayoutHtml($lay)
174 {
175 $html = '';
176 foreach ($lay->lg as $row) {
177 $html .= $this->_themeDetails->inputWrapper($this, $row->i);
178 }
179 return $html;
180 }
181
182 public function getConversationalView($hasFile, $msg = null)
183 {
184 $noTranslate = $this->noTranslateMarkup();
185 $conversationSettings = $this->getFormInfo()->conversationalSettings;
186 $this->_themeDetails = new DefaultConversationalTheme($conversationSettings);
187 $file_upload_tag = null;
188 $restrictionMsg = '';
189 $formID = $this->_form->getFormID();
190 if ($hasFile) {
191 $file_upload_tag = 'enctype="multipart/form-data"';
192 }
193 $formIdentifier = $this->_form->getFormIdentifier();
194 $fieldHtml = '';
195 $layouts = $this->_layout;
196 $isMultiStep = is_array($layouts) && count($layouts) > 1;
197 $layoutIsArray = !$isMultiStep && is_array($layouts);
198 if ($layoutIsArray) {
199 $layout = $layouts[0];
200 $layouts = isset($layout->layout) ? $layout->layout : $layout;
201 $this->_layout = $layouts;
202 }
203 $conversationalHelper = new ConversationalHelpers($this->getFormID(), $conversationSettings);
204 $welcomePageHtml = $conversationalHelper->getWelcomePageView();
205 if (!$isMultiStep) {
206 $tempLayout = $conversationalHelper->filterConversationalAllowedFields($layouts, $this->_fields);
207 $fieldHtml .= $this->getLayoutHtml($tempLayout);
208 } else {
209 foreach ($layouts as $lay) {
210 $tempLayout = $conversationalHelper->filterConversationalAllowedFields($lay->layout, $this->_fields);
211 $fieldHtml .= $this->getLayoutHtml($tempLayout);
212 }
213 }
214 $conversationNavigationHtml = $conversationalHelper->getNavigationView();
215
216 // $confMsg = $this->_form->getSuccessMessageMarkups();
217 $abandonmentMsg = $this->_form->getFormAbandonmentMessage();
218
219 // if (!empty($this->_buttons)) {
220 // $this->_buttons->name = $formIdentifier;
221 // $buttonClass = "button-{$formIdentifier}";
222 // $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
223 // }
224
225 if (!empty($msg)) {
226 $restrictionMsg =
227 '
228 <div class="' . $this->_form->getAtomicCls("_frm-ovrly-b{$formID}") . '">
229 <p class="' . $this->_form->getAtomicCls("_frm-ovrly-msg-b{$formID}") . '">
230 ' . $msg . '
231 </p>
232 </div>
233 ';
234 }
235 $formHTML =
236 '
237 <div id="' . $formIdentifier . '"' . $noTranslate['attrs'] . ' class="b' . $formID . '-bit-form ' . $this->_form->getAtomicCls("_frm-bg-b{$formID}") . ' bit-form bf-form-wrapper' . $noTranslate['cls'] . '">
238 ' . $restrictionMsg . '
239 <form novalidate id="form-' . $formIdentifier . '" class="_frm-bc' . $formID . ' bf-form" ' . $file_upload_tag . ' method=\'post\'>
240 <input type="text" class="d-none" name="csrf" value="' . $this->_tokens['csrf'] . '">
241 <input type="text" class="d-none" name="t_identity" value="' . $this->_tokens['t_identity'] . '">
242 ' . $this->honeypotField() . '
243 <input type="text" class="d-none" name="bitforms_id" value="bitforms_' . $this->_form->getFormID() . '">
244 ' . $this->languageField() . '
245 <div class="bc' . $formID . '-steps-container">
246 ' . $welcomePageHtml . '
247 ' . $fieldHtml . '
248 ' . $conversationNavigationHtml . '
249 </div>
250 </form>
251 <div id=\'bc-form-msg-wrp-' . $formIdentifier . '\' class="bc-form-msg-wrp"></div>
252 ' . $abandonmentMsg . '
253 </div>
254 ';
255
256 return $formHTML;
257 }
258
259 private function setView($hasFile, $msg)
260 {
261 $noTranslate = $this->noTranslateMarkup();
262 $file_upload_tag = null;
263 $restrictionMsg = '';
264 $formID = $this->_form->getFormID();
265 if ($hasFile) {
266 $file_upload_tag = 'enctype="multipart/form-data"';
267 }
268 $formIdentifier = $this->_form->getFormIdentifier();
269 $fieldHtml = '';
270 $layouts = $this->_layout;
271 $isMultiStep = is_array($layouts) && count($layouts) > 1;
272 $layoutIsArray = !$isMultiStep && is_array($layouts);
273 if ($layoutIsArray) {
274 $layout = $layouts[0];
275 $layouts = isset($layout->layout) ? $layout->layout : $layout;
276 $this->_layout = $layouts;
277 }
278 if (!$isMultiStep) {
279 $fieldHtml .= $this->getLayoutHtml($layouts);
280 } else {
281 $formViewHelper = new FormViewHelper($this->_form, $this->_formContents);
282
283 $fieldHtml .=
284 '
285 <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnr") . ' _frm-b-stp-cntnr\'>
286 ' . $formViewHelper->getStepHeaderHtml() . '
287 <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-wrpr") . ' _frm-b-stp-wrpr\'>
288 ' . $formViewHelper->getProgressBarMarkup() . '
289 <div class=\'' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt-wrpr") . ' _frm-b-stp-cntnt-wrpr\'>
290 ';
291 foreach ($layouts as $key => $lay) {
292 $hideOtherSteps = $key > 0 ? 'deactive' : '';
293 $step = $key + 1;
294 $ariaHidden = $key > 0 ? 'aria-hidden="true"' : '';
295 $fieldHtml .=
296 '
297 <div
298 class="' . $this->_form->getAtomicCls("_frm-b{$formID}-stp-cntnt") . ' _frm-b-stp-cntnt ' . $hideOtherSteps . '"
299 data-step="' . $step . '"
300 role="tabpanel"
301 id="step-' . $formID . '-' . $step . '-panel"
302 aria-labelledby="step-' . $formID . '-' . $step . '-tab"
303 ' . $ariaHidden . '
304 >
305 <div class="_frm-b' . $formID . ' _frm-b">
306 ' . $this->getLayoutHtml($lay->layout) . '
307 </div>
308 ' . $formViewHelper->getStepButtonMarkup() . '
309 </div>
310 ';
311 }
312 $fieldHtml .=
313 '
314 </div>
315 </div>
316 </div>
317 ';
318 }
319
320 $confMsg = $this->_form->getSuccessMessageMarkups();
321 $abandonmentMsg = $this->_form->getFormAbandonmentMessage();
322
323 if (!empty($this->_buttons)) {
324 $this->_buttons->name = $formIdentifier;
325 $buttonClass = "button-{$formIdentifier}";
326 $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
327 }
328
329 if (!empty($msg)) {
330 $restrictionMsg =
331 '
332 <div class="' . $this->_form->getAtomicCls("_frm-ovrly-b{$formID}") . '">
333 <p class="' . $this->_form->getAtomicCls("_frm-ovrly-msg-b{$formID}") . '">
334 ' . $msg . '
335 </p>
336 </div>
337 ';
338 }
339 $formHTML =
340 '
341 <div id="' . $formIdentifier . '"' . $noTranslate['attrs'] . ' class="b' . $formID . '-bit-form ' . $this->_form->getAtomicCls("_frm-bg-b{$formID}") . ' bit-form bf-form-wrapper' . $noTranslate['cls'] . '">
342 ' . $restrictionMsg . '
343 <form novalidate id="form-' . $formIdentifier . '" class="' . $this->_form->getAtomicCls("_frm-b{$formID}") . ' bf-form" ' . $file_upload_tag . ' method=\'post\'>
344 <input type="text" class="d-none" name="csrf" value="' . $this->_tokens['csrf'] . '">
345 <input type="text" class="d-none" name="t_identity" value="' . $this->_tokens['t_identity'] . '">
346 ' . $this->honeypotField() . '
347 <input type="text" class="d-none" name="bitforms_id" value="bitforms_' . $this->_form->getFormID() . '">
348 ' . $this->languageField() . '
349 ' . $fieldHtml . '
350 </form>
351 ' . $abandonmentMsg . '
352 <div id=\'bf-form-msg-wrp-' . $formIdentifier . '\'></div>
353 <!--
354 Live Region for Screen Reader Announcements (Accessibility)
355 -->
356 <div
357 class="bf-live-region"
358 role="status"
359 aria-live="polite"
360 aria-atomic="true"
361 style="position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;"
362 ></div>
363 ' . $confMsg . '
364 </div>
365 ';
366 return $formHTML;
367 }
368 }
369