PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V3.0.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV3.0.3
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 / FrontendFormHandler.php

FrontendFormHandler.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder V3.0.3, at includes/Frontend/Form/FrontendFormHandler.php

808 lines 32.1 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;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 use BitCode\BitForm\Admin\Form\AdminFormHandler;
10 use BitCode\BitForm\Admin\Form\FrontEndScriptGenerator;
11 use BitCode\BitForm\Admin\Form\Helpers;
12 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
13 use BitCode\BitForm\Core\Database\FormModel;
14 use BitCode\BitForm\Core\Form\FormManager;
15 use BitCode\BitForm\Core\Integration\IntegrationHandler;
16 use BitCode\BitForm\Core\Util\EscapingHelper;
17 use BitCode\BitForm\Core\Util\FieldValueHandler;
18 use BitCode\BitForm\Core\Util\FileDownloadProvider;
19 use BitCode\BitForm\Core\Util\FrontendHelpers;
20 use BitCode\BitForm\Core\Util\SmartTags;
21 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
22
23 final class FrontendFormHandler
24 {
25 public function __construct()
26 {
27 // before markup load - formids [], posts [1,2]
28 add_action('wp_enqueue_scripts', [$this, 'loadAssets']);
29 // markup loads - formids []
30 add_shortcode('bitform', [$this, 'handleFrontendRenderRequest']);
31 // after markup load - formids [1,35,3]
32 add_action('wp_footer', [$this, 'generateJS']);
33 }
34
35 private function validPassowordResetToken($token, $userID, $formId)
36 {
37 $existResetInteg = (new IntegrationHandler($formId))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
38 if (!is_wp_error($existResetInteg) && count($existResetInteg) > 0) {
39 if ('reset' === $existResetInteg[0]->integration_name) {
40 $user = get_userdata($userID);
41 if ($user) {
42 $validKey = check_password_reset_key($token, $user->user_login);
43 if (is_wp_error($validKey)) {
44 echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>This password reset token is invalid.</div>";
45 exit();
46 }
47 } else {
48 echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>Invalid User!!</div>";
49 exit();
50 }
51 }
52 }
53 }
54
55 private function getJSFileSrc($postId)
56 {
57 $formUpdateVersion = get_option('bitform_form_update_version');
58 $formScriptSrc = BITFORMS_UPLOAD_BASE_URL . "/form-scripts/$postId/bitform-js-$postId.js?bfv=$formUpdateVersion";
59
60 return $formScriptSrc;
61 }
62
63 public function generateJs($formID = null, $entryID = null, $formType = null)
64 {
65 // return true;
66 $isFormPreview = get_transient('bitform_form_preview');
67 if ($isFormPreview && !$formID) {
68 delete_transient('bitform_form_preview');
69 return;
70 }
71 $frontendScriptGenObj = new FrontEndScriptGenerator();
72 $isPageBuilder = FrontendHelpers::checkIsPageBuilder($_SERVER);
73 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
74 if ($isPageBuilder || empty($bfFrontendFormIds)) {
75 return;
76 }
77 // for unique fields ids in the same form (e.g. multiple forms in the same page)
78 $allFields = [];
79 $formContents = [];
80 $contentIds = [];
81 $formIDs = [];
82 $previewMode = 'classic';
83 $postId = '';
84
85 $formUpdateVersion = get_option('bitform_form_update_version');
86 if ($formID) {
87 $formIDs[] = $formID;
88 $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
89 $formInfo = $FrontendFormManager->getFormInfo();
90 $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
91 $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
92 $formContent->formId = $formID;
93 $formContents[] = $formContent;
94 $workFlowRunType = $entryID ? 'edit' : 'create';
95 $fields = $formContent->fields;
96 if ($entryID) {
97 $fields = $this->setFieldsValue($fields, $formID, $entryID);
98 }
99 $fields = $this->triggerWorkflowOnLoad($formID, 1, $fields, $workFlowRunType);
100 array_push($contentIds, $FormIdentifier);
101
102 foreach ($fields as $fk => $field) {
103 $allFields[$field->typ][] = ['fk' => $fk, 'field' => $field, 'formID' => $formID, 'contentId' => $FormIdentifier];
104 }
105 //Generate JS file for conversational form
106 if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
107 $frontendScriptGenObj->generateJsFile([$formContent], $allFields, [$FormIdentifier], $formID, [$formID], 'conversational');
108 }
109 $previewMode = 'preview';
110 $postId = $formID;
111 } else {
112 global $post;
113 if (!is_object($post) && !isset($post->ID)) {
114 return;
115 }
116 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
117 $bfUniqFormIds = FrontendHelpers::getAllUniqFormIdsInPage();
118 $formIDs = $bfUniqFormIds;
119 $regenerateScriptFlag = $this->regenerateScriptChecker($bfUniqFormIds);
120
121 $postId = $post->ID;
122 if (!$regenerateScriptFlag) {
123 $regenerateScriptFlag = $this->deleteUnusedFormPageIds($postId, $bfUniqFormIds);
124 }
125 $isJsGenerating = get_option('bitforms_frontend_js_generating');
126 if (!$regenerateScriptFlag && !$isJsGenerating && !empty($formIDs)) {
127 wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
128 return;
129 }
130 foreach ($bfFrontendFormIds as $index => $formId) {
131 $shortCodeCounter = $index + 1;
132 $FrontendFormManager = FrontendFormManager::getInstance($formId, $shortCodeCounter);
133 $formInfo = $FrontendFormManager->getFormInfo();
134 $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
135 $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
136 $formContent->formId = $formId;
137 $formContents[] = $formContent;
138 $fields = $this->triggerWorkflowOnLoad($formId, $shortCodeCounter, $formContent->fields);
139 $contentIds[] = $FormIdentifier;
140 $formFields = []; // indivisual form fields array for conversational view
141 foreach ($fields as $fk => $field) {
142 $fieldArr = ['fk' => $fk, 'field' => $field, 'formID' => $formId, 'contentId' => $FormIdentifier];
143 $allFields[$field->typ][] = $fieldArr;
144 $formFields[$field->typ][] = $fieldArr;
145 }
146 //Generate JS file for conversational form
147 if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
148 $frontendScriptGenObj->generateJsFile([$formContent], $formFields, [$FormIdentifier], $formId, [$formId], 'conversational');
149 }
150 }
151 }
152 if (empty($formIDs)) {
153 return;
154 }
155
156 $frontendScriptGenObj->generateJsFile($formContents, $allFields, $contentIds, $postId, $formIDs, $previewMode);
157 if ('preview' === $previewMode) {
158 return;
159 }
160 wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
161 }
162
163 private function deleteUnusedFormPageIds($postId, $formIDs)
164 {
165 global $post;
166 if (!is_object($post) && !isset($post->ID)) {
167 return;
168 }
169 $postId = $post->ID;
170 $formModel = new FormModel();
171 $forms = $formModel->get(
172 ['id', 'generated_script_page_ids']
173 );
174 $regenerateScriptFlag = false;
175 foreach ($forms as $form) {
176 $formId = $form->id;
177 $generatedScriptPageIdsDecoded = json_decode($form->generated_script_page_ids, true);
178 $generatedScriptPageIds = is_array($generatedScriptPageIdsDecoded) ? array_keys($generatedScriptPageIdsDecoded) : [];
179 if (!empty($generatedScriptPageIds) && !in_array($formId, $formIDs) && in_array($postId, $generatedScriptPageIds)) {
180 unset($generatedScriptPageIdsDecoded[$postId]);
181 if (empty($generatedScriptPageIdsDecoded)) {
182 $generatedScriptPageIdsDecoded = new \stdClass();
183 }
184 $regenerateScriptFlag = true;
185 $formModel->update(['generated_script_page_ids' => wp_json_encode($generatedScriptPageIdsDecoded)], ['id' => $formId]);
186 }
187 }
188 if ($regenerateScriptFlag) {
189 $formUpdateVersion = get_option('bitform_form_update_version');
190 if (!$formUpdateVersion) {
191 $formUpdateVersion = 1;
192 } else {
193 $formUpdateVersion = (int) $formUpdateVersion + 1;
194 }
195 update_option('bitform_form_update_version', $formUpdateVersion);
196 }
197 return $regenerateScriptFlag;
198 }
199
200 private function regenerateScriptChecker($formsIds)
201 {
202 global $post;
203 if (!is_a($post, 'WP_Post') && !isset($post->ID)) {
204 return;
205 }
206 $postId = $post->ID;
207 $regenerateScriptFlag = false;
208 $formModel = new FormModel();
209 foreach ($formsIds as $formId) {
210 $formInstance = FormManager::getInstance($formId);
211 if (!$formInstance->isExist()) {
212 continue;
213 }
214 $generatedPages = $formInstance->getFormData('generated_script_page_ids');
215 if (empty($generatedPages)) {
216 $regenerateScriptFlag = true;
217 } elseif (is_object($generatedPages) && (!isset($generatedPages->{$postId}) || (isset($generatedPages->{$postId}) && false === $generatedPages->{$postId}))) {
218 $regenerateScriptFlag = true;
219 }
220 if (!$regenerateScriptFlag) {
221 continue;
222 }
223 if (!is_object($generatedPages)) {
224 $generatedPages = (object) [];
225 }
226 $generatedPages->{$postId} = true;
227 $formModel->update(
228 [
229 'generated_script_page_ids' => \wp_json_encode($generatedPages)
230 ],
231 [
232 'id' => $formId,
233 ]
234 );
235 }
236 return $regenerateScriptFlag;
237 }
238
239 private function addInlineScript($code, $handle = '', $position = 'after')
240 {
241 $scriptHandle = !empty($handle) ? $handle : 'bf-inline-script';
242 $formUpdateVersion = get_option('bitform_form_update_version');
243 if (!wp_script_is($scriptHandle)) {
244 wp_register_script($scriptHandle, '', [], $formUpdateVersion, true);
245 wp_enqueue_script($scriptHandle);
246 }
247 wp_add_inline_script($scriptHandle, $code, $position);
248 }
249
250 private function addInlineStyle($code, $handle = '')
251 {
252 $styleHandle = !empty($handle) ? $handle : 'bf-inline-style';
253 $formUpdateVersion = get_option('bitform_form_update_version');
254 if (!wp_style_is($styleHandle)) {
255 wp_register_style($styleHandle, '', [], $formUpdateVersion);
256 wp_enqueue_style($styleHandle);
257 }
258 wp_add_inline_style($styleHandle, $code);
259 }
260
261 private function triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType = 'create')
262 {
263 $FrontendFormManager = new FrontendFormManager($formID, $shortCodeCounter);
264 $previousValue = $this->getValuesFromQueryParams();
265 $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
266 if (!empty($formContent->workFlowExist)) {
267 $workFlowRunHelper = new WorkFlow($formID);
268 if (!empty($formContent->workFlowExist->onload)) {
269 $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
270 $workFlowRunType,
271 $fields
272 );
273
274 if (!empty($workFlowreturnedOnLoad['fields'])) {
275 return $workFlowreturnedOnLoad['fields'];
276 }
277 }
278 }
279
280 return $fields;
281 }
282
283 private function executeOnUserInput($formID, $shortCodeCounter, $fields)
284 {
285 $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
286 $previousValue = $this->getValuesFromQueryParams();
287 $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
288 $customCodesExist = strpos(FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'], 'bfVars');
289 if ($customCodesExist || (!empty($formContent->workFlowExist) && !empty($formContent->workFlowExist->oninput))) {
290 $workFlowRunHelper = new WorkFlow($formID);
291 return $workFlowRunHelper->executeOnUserInput('create', $fields);
292 }
293 }
294
295 private function getValuesFromQueryParams()
296 {
297 // Read-only: query string parsed to pre-fill form fields. Values are sanitized per field before use.
298 $queryParamsValue = [];
299 if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
300 $reqField = sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING']));
301 foreach (explode('&', $reqField) as $keyValue) {
302 // $pattern = '/([a-zA-Z0-9])([a-zA-Z])\=+/';
303 $pattern = '/([^.]+)=(.*?)([^.]+)/';
304 $matches = preg_match($pattern, $keyValue, $matchFormat);
305 if ($matches) {
306 list($field, $value) = explode('=', $keyValue, 2);
307
308 if (!trim($value)) {
309 continue;
310 }
311
312 $queryParamsValue[$field][] = sanitize_text_field(urldecode($value));
313 }
314 }
315 }
316
317 return $queryParamsValue;
318 }
319
320 public function handleFrontendRenderRequest($atts)
321 {
322 $formType = isset($atts['type']) ? $atts['type'] : 'classic';
323 $formPreview = isset($atts['form_preview']) ? $atts['form_preview'] : false;
324 if (isset($atts['form_id'])) {
325 $formID = intval($atts['form_id']);
326 }
327 if (isset($atts['entry_id'])) {
328 $entryId = intval($atts['entry_id']);
329 // Read-only: entry ID from query string for shortcode render. No state mutation.
330 } elseif (isset($_GET['bf_entry_id']) && !is_array($_GET['bf_entry_id'])) {
331 $entryId = intval(sanitize_text_field(wp_unslash($_GET['bf_entry_id'])));
332 } else {
333 $entryId = false;
334 }
335 if (isset($atts['id'])) {
336 $atts = shortcode_atts(['id' => 0], $atts);
337 $formID = intval($atts['id']);
338 }
339
340 if (!$formID) {
341 return __('Form ID cannot be empty', 'bit-form');
342 }
343
344 if (!$this->isExist($formID)) {
345 /* translators: %s: form ID */
346 return sprintf(__('#%s no. Form doesn\'t exists', 'bit-form'), $formID);
347 }
348
349 // Add-ons may detect whether the current visitor is resuming an abandoned entry.
350 $isAbandoned = (bool) apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts);
351
352 FrontendHelpers::setBfFrontendFormIds($formID);
353 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
354 $shortCodeCounter = count($bfFrontendFormIds);
355 $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
356
357 if (!$FrontendFormManager->checkStatus()) {
358 /* translators: %s: form ID */
359 return sprintf(__('#%s no. Form is not active', 'bit-form'), $formID);
360 }
361 ob_start();
362 $this->loadAssets($formID, $formType);
363
364 $font = $FrontendFormManager->getFont();
365
366 if ($font && !$formPreview) {
367 wp_enqueue_style('bf-google-font', $font, '1.0.0', true);
368 }
369
370 // Read-only: password reset token from URL for display-time validation. No state written until form is submitted.
371 if (!empty($_GET['token']) && !empty($_GET['id'])) {
372 $this->validPassowordResetToken(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID);
373 }
374
375 $previousValue = $this->getValuesFromQueryParams();
376 $errorMessages = []; // delete
377 $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
378 $nonce = $FrontendFormManager->getFormToken();
379 $file = count($FrontendFormManager->getUploadFields()) > 0 ? $FrontendFormManager->getUploadFields() : false;
380
381 $FrontendFormManager->setViewCount();
382
383 $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
384 $fields = $formContent->fields;
385 $layout = $formContent->layout;
386 $nestedLayout = isset($formContent->nestedLayout) ? $formContent->nestedLayout : (object) [];
387 $buttons = !empty($formContent->buttons) ? $formContent->buttons : '';
388 $additional = $formContent->additional;
389
390 // $workFlowRunType = $entryId ? 'edit' : 'create';
391 if ($entryId && (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess'))) {
392 $workFlowRunType = 'edit';
393 $adminFormHandler = new AdminFormHandler();
394 $getEntry = $adminFormHandler->getSingleEntry($formID, $entryId);
395 if (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess', '', $getEntry->__user_id)) {
396 $fields = $this->setFieldsValue($fields, $formID, $entryId);
397 } elseif (!$isAbandoned) {
398 $entryId = false;
399 $workFlowRunType = 'create';
400 }
401 } else {
402 $entryId = false;
403 $workFlowRunType = 'create';
404 }
405
406 // if ($entryId) {
407 // $fields = $this->setFieldsValue($fields, $formID, $entryId);
408 // }
409
410 $fields = apply_filters('bitform_filter_before_workflow_onload_fields', $fields, $formID);
411 $fields = $this->triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType);
412 $fields = apply_filters('bitform_filter_after_workflow_onload_fields', $fields, $formID);
413 do_action('bitform_onload_fields', $fields, $formID);
414 $workFlowreturnedOnUserInput = $this->executeOnUserInput($formID, $shortCodeCounter, $fields);
415
416 // test for form before remove
417 $noLabelFieldTypes = ['decision-box', 'gdpr', 'html', 'shortcode', 'button', 'paypal', 'razorpay', 'recaptcha', 'turnstile', 'hcaptcha', 'stripe', 'spacer'];
418 foreach ($fields as $fldKey => $field) {
419 if (!in_array($field->typ, $noLabelFieldTypes) && isset($field->lbl)) {
420 $lblReplaceToBackslash = str_replace('$_bf_$', '\\', $field->lbl);
421 $fields->{$fldKey}->lbl = FieldValueHandler::replaceSmartTagWithValue($lblReplaceToBackslash);
422 }
423 }
424 $fieldsKey = $FrontendFormManager->getFieldsKey();
425
426 $captchaV3Settings = $FrontendFormManager->getCaptchaV3Settings();
427 if ($FrontendFormManager->getCaptchaSettings() || $captchaV3Settings || $FrontendFormManager->getTurnstileSettings() || $FrontendFormManager->isFieldTypeExist('hcaptcha')) {
428 $integrationHandler = new IntegrationHandler(0);
429 $allFormIntegrations = $integrationHandler->getAllIntegration('app');
430 if (!is_wp_error($allFormIntegrations)) {
431 foreach ($allFormIntegrations as $integration) {
432 if (
433 $FrontendFormManager->getCaptchaSettings()
434 && !is_null($integration->integration_type)
435 && 'gReCaptcha' === $integration->integration_type
436 ) {
437 $integrationDetails = json_decode($integration->integration_details);
438 $integrationDetails->id = $integration->id;
439 $reCAPTCHA = $integrationDetails;
440 $reCAPTCHAVersion = 'v2';
441 }
442
443 if (
444 $FrontendFormManager->getTurnstileSettings()
445 && !is_null($integration->integration_type)
446 && 'turnstileCaptcha' === $integration->integration_type
447 ) {
448 $integrationDetails = json_decode($integration->integration_details);
449 $turnstileSiteKey = $integrationDetails->siteKey;
450 }
451
452 if (
453 $FrontendFormManager->isFieldTypeExist('hcaptcha')
454 && !is_null($integration->integration_type)
455 && 'hcaptcha' === $integration->integration_type
456 ) {
457 $integrationDetails = json_decode($integration->integration_details);
458 $hCaptchaSiteKey = $integrationDetails->siteKey;
459 }
460
461 if ($captchaV3Settings) {
462 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
463 $integrationDetails = json_decode($integration->integration_details);
464 $integrationDetails->id = $integration->id;
465 $reCAPTCHA = $integrationDetails;
466 $reCAPTCHAVersion = 'v3';
467 }
468 }
469 }
470 }
471 }
472
473 if ($captchaV3Settings && !empty($reCAPTCHA->siteKey)) {
474 // DANGER: no matter what, DONT CHANGE THE SCRIPT ID OF THIS SCRIPT
475 $scriptId = BITFORMS_PREFIX . 'recaptcha';
476 // External Google reCAPTCHA script; version managed by URL query param. Loaded in header because
477 // standalone form views do not render wp_footer(), making footer enqueue unreliable.
478 wp_enqueue_script($scriptId, "https://www.google.com/recaptcha/api.js?render={$reCAPTCHA->siteKey}", [], null, false);
479 }
480
481 $configs = [
482 'bf_separator' => BITFORMS_BF_SEPARATOR,
483 ];
484
485 // check if fields has paypal or razorpay
486 $paymentFields = ['paypal', 'razorpay', 'stripe'];
487 $paymentFieldData = [];
488 foreach ($fields as $key => $field) {
489 if (in_array($field->typ, $paymentFields)) {
490 $paymentFieldData[$key] = $field;
491 }
492 }
493
494 if (!empty($paymentFieldData)) {
495 $integrationHandler = new IntegrationHandler(0);
496 foreach ($paymentFieldData as $fldKey => $fldData) {
497 $paymentIntegration = $integrationHandler->getAIntegration($fldData->payIntegID);
498 if (is_wp_error($paymentIntegration)) {
499 continue;
500 }
501 if ('paypal' === $fldData->typ) {
502 $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
503 $clientID = $integrationDetails->clientID;
504 $fields->{$fldKey}->clientId = $clientID;
505 } elseif ('razorpay' === $fldData->typ) {
506 $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
507 $clientID = $integrationDetails->apiKey;
508 $fields->{$fldKey}->clientId = $clientID;
509 } elseif ('stripe' === $fldData->typ) {
510 $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
511 $publishableKey = $integrationDetails->publishableKey;
512 $fields->{$fldKey}->publishableKey = $publishableKey;
513 }
514 }
515 }
516
517 $bitFormFrontArr = [
518 'ajaxURL' => admin_url('admin-ajax.php'),
519 'nonce' => $nonce,
520 'version' => BITFORMS_VERSION,
521 'layout' => $layout,
522 'nestedLayout' => $nestedLayout,
523 'fields' => $fields,
524 'buttons' => $buttons,
525 'fieldsKey' => $fieldsKey,
526 'file' => $file,
527 'configs' => $configs,
528 'formId' => $formID,
529 'appID' => "bitforms_{$formID}",
530 'GCLID' => $FrontendFormManager->isGCLIDEnabled(),
531 'assetUrl' => BITFORMS_ASSET_URI,
532 'onfieldCondition' => !empty($workFlowreturnedOnUserInput['onfield_input_conditions']) ? $workFlowreturnedOnUserInput['onfield_input_conditions'] : false,
533 'smartTags' => SmartTags::smartTags(SmartTags::getPostUserData()),
534 'paymentCallbackUrl' => get_rest_url() . 'bitform/v1/payments/razorpay',
535 'gRecaptchaSiteKey' => !empty($reCAPTCHA->siteKey) ? $reCAPTCHA->siteKey : null,
536 'gRecaptchaVersion' => !empty($reCAPTCHAVersion) ? $reCAPTCHAVersion : null,
537 'turnstileSiteKey' => !empty($turnstileSiteKey) ? $turnstileSiteKey : null,
538 'hCaptchaSiteKey' => !empty($hCaptchaSiteKey) ? $hCaptchaSiteKey : null,
539 ];
540
541 if ($entryId) {
542 $bitFormFrontArr['entryId'] = $entryId;
543 }
544
545 if (isset($additional->enabled->validateFocusLost)) {
546 $bitFormFrontArr['validateFocusLost'] = true;
547 }
548
549 if (!empty($isAbandoned)) {
550 $bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $isAbandoned);
551 if (empty($entryId)) {
552 $bitFormFrontArr['entryId'] = $isAbandoned;
553 }
554 }
555
556 $formInfo = $FrontendFormManager->getFormInfo();
557 $bitFormFrontArr['formName'] = $formInfo->formName ?? '';
558 if (is_array($layout) && count($layout) > 1) {
559 $multiStepSettings = isset($formInfo->multiStepSettings) ? $formInfo->multiStepSettings : null;
560 $newTempSettings = (object) [
561 'validateOnStepChange' => isset($multiStepSettings->validateOnStepChange) ? $multiStepSettings->validateOnStepChange : false,
562 'maintainStepHistory' => isset($multiStepSettings->maintainStepHistory) ? $multiStepSettings->maintainStepHistory : false,
563 'saveProgress' => isset($multiStepSettings->saveProgress) ? $multiStepSettings->saveProgress : false,
564 'showPercentage' => isset($multiStepSettings->progressSettings->showPercentage) ? $multiStepSettings->progressSettings->showPercentage : false,
565 ];
566 $bitFormFrontArr['formInfo'] = (object) [
567 'multiStepSettings' => $newTempSettings
568 ];
569 }
570
571 if (Helpers::property_exists_nested($formInfo, 'conversationalSettings->enable', true)) {
572 if (!isset($bitFormFrontArr['formInfo'])) {
573 $bitFormFrontArr['formInfo'] = new \stdClass();
574 }
575 $bitFormFrontArr['formInfo']->conversationalSettings = $formInfo->conversationalSettings;
576 }
577
578 $formAbandonmentSettings = $FrontendFormManager->getFormAbandonmentSettings();
579 if (Helpers::property_exists_nested($formAbandonmentSettings, 'active', true)) {
580 $bitFormFrontArr['formSettings'] = (object)[
581 'formAbandonment' => $formAbandonmentSettings
582 ];
583 }
584
585 $layout = wp_json_encode($layout);
586 $buttons = wp_json_encode($buttons);
587 $frontArr = wp_json_encode($bitFormFrontArr);
588
589 $bfGlobals = sprintf('
590 if(!window.bf_globals) {
591 window.bf_globals = {}
592 } if(!window.bf_globals.%1$s) {
593 window.bf_globals.%1$s = {}
594 }
595 if(document.getElementById("%1$s")) {
596 window.bf_globals.%1$s = {
597 ...window.bf_globals.%1$s,
598 ...%2$s
599 };
600 }', $FormIdentifier, $frontArr);
601
602 if ('conversational' === $formType
603 && isset($formContent->formInfo->conversationalSettings->enable)
604 && $formContent->formInfo->conversationalSettings->enable) {
605 $html = $FrontendFormManager->conversationalFormView($fields, $file, $errorMessages);
606 } else {
607 $html = $FrontendFormManager->formView($fields, $file, $errorMessages);
608 }
609
610 // if form preview then return html otherwise echo with output buffer
611 if ($formPreview) {
612 ob_clean();
613 $formViewObject = new \stdClass();
614 $formViewObject->html = $html;
615 $formViewObject->font = $font;
616 $formViewObject->bfGlobals = $bfGlobals;
617 $formViewObject->formContent = $formContent;
618 return $formViewObject;
619 }
620
621 $bfGlobalsHandle = 'bitform-bf-globals-' . sanitize_key($FormIdentifier);
622 $this->addInlineScript($bfGlobals, $bfGlobalsHandle, 'after');
623 $this->emitShowPickerBridge();
624
625 echo wp_kses(trim($html), EscapingHelper::getFormAllowedHtml($formContent));
626 return ob_get_clean();
627 }
628
629 /**
630 * Delegated listener that opens the native picker on date/time inputs marked
631 * with data-bf-show-picker. Replaces the legacy hardcoded onclick attribute.
632 * Registered as inline script once per request via wp_add_inline_script so
633 * the markup never travels through wp_kses().
634 */
635 private function emitShowPickerBridge()
636 {
637 static $emitted = false;
638 if ($emitted) {
639 return;
640 }
641 $emitted = true;
642 $code = 'if(!window.__bfShowPickerBound){window.__bfShowPickerBound=true;document.addEventListener("click",function(e){var t=e.target;if(t&&t.matches&&t.matches("input[data-bf-show-picker=\"1\"]")&&typeof t.showPicker==="function"){try{t.showPicker();}catch(_){}}});}';
643 $this->addInlineScript($code, 'bitform-show-picker-bridge', 'after');
644 }
645
646 private function isExist($formID)
647 {
648 $formModel = new FormModel();
649 $form = $formModel->get(
650 [
651 'id'
652 ],
653 [
654 'id' => $formID,
655 ]
656 );
657 if (!is_wp_error($form)) {
658 return true;
659 }
660 return false;
661 }
662
663 private function getFieldsValue($formID, $entryID)
664 {
665 $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
666 $formEntryModel = new FormEntryMetaModel();
667 $metaValues = $formEntryModel->get(
668 [
669 'meta_key',
670 'meta_value'
671 ],
672 [
673 'bitforms_form_entry_id' => $entryID,
674 ]
675 );
676 $formFields = $FrontendFormManager->getFields();
677 $fldsData = (object) [];
678 if (!is_wp_error($metaValues)) {
679 foreach ($metaValues as $metaValue) {
680 $metaKey = $metaValue->meta_key;
681 $metaVal = $metaValue->meta_value;
682 // if meta value is array then convert to string
683 if (preg_match('/^\[.*\]$/', $metaVal)) {
684 $metaVal = json_decode($metaVal);
685 //check is it array of objects
686 if (is_array($metaVal) && is_object($metaVal[0])) {
687 $metaVal = $metaValue->meta_value;
688 } else {
689 $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
690 }
691 }
692 if (!isset($fldsData->{$metaKey})) {
693 $fldsData->{$metaKey} = '';
694 }
695 $fldsData->{$metaKey} = $metaVal;
696 if (isset($formFields[$metaKey]['type']) && in_array($formFields[$metaKey]['type'], ['file-up', 'advanced-file-up'])) {
697 $fldsData->{$metaKey} = $metaValue->meta_value;
698 }
699 }
700 }
701
702 return $fldsData;
703 }
704
705 public function setFieldsValue($fields, $formID, $entryID)
706 {
707 $formEntryModel = new FormEntryMetaModel();
708 $metaValues = $formEntryModel->get(
709 [
710 'meta_key',
711 'meta_value'
712 ],
713 [
714 'bitforms_form_entry_id' => $entryID,
715 ]
716 );
717 if (!is_wp_error($metaValues)) {
718 foreach ($metaValues as $metaValue) {
719 $metaKey = $metaValue->meta_key;
720 $metaVal = $metaValue->meta_value;
721 // if meta value is array then convert to string
722 if (preg_match('/^\[.*\]$/', $metaVal)) {
723 $metaVal = json_decode($metaVal);
724 //check is it array of objects
725 if (is_array($metaVal) && is_object($metaVal[0])) {
726 $metaVal = $metaValue->meta_value;
727 } else {
728 $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
729 }
730 }
731 if (property_exists($fields, $metaKey)) {
732 $fields->{$metaKey}->val = $metaVal;
733 if ('file-up' === $fields->{$metaKey}->typ || 'advanced-file-up' === $fields->{$metaKey}->typ) {
734 $fields->{$metaKey}->val = $metaValue->meta_value;
735 $fields->{$metaKey}->config->oldFiles = $metaValue->meta_value;
736 $urlQuery = wp_parse_url(FileDownloadProvider::getBaseDownloadURL(), PHP_URL_QUERY);
737 $baseDLURL = FileDownloadProvider::getBaseDownloadURL();
738 $baseDLURL = empty($urlQuery) ? $baseDLURL . '?' : $baseDLURL . '&';
739 $fields->{$metaKey}->config->baseDLURL = $baseDLURL . "formID={$formID}&entryID={$entryID}";
740 }
741 }
742 }
743 }
744 return $fields;
745 }
746
747 public function loadAssets($formID = 0, $fromType = 'classic')
748 {
749 $bfUniqFormIds = FrontendHelpers::getAllFormIdsInPage();
750 $isPageBuilder = FrontendHelpers::$isPageBuilder;
751 $bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;
752
753 if (!empty($formID)) {
754 $formIds = [$formID];
755 } else {
756 $formIds = $bfUniqFormIds;
757 }
758 foreach ($formIds as $formID) {
759 global $bitform_dequeued_styles;
760 if (is_array($bitform_dequeued_styles) && in_array($formID, $bitform_dequeued_styles)) {
761 continue;
762 }
763 if ($bfMultipleFormsExists) {
764 $newFormId = $formID . '-formid';
765 } else {
766 $newFormId = $formID;
767 }
768 $formUpdateVersion = get_option('bitform_form_update_version');
769 if (!wp_style_is('bitform-style-' . $newFormId) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css')) {
770 wp_enqueue_style(
771 'bitform-style-' . $newFormId,
772 BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-{$newFormId}.css",
773 [],
774 $formUpdateVersion
775 );
776 if ($isPageBuilder) {
777 $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css');
778 echo '<style id="bitform-style-' . esc_attr((string) $newFormId) . '">' . wp_kses($formStyle, []) . '</style>';
779 }
780 }
781 if (!wp_style_is('bitform-style-custom-' . $formID) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css')) {
782 wp_enqueue_style(
783 'bitform-style-custom-' . $formID,
784 BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-custom-{$formID}.css",
785 [],
786 $formUpdateVersion
787 );
788 if ($isPageBuilder) {
789 $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css');
790 echo '<style id="bitform-style-custom-' . esc_attr((string) $formID) . '">' . wp_kses($formStyle, []) . '</style>';
791 }
792 }
793 // load conversational form css
794 if ('conversational' === $fromType) {
795 if (!wp_style_is('bitform-conversational-style-' . $formID) &&
796 is_readable(BITFORMS_CONTENT_DIR . "/form-styles/bitform-conversational-{$formID}.css")) {
797 wp_enqueue_style(
798 'bitform-conversational-style',
799 BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-conversational-{$formID}.css",
800 [],
801 $formUpdateVersion
802 );
803 }
804 }
805 }
806 }
807 }
808