PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.15.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.15.3
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/FrontendFormHandler.php +90 -292 3.2.22.15.3 View file →
@@ -1,13 +1,8 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Frontend\Form;
4 4
5 -if (!defined('ABSPATH')) {
6 - exit;
7 -}
8 -
9 -use BitCode\BitForm\Admin\Form\AdminFormHandler;
10 5 use BitCode\BitForm\Admin\Form\FrontEndScriptGenerator;
11 6 use BitCode\BitForm\Admin\Form\Helpers;
12 7 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
13 8 use BitCode\BitForm\Core\Database\FormModel;
@@ -12,17 +7,15 @@
12 7 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
13 8 use BitCode\BitForm\Core\Database\FormModel;
14 9 use BitCode\BitForm\Core\Form\FormManager;
15 10 use BitCode\BitForm\Core\Integration\IntegrationHandler;
16 -use BitCode\BitForm\Core\Util\EscapingHelper;
17 11 use BitCode\BitForm\Core\Util\FieldValueHandler;
18 12 use BitCode\BitForm\Core\Util\FileDownloadProvider;
19 13 use BitCode\BitForm\Core\Util\FrontendHelpers;
20 -use BitCode\BitForm\Core\Util\Log;
21 -use BitCode\BitForm\Core\Util\SmartTagRegistry;
22 14 use BitCode\BitForm\Core\Util\SmartTags;
23 15 use BitCode\BitForm\Core\Util\Utilities;
24 16 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
17 +use BitCode\BitFormPro\Admin\FormSettings\FormAbandonment;
25 18
26 19 final class FrontendFormHandler
27 20 {
28 21 public function __construct()
@@ -56,45 +49,14 @@
56 49 }
57 50
58 51 private function getJSFileSrc($postId)
59 52 {
60 - $formUpdateVersion = get_option('bitform_form_update_version');
53 + $formUpdateVersion = get_option('bit-form_form_update_version');
61 54 $formScriptSrc = BITFORMS_UPLOAD_BASE_URL . "/form-scripts/$postId/bitform-js-$postId.js?bfv=$formUpdateVersion";
62 55
63 56 return $formScriptSrc;
64 57 }
65 58
66 - private function getJSFilePath($postId)
67 - {
68 - return BITFORMS_CONTENT_DIR . "/form-scripts/$postId/bitform-js-$postId.js";
69 - }
70 -
71 - /**
72 - * Does this page's bundle need (re)generating?
73 - *
74 - * The DB flag alone is not enough: a page marked generated whose file was never written
75 - * (crashed generation, unwritable uploads dir) would enqueue a 404 forever. Conversely a
76 - * file that cannot be written must not make every request rebuild it, so a missing file
77 - * is retried on a backoff window rather than on every hit.
78 - *
79 - * @param int $postId
80 - * @param bool $regenerateScriptFlag DB-side verdict from regenerateScriptChecker()
81 - *
82 - * @return bool
83 - */
84 - private function needsScriptGeneration($postId, $regenerateScriptFlag)
85 - {
86 - if (file_exists($this->getJSFilePath($postId))) {
87 - return (bool) $regenerateScriptFlag;
88 - }
89 - $retryKey = 'bitforms_js_regen_' . $postId;
90 - if (get_transient($retryKey)) {
91 - return false;
92 - }
93 - set_transient($retryKey, 1, 5 * MINUTE_IN_SECONDS);
94 - return true;
95 - }
96 -
97 59 public function generateJs($formID = null, $entryID = null, $formType = null)
98 60 {
99 61 // return true;
100 62 $isFormPreview = get_transient('bitform_form_preview');
@@ -115,12 +77,12 @@
115 77 $formIDs = [];
116 78 $previewMode = 'classic';
117 79 $postId = '';
118 80
119 - $formUpdateVersion = get_option('bitform_form_update_version');
81 + $formUpdateVersion = get_option('bit-form_form_update_version');
120 82 if ($formID) {
121 83 $formIDs[] = $formID;
122 - $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
84 + $FrontendFormManager = new FrontendFormManager($formID, 1);
123 85 $formInfo = $FrontendFormManager->getFormInfo();
124 86 $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
125 87 $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
126 88 $formContent->formId = $formID;
@@ -156,10 +118,8 @@
156 118 if (!$regenerateScriptFlag) {
157 119 $regenerateScriptFlag = $this->deleteUnusedFormPageIds($postId, $bfUniqFormIds);
158 120 }
159 121 $isJsGenerating = get_option('bitforms_frontend_js_generating');
160 - // The fast path also requires the cached bundle to exist on disk, not just be flagged in the DB.
161 - $regenerateScriptFlag = $this->needsScriptGeneration($postId, $regenerateScriptFlag);
162 122 if (!$regenerateScriptFlag && !$isJsGenerating && !empty($formIDs)) {
163 123 wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
164 124 return;
165 125 }
@@ -164,9 +124,9 @@
164 124 return;
165 125 }
166 126 foreach ($bfFrontendFormIds as $index => $formId) {
167 127 $shortCodeCounter = $index + 1;
168 - $FrontendFormManager = FrontendFormManager::getInstance($formId, $shortCodeCounter);
128 + $FrontendFormManager = new FrontendFormManager($formId, $shortCodeCounter);
169 129 $formInfo = $FrontendFormManager->getFormInfo();
170 130 $FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
171 131 $formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
172 132 $formContent->formId = $formId;
@@ -189,16 +149,8 @@
189 149 return;
190 150 }
191 151
192 152 $frontendScriptGenObj->generateJsFile($formContents, $allFields, $contentIds, $postId, $formIDs, $previewMode);
193 - if ('preview' === $previewMode) {
194 - return;
195 - }
196 - // Only mark the page as generated once the bundle is verifiably on disk; otherwise the
197 - // next request must retry generation instead of fast-pathing to a stale/missing file.
198 - if (!empty($bfUniqFormIds) && file_exists($this->getJSFilePath($postId))) {
199 - $this->markScriptGenerated($bfUniqFormIds, $postId);
200 - }
201 153 wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
202 154 }
203 155
204 156 private function deleteUnusedFormPageIds($postId, $formIDs)
@@ -226,15 +178,15 @@
226 178 $formModel->update(['generated_script_page_ids' => wp_json_encode($generatedScriptPageIdsDecoded)], ['id' => $formId]);
227 179 }
228 180 }
229 181 if ($regenerateScriptFlag) {
230 - $formUpdateVersion = get_option('bitform_form_update_version');
182 + $formUpdateVersion = get_option('bit-form_form_update_version');
231 183 if (!$formUpdateVersion) {
232 184 $formUpdateVersion = 1;
233 185 } else {
234 186 $formUpdateVersion = (int) $formUpdateVersion + 1;
235 187 }
236 - update_option('bitform_form_update_version', $formUpdateVersion);
188 + update_option('bit-form_form_update_version', $formUpdateVersion);
237 189 }
238 190 return $regenerateScriptFlag;
239 191 }
240 192
@@ -244,45 +196,27 @@
244 196 if (!is_a($post, 'WP_Post') && !isset($post->ID)) {
245 197 return;
246 198 }
247 199 $postId = $post->ID;
248 - // Read-only check. Marking the page as generated is deferred to markScriptGenerated(),
249 - // called only after the bundle file is actually written — marking here left the DB
250 - // saying "generated" while the file stayed stale whenever generation failed mid-way.
200 + $regenerateScriptFlag = false;
201 + $formModel = new FormModel();
251 202 foreach ($formsIds as $formId) {
252 - $formInstance = FormManager::getInstance($formId);
203 + $formInstance = new FormManager($formId);
253 204 if (!$formInstance->isExist()) {
254 205 continue;
255 206 }
256 207 $generatedPages = $formInstance->getFormData('generated_script_page_ids');
257 208 if (empty($generatedPages)) {
258 - return true;
209 + $regenerateScriptFlag = true;
210 + } elseif (is_object($generatedPages) && (!isset($generatedPages->{$postId}) || (isset($generatedPages->{$postId}) && false === $generatedPages->{$postId}))) {
211 + $regenerateScriptFlag = true;
259 212 }
260 - if (is_object($generatedPages) && (!isset($generatedPages->{$postId}) || false === $generatedPages->{$postId})) {
261 - return true;
262 - }
263 - }
264 - return false;
265 - }
266 -
267 - private function markScriptGenerated($formsIds, $postId)
268 - {
269 - // Fetched via FormModel rather than FormManager: FormManager keeps its row in a static
270 - // property shared across instances, so after the render loop it holds the last form's
271 - // data regardless of which instance is asked.
272 - $formModel = new FormModel();
273 - foreach ($formsIds as $formId) {
274 - $form = $formModel->get(['generated_script_page_ids'], ['id' => $formId]);
275 - if (is_wp_error($form) || empty($form)) {
213 + if (!$regenerateScriptFlag) {
276 214 continue;
277 215 }
278 - $generatedPages = Utilities::jsonObj($form[0]->generated_script_page_ids ?? '');
279 216 if (!is_object($generatedPages)) {
280 217 $generatedPages = (object) [];
281 218 }
282 - if (!empty($generatedPages->{$postId})) {
283 - continue;
284 - }
285 219 $generatedPages->{$postId} = true;
286 220 $formModel->update(
287 221 [
288 222 'generated_script_page_ids' => \wp_json_encode($generatedPages)
@@ -291,16 +225,16 @@
291 225 'id' => $formId,
292 226 ]
293 227 );
294 228 }
229 + return $regenerateScriptFlag;
295 230 }
296 231
297 232 private function addInlineScript($code, $handle = '', $position = 'after')
298 233 {
299 234 $scriptHandle = !empty($handle) ? $handle : 'bf-inline-script';
300 - $formUpdateVersion = get_option('bitform_form_update_version');
301 235 if (!wp_script_is($scriptHandle)) {
302 - wp_register_script($scriptHandle, '', [], $formUpdateVersion, true);
236 + wp_register_script($scriptHandle, '', [], '', true);
303 237 wp_enqueue_script($scriptHandle);
304 238 }
305 239 wp_add_inline_script($scriptHandle, $code, $position);
306 240 }
@@ -307,11 +241,10 @@
307 241
308 242 private function addInlineStyle($code, $handle = '')
309 243 {
310 244 $styleHandle = !empty($handle) ? $handle : 'bf-inline-style';
311 - $formUpdateVersion = get_option('bitform_form_update_version');
312 245 if (!wp_style_is($styleHandle)) {
313 - wp_register_style($styleHandle, '', [], $formUpdateVersion);
246 + wp_register_style($styleHandle, '', [], '', true);
314 247 wp_enqueue_style($styleHandle);
315 248 }
316 249 wp_add_inline_style($styleHandle, $code);
317 250 }
@@ -337,37 +270,37 @@
337 270
338 271 return $fields;
339 272 }
340 273
341 - private function executeOnUserInput($formID, $shortCodeCounter, $workFlowRunType = 'create')
274 + private function executeOnUserInput($formID, $shortCodeCounter, $fields)
342 275 {
343 - $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
276 + $FrontendFormManager = new FrontendFormManager($formID, $shortCodeCounter);
344 277 $previousValue = $this->getValuesFromQueryParams();
345 278 $formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
346 279 $customCodesExist = strpos(FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'], 'bfVars');
347 280 if ($customCodesExist || (!empty($formContent->workFlowExist) && !empty($formContent->workFlowExist->oninput))) {
348 281 $workFlowRunHelper = new WorkFlow($formID);
349 - return $workFlowRunHelper->executeOnUserInput($workFlowRunType);
282 + return $workFlowRunHelper->executeOnUserInput('create', $fields);
350 283 }
351 284 }
352 285
353 286 private function getValuesFromQueryParams()
354 287 {
355 - // Read-only: query string parsed to pre-fill form fields. Values are sanitized per field before use.
356 288 $queryParamsValue = [];
357 289 if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
358 - $reqField = wp_unslash($_SERVER['QUERY_STRING']);
290 + $reqField = $_SERVER['QUERY_STRING'];
359 291 foreach (explode('&', $reqField) as $keyValue) {
360 - if (false !== strpos($keyValue, '=')) {
292 + // $pattern = '/([a-zA-Z0-9])([a-zA-Z])\=+/';
293 + $pattern = '/([^.]+)=(.*?)([^.]+)/';
294 + $matches = preg_match($pattern, $keyValue, $matchFormat);
295 + if ($matches) {
361 296 list($field, $value) = explode('=', $keyValue, 2);
362 297
363 298 if (!trim($value)) {
364 299 continue;
365 300 }
366 - $field = sanitize_text_field(urldecode($field));
367 - if (!empty($field)) {
368 - $queryParamsValue[$field][] = sanitize_text_field(urldecode($value));
369 - }
301 +
302 + $queryParamsValue[$field][] = sanitize_text_field(urldecode($value));
370 303 }
371 304 }
372 305 }
373 306
@@ -382,11 +315,10 @@
382 315 $formID = intval($atts['form_id']);
383 316 }
384 317 if (isset($atts['entry_id'])) {
385 318 $entryId = intval($atts['entry_id']);
386 - // Read-only: entry ID from query string for shortcode render. No state mutation.
387 - } elseif (isset($_GET['bf_entry_id']) && !is_array($_GET['bf_entry_id'])) {
388 - $entryId = intval(sanitize_text_field(wp_unslash($_GET['bf_entry_id'])));
319 + } elseif (isset($_GET['bf_entry_id'])) {
320 + $entryId = $_GET['bf_entry_id'];
389 321 } else {
390 322 $entryId = false;
391 323 }
392 324 if (isset($atts['id'])) {
@@ -398,22 +330,24 @@
398 330 return __('Form ID cannot be empty', 'bit-form');
399 331 }
400 332
401 333 if (!$this->isExist($formID)) {
402 - /* translators: %s: form ID */
403 334 return sprintf(__('#%s no. Form doesn\'t exists', 'bit-form'), $formID);
404 335 }
405 336
406 - // Add-ons may detect whether the current visitor is resuming an abandoned entry.
407 - $isAbandoned = (bool) apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts);
337 + // check for abandoned form entry id
338 + $isAbandoned = false;
339 + if (empty($entryId) && Utilities::isPro() && class_exists('\BitCode\BitFormPro\Admin\FormSettings\FormAbandonment')) {
340 + $FormAbandonment = new FormAbandonment($formID);
341 + $isAbandoned = $FormAbandonment->checkAbandonedFormEntryId();
342 + }
408 343
409 344 FrontendHelpers::setBfFrontendFormIds($formID);
410 345 $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
411 346 $shortCodeCounter = count($bfFrontendFormIds);
412 - $FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
347 + $FrontendFormManager = new FrontendFormManager($formID, $shortCodeCounter);
413 348
414 349 if (!$FrontendFormManager->checkStatus()) {
415 - /* translators: %s: form ID */
416 350 return sprintf(__('#%s no. Form is not active', 'bit-form'), $formID);
417 351 }
418 352 ob_start();
419 353 $this->loadAssets($formID, $formType);
@@ -423,11 +357,10 @@
423 357 if ($font && !$formPreview) {
424 358 wp_enqueue_style('bf-google-font', $font, '1.0.0', true);
425 359 }
426 360
427 - // Read-only: password reset token from URL for display-time validation. No state written until form is submitted.
428 361 if (!empty($_GET['token']) && !empty($_GET['id'])) {
429 - $this->validPassowordResetToken(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID);
362 + $this->validPassowordResetToken($_GET['token'], $_GET['id'], $formID);
430 363 }
431 364
432 365 $previousValue = $this->getValuesFromQueryParams();
433 366 $errorMessages = []; // delete
@@ -444,20 +377,13 @@
444 377 $buttons = !empty($formContent->buttons) ? $formContent->buttons : '';
445 378 $additional = $formContent->additional;
446 379
447 380 // $workFlowRunType = $entryId ? 'edit' : 'create';
448 - if ($entryId && (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess'))) {
381 + if ($entryId && (get_current_user_id() || is_admin())) {
382 + // return sprintf(__('Sorry!, You cannot edit #%s no form.', 'bit-form'), $formID);
449 383 $workFlowRunType = 'edit';
450 - $adminFormHandler = new AdminFormHandler();
451 - $getEntry = $adminFormHandler->getSingleEntry($formID, $entryId);
452 - if (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess', '', $getEntry->__user_id)) {
453 - $fields = $this->setFieldsValue($fields, $formID, $entryId);
454 - } elseif (!$isAbandoned) {
455 - $entryId = false;
456 - $workFlowRunType = 'create';
457 - }
384 + $fields = $this->setFieldsValue($fields, $formID, $entryId);
458 385 } else {
459 - $entryId = false;
460 386 $workFlowRunType = 'create';
461 387 }
462 388
463 389 // if ($entryId) {
@@ -467,14 +393,14 @@
467 393 $fields = apply_filters('bitform_filter_before_workflow_onload_fields', $fields, $formID);
468 394 $fields = $this->triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType);
469 395 $fields = apply_filters('bitform_filter_after_workflow_onload_fields', $fields, $formID);
470 396 do_action('bitform_onload_fields', $fields, $formID);
471 - $workFlowreturnedOnUserInput = $this->executeOnUserInput($formID, $shortCodeCounter, $workFlowRunType);
397 + $workFlowreturnedOnUserInput = $this->executeOnUserInput($formID, $shortCodeCounter, $fields);
472 398
473 399 // test for form before remove
474 - $noLabelFieldTypes = ['decision-box', 'gdpr', 'html', 'shortcode', 'button', 'paypal', 'razorpay', 'recaptcha', 'turnstile', 'hcaptcha', 'stripe', 'spacer'];
400 + $noLabel = ['decision-box', 'html', 'shortcode', 'button', 'paypal', 'razorpay', 'recaptcha'];
475 401 foreach ($fields as $fldKey => $field) {
476 - if (!in_array($field->typ, $noLabelFieldTypes) && isset($field->lbl)) {
402 + if (!in_array($field->typ, $noLabel) && isset($field->lbl)) {
477 403 $lblReplaceToBackslash = str_replace('$_bf_$', '\\', $field->lbl);
478 404 $fields->{$fldKey}->lbl = FieldValueHandler::replaceSmartTagWithValue($lblReplaceToBackslash);
479 405 }
480 406 }
@@ -480,9 +406,9 @@
480 406 }
481 407 $fieldsKey = $FrontendFormManager->getFieldsKey();
482 408
483 409 $captchaV3Settings = $FrontendFormManager->getCaptchaV3Settings();
484 - if ($FrontendFormManager->getCaptchaSettings() || $captchaV3Settings || $FrontendFormManager->getTurnstileSettings() || $FrontendFormManager->isFieldTypeExist('hcaptcha')) {
410 + if ($FrontendFormManager->getCaptchaSettings() || $captchaV3Settings || $FrontendFormManager->getTurnstileSettings()) {
485 411 $integrationHandler = new IntegrationHandler(0);
486 412 $allFormIntegrations = $integrationHandler->getAllIntegration('app');
487 413 if (!is_wp_error($allFormIntegrations)) {
488 414 foreach ($allFormIntegrations as $integration) {
@@ -490,14 +416,12 @@
490 416 $FrontendFormManager->getCaptchaSettings()
491 417 && !is_null($integration->integration_type)
492 418 && 'gReCaptcha' === $integration->integration_type
493 419 ) {
494 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
495 - if ($integrationDetails) {
496 - $integrationDetails->id = $integration->id;
497 - $reCAPTCHA = $integrationDetails;
498 - $reCAPTCHAVersion = 'v2';
499 - }
420 + $integrationDetails = json_decode($integration->integration_details);
421 + $integrationDetails->id = $integration->id;
422 + $reCAPTCHA = $integrationDetails;
423 + $reCAPTCHAVersion = 'v2';
500 424 }
501 425
502 426 if (
503 427 $FrontendFormManager->getTurnstileSettings()
@@ -503,29 +427,18 @@
503 427 $FrontendFormManager->getTurnstileSettings()
504 428 && !is_null($integration->integration_type)
505 429 && 'turnstileCaptcha' === $integration->integration_type
506 430 ) {
507 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
508 - $turnstileSiteKey = $integrationDetails->siteKey ?? '';
431 + $integrationDetails = json_decode($integration->integration_details);
432 + $turnstileSiteKey = $integrationDetails->siteKey;
509 433 }
510 434
511 - if (
512 - $FrontendFormManager->isFieldTypeExist('hcaptcha')
513 - && !is_null($integration->integration_type)
514 - && 'hcaptcha' === $integration->integration_type
515 - ) {
516 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
517 - $hCaptchaSiteKey = $integrationDetails->siteKey ?? '';
518 - }
519 -
520 435 if ($captchaV3Settings) {
521 436 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
522 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
523 - if ($integrationDetails) {
524 - $integrationDetails->id = $integration->id;
525 - $reCAPTCHA = $integrationDetails;
526 - $reCAPTCHAVersion = 'v3';
527 - }
437 + $integrationDetails = json_decode($integration->integration_details);
438 + $integrationDetails->id = $integration->id;
439 + $reCAPTCHA = $integrationDetails;
440 + $reCAPTCHAVersion = 'v3';
528 441 }
529 442 }
530 443 }
531 444 }
@@ -533,11 +446,9 @@
533 446
534 447 if ($captchaV3Settings && !empty($reCAPTCHA->siteKey)) {
535 448 // DANGER: no matter what, DONT CHANGE THE SCRIPT ID OF THIS SCRIPT
536 449 $scriptId = BITFORMS_PREFIX . 'recaptcha';
537 - // External Google reCAPTCHA script; version managed by URL query param. Loaded in header because
538 - // standalone form views do not render wp_footer(), making footer enqueue unreliable.
539 - wp_enqueue_script($scriptId, "https://www.google.com/recaptcha/api.js?render={$reCAPTCHA->siteKey}", [], null, false);
450 + wp_enqueue_script($scriptId, "https://www.google.com/recaptcha/api.js?render={$reCAPTCHA->siteKey}");
540 451 }
541 452
542 453 $configs = [
543 454 'bf_separator' => BITFORMS_BF_SEPARATOR,
@@ -558,20 +469,19 @@
558 469 $paymentIntegration = $integrationHandler->getAIntegration($fldData->payIntegID);
559 470 if (is_wp_error($paymentIntegration)) {
560 471 continue;
561 472 }
562 - $paymentIntegrationRow = Utilities::firstRow($paymentIntegration);
563 473 if ('paypal' === $fldData->typ) {
564 - $integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
565 - $clientID = $integrationDetails->clientID ?? '';
474 + $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
475 + $clientID = $integrationDetails->clientID;
566 476 $fields->{$fldKey}->clientId = $clientID;
567 477 } elseif ('razorpay' === $fldData->typ) {
568 - $integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
569 - $clientID = $integrationDetails->apiKey ?? '';
478 + $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
479 + $clientID = $integrationDetails->apiKey;
570 480 $fields->{$fldKey}->clientId = $clientID;
571 481 } elseif ('stripe' === $fldData->typ) {
572 - $integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
573 - $publishableKey = $integrationDetails->publishableKey ?? '';
482 + $integrationDetails = json_decode($paymentIntegration[0]->integration_details);
483 + $publishableKey = $integrationDetails->publishableKey;
574 484 $fields->{$fldKey}->publishableKey = $publishableKey;
575 485 }
576 486 }
577 487 }
@@ -591,14 +501,13 @@
591 501 'appID' => "bitforms_{$formID}",
592 502 'GCLID' => $FrontendFormManager->isGCLIDEnabled(),
593 503 'assetUrl' => BITFORMS_ASSET_URI,
594 504 'onfieldCondition' => !empty($workFlowreturnedOnUserInput['onfield_input_conditions']) ? $workFlowreturnedOnUserInput['onfield_input_conditions'] : false,
595 - 'smartTags' => $this->buildFrontendSmartTags($formID, $workFlowreturnedOnUserInput, $fields),
505 + 'smartTags' => SmartTags::smartTags(SmartTags::getPostUserData()),
596 506 'paymentCallbackUrl' => get_rest_url() . 'bitform/v1/payments/razorpay',
597 507 'gRecaptchaSiteKey' => !empty($reCAPTCHA->siteKey) ? $reCAPTCHA->siteKey : null,
598 508 'gRecaptchaVersion' => !empty($reCAPTCHAVersion) ? $reCAPTCHAVersion : null,
599 - 'turnstileSiteKey' => !empty($turnstileSiteKey) ? $turnstileSiteKey : null,
600 - 'hCaptchaSiteKey' => !empty($hCaptchaSiteKey) ? $hCaptchaSiteKey : null,
509 + 'turnstileSiteKey' => !empty($turnstileSiteKey) ? $turnstileSiteKey : null
601 510 ];
602 511
603 512 if ($entryId) {
604 513 $bitFormFrontArr['entryId'] = $entryId;
@@ -610,14 +519,13 @@
610 519
611 520 if (!empty($isAbandoned)) {
612 521 $bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $isAbandoned);
613 522 if (empty($entryId)) {
614 - $bitFormFrontArr['entryId'] = $entryId;
523 + $bitFormFrontArr['entryId'] = $isAbandoned;
615 524 }
616 525 }
617 526
618 527 $formInfo = $FrontendFormManager->getFormInfo();
619 - $bitFormFrontArr['formName'] = $formInfo->formName ?? '';
620 528 if (is_array($layout) && count($layout) > 1) {
621 529 $multiStepSettings = isset($formInfo->multiStepSettings) ? $formInfo->multiStepSettings : null;
622 530 $newTempSettings = (object) [
623 531 'validateOnStepChange' => isset($multiStepSettings->validateOnStepChange) ? $multiStepSettings->validateOnStepChange : false,
@@ -643,24 +551,27 @@
643 551 'formAbandonment' => $formAbandonmentSettings
644 552 ];
645 553 }
646 554
555 + $bitFormsFront = apply_filters(
556 + 'bitforms_localized_script',
557 + $bitFormFrontArr
558 + );
559 +
647 560 $layout = wp_json_encode($layout);
648 561 $buttons = wp_json_encode($buttons);
649 562 $frontArr = wp_json_encode($bitFormFrontArr);
650 563
651 - $bfGlobals = sprintf('
564 + $bfGlobals = <<<BFGLOBALS
652 565 if(!window.bf_globals) {
653 566 window.bf_globals = {}
654 - } if(!window.bf_globals.%1$s) {
655 - window.bf_globals.%1$s = {}
567 + } if(!window.bf_globals.{$FormIdentifier}) {
568 + window.bf_globals.{$FormIdentifier} = {}
656 569 }
657 - if(document.getElementById("%1$s")) {
658 - window.bf_globals.%1$s = {
659 - ...window.bf_globals.%1$s,
660 - ...%2$s
661 - };
662 - }', $FormIdentifier, $frontArr);
570 + if(document.getElementById('{$FormIdentifier}')) {
571 + window.bf_globals.{$FormIdentifier} = {...window.bf_globals.{$FormIdentifier}, ...{$frontArr}};
572 + }
573 +BFGLOBALS;
663 574
664 575 if ('conversational' === $formType
665 576 && isset($formContent->formInfo->conversationalSettings->enable)
666 577 && $formContent->formInfo->conversationalSettings->enable) {
@@ -675,94 +586,17 @@
675 586 $formViewObject = new \stdClass();
676 587 $formViewObject->html = $html;
677 588 $formViewObject->font = $font;
678 589 $formViewObject->bfGlobals = $bfGlobals;
679 - $formViewObject->formContent = $formContent;
680 590 return $formViewObject;
681 591 }
682 -
683 - $bfGlobalsHandle = 'bitform-bf-globals-' . sanitize_key($FormIdentifier);
684 - $this->addInlineScript($bfGlobals, $bfGlobalsHandle, 'after');
685 - $this->emitShowPickerBridge();
686 -
687 - echo wp_kses(trim($html), EscapingHelper::getFormAllowedHtml($formContent));
592 + $html .= <<<BFGLOBALSSCRIPT
593 + <script id="bit-form-bf-globals-{$FormIdentifier}">{$bfGlobals}</script>
594 +BFGLOBALSSCRIPT;
595 + echo trim($html);
688 596 return ob_get_clean();
689 597 }
690 598
691 - /**
692 - * Build the smart-tag map exposed to the browser in window.bf_globals[formId].smartTags.
693 - *
694 - * Security: the legacy code shipped the ENTIRE ~43-tag map to every visitor, leaking
695 - * PII (admin/user/author email) and freezing per-visitor request data (IP, time,
696 - * browser, referer) into cacheable HTML. We now emit ONLY tags that are (a) actually
697 - * referenced by this form's client-evaluated surfaces — conditional logic, payment
698 - * notes, admin custom JS — AND (b) flagged frontend-safe in the registry (static/post
699 - * context only). Sensitive (identity) and request/visitor tags are never emitted; they
700 - * resolve server-side at submit time instead.
701 - *
702 - * @param int|string $formID
703 - * @param mixed $workflowConditions on-field input conditions (client-evaluated)
704 - * @param mixed $fields form fields object (carries payment notes, etc.)
705 - * @return array<string,string>
706 - */
707 - private function buildFrontendSmartTags($formID, $workflowConditions, $fields)
708 - {
709 - // Haystack = only surfaces the browser actually evaluates against smartTags.
710 - $haystack = wp_json_encode($workflowConditions) . ' ' . wp_json_encode($fields);
711 - $customJs = FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'];
712 - if (is_string($customJs) && '' !== $customJs) {
713 - $haystack .= ' ' . $customJs;
714 - }
715 -
716 - $ctx = SmartTags::getPostUserData();
717 - $frontendSmartTags = [];
718 - $referenced = [];
719 - foreach (SmartTags::smartTagFieldKeys() as $key) {
720 - if (!SmartTagRegistry::isFrontendExposable($key)) {
721 - continue; // identity / request / param tags never travel to the browser
722 - }
723 - // Match '${' . key prefix so keys containing spaces/slashes/commas are handled.
724 - if (false !== strpos($haystack, '${' . $key)) {
725 - $referenced[] = $key;
726 - $frontendSmartTags[$key] = SmartTagRegistry::resolve($key, $ctx);
727 - }
728 - }
729 -
730 - /**
731 - * Escape hatch: a site that genuinely needs an extra tag client-side can opt it
732 - * back in explicitly here, rather than core shipping everything by default.
733 - *
734 - * @param array<string,string> $frontendSmartTags resolved frontend-safe smart tags
735 - * @param int|string $formID
736 - * @param string[] $referenced keys detected in client surfaces
737 - */
738 - return apply_filters('bitform_frontend_smarttags', $frontendSmartTags, $formID, $referenced);
739 - }
740 -
741 - /**
742 - * Delegated listener that opens the native picker on date/time inputs marked
743 - * with data-bf-show-picker. Replaces the legacy hardcoded onclick attribute.
744 - * Registered as inline script once per request via wp_add_inline_script so
745 - * the markup never travels through wp_kses().
746 - */
747 - private function emitShowPickerBridge()
748 - {
749 - static $emitted = false;
750 - if ($emitted) {
751 - return;
752 - }
753 - $emitted = true;
754 - $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(_){}}});}';
755 - $this->addInlineScript($code, 'bitform-show-picker-bridge', 'after');
756 - }
757 -
758 - /**
759 - * Does this form row exist?
760 - *
761 - * @param int $formID
762 - *
763 - * @return bool
764 - */
765 599 private function isExist($formID)
766 600 {
767 601 $formModel = new FormModel();
768 602 $form = $formModel->get(
@@ -772,37 +606,16 @@
772 606 [
773 607 'id' => $formID,
774 608 ]
775 609 );
776 -
777 - if (is_wp_error($form)) {
778 - if ('result_empty' !== $form->get_error_code()) {
779 - Log::debug_log([
780 - 'message' => 'Form lookup failed — reported to the visitor as a missing form',
781 - 'formID' => $formID,
782 - 'code' => $form->get_error_code(),
783 - 'error' => $form->get_error_message(),
784 - ]);
785 - }
786 -
787 - return false;
610 + if (!is_wp_error($form)) {
611 + return true;
788 612 }
789 -
790 - if (empty($form)) {
791 - Log::debug_log([
792 - 'message' => 'Form lookup returned no rows without an error (is the form table present?)',
793 - 'formID' => $formID,
794 - ]);
795 -
796 - return false;
797 - }
798 -
799 - return true;
613 + return false;
800 614 }
801 615
802 616 private function getFieldsValue($formID, $entryID)
803 617 {
804 - $FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
805 618 $formEntryModel = new FormEntryMetaModel();
806 619 $metaValues = $formEntryModel->get(
807 620 [
808 621 'meta_key',
@@ -811,9 +624,8 @@
811 624 [
812 625 'bitforms_form_entry_id' => $entryID,
813 626 ]
814 627 );
815 - $formFields = $FrontendFormManager->getFields();
816 628 $fldsData = (object) [];
817 629 if (!is_wp_error($metaValues)) {
818 630 foreach ($metaValues as $metaValue) {
819 631 $metaKey = $metaValue->meta_key;
@@ -831,11 +643,8 @@
831 643 if (!isset($fldsData->{$metaKey})) {
832 644 $fldsData->{$metaKey} = '';
833 645 }
834 646 $fldsData->{$metaKey} = $metaVal;
835 - if (isset($formFields[$metaKey]['type']) && in_array($formFields[$metaKey]['type'], ['file-up', 'advanced-file-up'])) {
836 - $fldsData->{$metaKey} = $metaValue->meta_value;
837 - }
838 647 }
839 648 }
840 649
841 650 return $fldsData;
@@ -853,23 +662,8 @@
853 662 'bitforms_form_entry_id' => $entryID,
854 663 ]
855 664 );
856 665 if (!is_wp_error($metaValues)) {
857 - $urlQuery = wp_parse_url(FileDownloadProvider::getBaseDownloadURL(), PHP_URL_QUERY);
858 - $baseDLURL = FileDownloadProvider::getBaseDownloadURL();
859 - $baseDLURL = empty($urlQuery) ? $baseDLURL . '?' : $baseDLURL . '&';
860 - $baseDLURL .= "formID={$formID}&entryID={$entryID}";
861 -
862 - foreach ($fields as $field) {
863 - if ('file-up' === $field->typ || 'advanced-file-up' === $field->typ) {
864 - if (!isset($field->config)) {
865 - $field->config = (object) [];
866 - } elseif (is_array($field->config)) {
867 - $field->config = (object) $field->config;
868 - }
869 - $field->config->baseDLURL = $baseDLURL;
870 - }
871 - }
872 666 foreach ($metaValues as $metaValue) {
873 667 $metaKey = $metaValue->meta_key;
874 668 $metaVal = $metaValue->meta_value;
875 669 // if meta value is array then convert to string
@@ -886,8 +680,12 @@
886 680 $fields->{$metaKey}->val = $metaVal;
887 681 if ('file-up' === $fields->{$metaKey}->typ || 'advanced-file-up' === $fields->{$metaKey}->typ) {
888 682 $fields->{$metaKey}->val = $metaValue->meta_value;
889 683 $fields->{$metaKey}->config->oldFiles = $metaValue->meta_value;
684 + $urlQuery = wp_parse_url(FileDownloadProvider::getBaseDownloadURL(), PHP_URL_QUERY);
685 + $baseDLURL = FileDownloadProvider::getBaseDownloadURL();
686 + $baseDLURL = empty($urlQuery) ? $baseDLURL . '?' : $baseDLURL . '&';
687 + $fields->{$metaKey}->config->baseDLURL = $baseDLURL . "formID={$formID}&entryID={$entryID}";
890 688 }
891 689 }
892 690 }
893 691 }
@@ -914,9 +712,9 @@
914 712 $newFormId = $formID . '-formid';
915 713 } else {
916 714 $newFormId = $formID;
917 715 }
918 - $formUpdateVersion = get_option('bitform_form_update_version');
716 + $formUpdateVersion = get_option('bit-form_form_update_version');
919 717 if (!wp_style_is('bitform-style-' . $newFormId) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css')) {
920 718 wp_enqueue_style(
921 719 'bitform-style-' . $newFormId,
922 720 BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-{$newFormId}.css",
@@ -924,9 +722,9 @@
924 722 $formUpdateVersion
925 723 );
926 724 if ($isPageBuilder) {
927 725 $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css');
928 - echo '<style id="bitform-style-' . esc_attr((string) $newFormId) . '">' . wp_kses($formStyle, []) . '</style>';
726 + echo sprintf("<style id='bitform-style-{$newFormId}'>%s</style>", $formStyle);
929 727 }
930 728 }
931 729 if (!wp_style_is('bitform-style-custom-' . $formID) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css')) {
932 730 wp_enqueue_style(
@@ -936,9 +734,9 @@
936 734 $formUpdateVersion
937 735 );
938 736 if ($isPageBuilder) {
939 737 $formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css');
940 - echo '<style id="bitform-style-custom-' . esc_attr((string) $formID) . '">' . wp_kses($formStyle, []) . '</style>';
738 + echo sprintf("<style id='bitform-style-custom-{$formID}'>%s</style>", $formStyle);
941 739 }
942 740 }
943 741 // load conversational form css
944 742 if ('conversational' === $fromType) {