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/FormViewer.php +346 -88 1.1.83.3.1 View file →
@@ -1,110 +1,368 @@
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\Translation\TranslationManager;
5 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;
6 10
7 11 class FormViewer
8 12 {
9 - private $_theme = 'Default';
10 - private $_themeDetails;
11 - private $_fields;
12 - private $_layout;
13 - private $_buttons;
14 - private $_form;
15 - private $_error;
16 - private $_previousValue;
17 - public function __construct(FrontendFormManager $formManager, $form_contents, $errorMessages = null, $previousValue = null)
18 - {
19 - $this->_fields = $form_contents->fields;
20 - $this->_layout = $form_contents->layout;
21 - $this->_buttons = $form_contents->buttons;
22 - $this->_form = $formManager;
23 - $this->_error = $errorMessages;
24 - $this->_previousValue = $previousValue;
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();
25 50 }
26 - public function getView($hasFile)
27 - {
28 - $name = str_replace(' ', null, $this->_theme);
29 - $file = strpos($name, 'Theme') !== false ? $name . '.php' : $name . 'Theme.php';
30 - if (file_exists(__DIR__ . '/' . $file)) {
31 - $className = __NAMESPACE__ . '\\Theme\\' . $name . 'Theme';
32 - $this->_themeDetails = new $className;
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);
33 141 } else {
34 - $className = __NAMESPACE__ . '\\Theme\\' . 'DefaultTheme';
35 - $this->_themeDetails = new $className;
142 + $value = esc_attr($valueToEsc);
36 143 }
37 - return $this->setView($hasFile);
144 + } else {
145 + $value = !is_array($valueToEsc) ? esc_attr($valueToEsc) :
146 + esc_attr($valueToEsc[count($valueToEsc) - 1]);
147 + }
38 148 }
149 + return $value;
150 + }
39 151
40 - public function setTheme($theme = 'Default')
41 - {
42 - $this->_theme = $theme;
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);
43 178 }
179 + return $html;
180 + }
44 181
45 - private function setView($hasFile)
46 - {
47 - $file_upload_tag = null;
48 - $formID = $this->_form->getFormID();
49 - if ($hasFile) {
50 - $file_upload_tag = 'enctype="multipart/form-data"';
51 - wp_enqueue_script('bitforms-frontend-file');
52 - }
53 - $formIdentifier = $this->_form->getFormIdentifier();
54 - $fieldHtml = "";
55 - foreach ($this->_layout->lg as $key => $row) {
56 - $field_name = $row->i;
57 - // $field_name .= isset($this->_fields->{$row->i}->lbl) ? preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\\!]/', '_', $this->_fields->{$row->i}->lbl) : "";
58 - $error = isset($this->_error[$field_name]) ? $this->_error[$field_name] : null;
59 - $valueToEsc = isset($this->_previousValue[$field_name]) ?
60 - $this->_previousValue[$field_name] : (isset($this->_fields->{$row->i}->val) ? $this->_fields->{$row->i}->val : null);
61 - if (empty($valueToEsc)) {
62 - $value = null;
63 - } else {
64 - if ((isset($this->_fields->{$row->i}->mul) || $this->_fields->{$row->i}->typ === 'check' || $this->_fields->{$row->i}->typ === 'select')) {
65 - if ($this->_fields->{$row->i}->typ === 'select' && is_string($valueToEsc)) {
66 - $valueToEsc = explode(',', $valueToEsc);
67 - }
68 - if (is_array($valueToEsc)) {
69 - $value = array_map('esc_attr', $valueToEsc);
70 - } else {
71 - $value = esc_attr($valueToEsc);
72 - }
73 - } else {
74 - $value = !is_array($valueToEsc) ? esc_attr($valueToEsc) :
75 - esc_attr($valueToEsc[count($valueToEsc) - 1]);
76 - }
77 - }
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();
78 215
79 - $fieldHtml .= $this->_themeDetails->getField(
80 - $this->_fields->{$row->i},
81 - $row->i,
82 - $field_name,
83 - $error,
84 - $value,
85 - $formID
86 - );
87 - }
216 + // $confMsg = $this->_form->getSuccessMessageMarkups();
217 + $abandonmentMsg = $this->_form->getFormAbandonmentMessage();
88 218
89 - $this->_buttons->name = $formIdentifier;
90 - $buttonClass = "button-{$formIdentifier}";
91 - $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
219 + // if (!empty($this->_buttons)) {
220 + // $this->_buttons->name = $formIdentifier;
221 + // $buttonClass = "button-{$formIdentifier}";
222 + // $subBtn = $this->_themeDetails->getField($this->_buttons, $buttonClass, '');
223 + // }
92 224
93 - $formHTML =
94 - <<<FORM
95 -<div id="f-{$formID}">
96 - <form id="form-{$formIdentifier}" class="_frm-bg-{$formID}" action="" $file_upload_tag method='post'>
97 - <input type="hidden" name="bitforms_token" value="{$this->_form->getFormToken()}">
98 - <input type="hidden" name="bitforms_id" value="bitforms_{$this->_form->getFormID()}">
99 - <div class="_frm-{$formID}">
100 - <div class="_frm-g _frm-g-{$formID}">
101 - $fieldHtml
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() . '
102 309 </div>
310 +';
311 + }
312 + $fieldHtml .=
313 + '
314 + </div>
103 315 </div>
104 - $subBtn
105 - </form>
106 -</div>
107 -FORM;
108 - return $formHTML;
316 + </div>
317 +';
109 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 + }
110 368 }