getAllIntegration('wp_user_auth', 'wp_auth', 1);
if (!is_wp_error($existResetInteg) && count($existResetInteg) > 0) {
if ('reset' === $existResetInteg[0]->integration_name) {
$user = get_userdata($userID);
if ($user) {
$validKey = check_password_reset_key($token, $user->user_login);
if (is_wp_error($validKey)) {
echo "
This password reset token is invalid.
";
exit();
}
} else {
echo "Invalid User!!
";
exit();
}
}
}
}
private function getJSFileSrc($postId)
{
$formUpdateVersion = get_option('bitform_form_update_version');
$formScriptSrc = BITFORMS_UPLOAD_BASE_URL . "/form-scripts/$postId/bitform-js-$postId.js?bfv=$formUpdateVersion";
return $formScriptSrc;
}
private function getJSFilePath($postId)
{
return BITFORMS_CONTENT_DIR . "/form-scripts/$postId/bitform-js-$postId.js";
}
/**
* Does this page's bundle need (re)generating?
*
* The DB flag alone is not enough: a page marked generated whose file was never written
* (crashed generation, unwritable uploads dir) would enqueue a 404 forever. Conversely a
* file that cannot be written must not make every request rebuild it, so a missing file
* is retried on a backoff window rather than on every hit.
*
* @param int $postId
* @param bool $regenerateScriptFlag DB-side verdict from regenerateScriptChecker()
*
* @return bool
*/
private function needsScriptGeneration($postId, $regenerateScriptFlag)
{
if (file_exists($this->getJSFilePath($postId))) {
return (bool) $regenerateScriptFlag;
}
$retryKey = 'bitforms_js_regen_' . $postId;
if (get_transient($retryKey)) {
return false;
}
set_transient($retryKey, 1, 5 * MINUTE_IN_SECONDS);
return true;
}
public function generateJs($formID = null, $entryID = null, $formType = null)
{
// bitform-js-{postId}.js is disk-cached per post ID with no language key.
// Display strings must stay out of it and travel in bf_globals per request.
// return true;
$isFormPreview = get_transient('bitform_form_preview');
if ($isFormPreview && !$formID) {
delete_transient('bitform_form_preview');
return;
}
$frontendScriptGenObj = new FrontEndScriptGenerator();
$isPageBuilder = FrontendHelpers::checkIsPageBuilder($_SERVER);
$bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
if ($isPageBuilder || empty($bfFrontendFormIds)) {
return;
}
// for unique fields ids in the same form (e.g. multiple forms in the same page)
$allFields = [];
$formContents = [];
$contentIds = [];
$formIDs = [];
$previewMode = 'classic';
$postId = '';
$formUpdateVersion = get_option('bitform_form_update_version');
if ($formID) {
$formIDs[] = $formID;
$FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
$formInfo = $FrontendFormManager->getFormInfo();
$FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
$formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
$formContent->formId = $formID;
$formContents[] = $formContent;
$workFlowRunType = $entryID ? 'edit' : 'create';
$fields = $formContent->fields;
if ($entryID) {
$fields = $this->setFieldsValue($fields, $formID, $entryID);
}
$fields = $this->triggerWorkflowOnLoad($formID, 1, $fields, $workFlowRunType);
array_push($contentIds, $FormIdentifier);
foreach ($fields as $fk => $field) {
$allFields[$field->typ][] = ['fk' => $fk, 'field' => $field, 'formID' => $formID, 'contentId' => $FormIdentifier];
}
//Generate JS file for conversational form
if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
$frontendScriptGenObj->generateJsFile([$formContent], $allFields, [$FormIdentifier], $formID, [$formID], 'conversational');
}
$previewMode = 'preview';
$postId = $formID;
} else {
global $post;
if (!is_object($post) && !isset($post->ID)) {
return;
}
$bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
$bfUniqFormIds = FrontendHelpers::getAllUniqFormIdsInPage();
$formIDs = $bfUniqFormIds;
$regenerateScriptFlag = $this->regenerateScriptChecker($bfUniqFormIds);
$postId = $post->ID;
if (!$regenerateScriptFlag) {
$regenerateScriptFlag = $this->deleteUnusedFormPageIds($postId, $bfUniqFormIds);
}
$isJsGenerating = get_option('bitforms_frontend_js_generating');
// The fast path also requires the cached bundle to exist on disk, not just be flagged in the DB.
$regenerateScriptFlag = $this->needsScriptGeneration($postId, $regenerateScriptFlag);
if (!$regenerateScriptFlag && !$isJsGenerating && !empty($formIDs)) {
wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
return;
}
foreach ($bfFrontendFormIds as $index => $formId) {
$shortCodeCounter = $index + 1;
$FrontendFormManager = FrontendFormManager::getInstance($formId, $shortCodeCounter);
$formInfo = $FrontendFormManager->getFormInfo();
$FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
$formContent = $FrontendFormManager->getFormContentWithValue($this->getValuesFromQueryParams());
$formContent->formId = $formId;
$formContents[] = $formContent;
$fields = $this->triggerWorkflowOnLoad($formId, $shortCodeCounter, $formContent->fields);
$contentIds[] = $FormIdentifier;
$formFields = []; // indivisual form fields array for conversational view
foreach ($fields as $fk => $field) {
$fieldArr = ['fk' => $fk, 'field' => $field, 'formID' => $formId, 'contentId' => $FormIdentifier];
$allFields[$field->typ][] = $fieldArr;
$formFields[$field->typ][] = $fieldArr;
}
//Generate JS file for conversational form
if (!empty($formInfo->conversationalSettings->enable) && $formInfo->conversationalSettings->enable) {
$frontendScriptGenObj->generateJsFile([$formContent], $formFields, [$FormIdentifier], $formId, [$formId], 'conversational');
}
}
}
if (empty($formIDs)) {
return;
}
$frontendScriptGenObj->generateJsFile($formContents, $allFields, $contentIds, $postId, $formIDs, $previewMode);
if ('preview' === $previewMode) {
return;
}
// Only mark the page as generated once the bundle is verifiably on disk; otherwise the
// next request must retry generation instead of fast-pathing to a stale/missing file.
if (!empty($bfUniqFormIds) && file_exists($this->getJSFilePath($postId))) {
$this->markScriptGenerated($bfUniqFormIds, $postId);
}
wp_enqueue_script('bit-form-all-script-test', $this->getJSFileSrc($postId), [], $formUpdateVersion, true);
}
private function deleteUnusedFormPageIds($postId, $formIDs)
{
global $post;
if (!is_object($post) && !isset($post->ID)) {
return;
}
$postId = $post->ID;
$formModel = new FormModel();
$forms = $formModel->get(
['id', 'generated_script_page_ids']
);
$regenerateScriptFlag = false;
foreach ($forms as $form) {
$formId = $form->id;
$generatedScriptPageIdsDecoded = json_decode((string) $form->generated_script_page_ids, true);
$generatedScriptPageIds = is_array($generatedScriptPageIdsDecoded) ? array_keys($generatedScriptPageIdsDecoded) : [];
if (!empty($generatedScriptPageIds) && !in_array($formId, $formIDs) && in_array($postId, $generatedScriptPageIds)) {
unset($generatedScriptPageIdsDecoded[$postId]);
if (empty($generatedScriptPageIdsDecoded)) {
$generatedScriptPageIdsDecoded = new \stdClass();
}
$regenerateScriptFlag = true;
$formModel->update(['generated_script_page_ids' => wp_json_encode($generatedScriptPageIdsDecoded)], ['id' => $formId]);
}
}
if ($regenerateScriptFlag) {
$formUpdateVersion = get_option('bitform_form_update_version');
if (!$formUpdateVersion) {
$formUpdateVersion = 1;
} else {
$formUpdateVersion = (int) $formUpdateVersion + 1;
}
update_option('bitform_form_update_version', $formUpdateVersion);
}
return $regenerateScriptFlag;
}
private function regenerateScriptChecker($formsIds)
{
global $post;
if (!is_a($post, 'WP_Post') && !isset($post->ID)) {
return;
}
$postId = $post->ID;
// Read-only check. Marking the page as generated is deferred to markScriptGenerated(),
// called only after the bundle file is actually written — marking here left the DB
// saying "generated" while the file stayed stale whenever generation failed mid-way.
foreach ($formsIds as $formId) {
$formInstance = FormManager::getInstance($formId);
if (!$formInstance->isExist()) {
continue;
}
$generatedPages = $formInstance->getFormData('generated_script_page_ids');
if (empty($generatedPages)) {
return true;
}
if (is_object($generatedPages) && (!isset($generatedPages->{$postId}) || false === $generatedPages->{$postId})) {
return true;
}
}
return false;
}
private function markScriptGenerated($formsIds, $postId)
{
// Fetched via FormModel rather than FormManager: FormManager keeps its row in a static
// property shared across instances, so after the render loop it holds the last form's
// data regardless of which instance is asked.
$formModel = new FormModel();
foreach ($formsIds as $formId) {
$form = $formModel->get(['generated_script_page_ids'], ['id' => $formId]);
if (is_wp_error($form) || empty($form)) {
continue;
}
$generatedPages = Utilities::jsonObj($form[0]->generated_script_page_ids ?? '');
if (!is_object($generatedPages)) {
$generatedPages = (object) [];
}
if (!empty($generatedPages->{$postId})) {
continue;
}
$generatedPages->{$postId} = true;
$formModel->update(
[
'generated_script_page_ids' => \wp_json_encode($generatedPages)
],
[
'id' => $formId,
]
);
}
}
private function addInlineScript($code, $handle = '', $position = 'after')
{
$scriptHandle = !empty($handle) ? $handle : 'bf-inline-script';
$formUpdateVersion = get_option('bitform_form_update_version');
if (!wp_script_is($scriptHandle)) {
wp_register_script($scriptHandle, '', [], $formUpdateVersion, true);
wp_enqueue_script($scriptHandle);
}
wp_add_inline_script($scriptHandle, $code, $position);
}
private function addInlineStyle($code, $handle = '')
{
$styleHandle = !empty($handle) ? $handle : 'bf-inline-style';
$formUpdateVersion = get_option('bitform_form_update_version');
if (!wp_style_is($styleHandle)) {
wp_register_style($styleHandle, '', [], $formUpdateVersion);
wp_enqueue_style($styleHandle);
}
wp_add_inline_style($styleHandle, $code);
}
private function triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType = 'create')
{
$FrontendFormManager = new FrontendFormManager($formID, $shortCodeCounter);
$previousValue = $this->getValuesFromQueryParams();
$formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
if (!empty($formContent->workFlowExist)) {
$workFlowRunHelper = new WorkFlow($formID);
if (!empty($formContent->workFlowExist->onload)) {
$workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
$workFlowRunType,
$fields
);
if (!empty($workFlowreturnedOnLoad['fields'])) {
return $workFlowreturnedOnLoad['fields'];
}
}
}
return $fields;
}
private function executeOnUserInput($formID, $shortCodeCounter, $workFlowRunType = 'create')
{
$FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
$previousValue = $this->getValuesFromQueryParams();
$formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
$customCodesExist = strpos(FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'], 'bfVars');
if ($customCodesExist || (!empty($formContent->workFlowExist) && !empty($formContent->workFlowExist->oninput))) {
$workFlowRunHelper = new WorkFlow($formID);
return $workFlowRunHelper->executeOnUserInput($workFlowRunType);
}
}
private function getValuesFromQueryParams()
{
// Read-only: query string parsed to pre-fill form fields. Values are sanitized per field before use.
$queryParamsValue = [];
if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
$reqField = wp_unslash($_SERVER['QUERY_STRING']);
foreach (explode('&', $reqField) as $keyValue) {
if (false !== strpos($keyValue, '=')) {
list($field, $value) = explode('=', $keyValue, 2);
if (!trim($value)) {
continue;
}
$field = sanitize_text_field(urldecode($field));
if (!empty($field)) {
$queryParamsValue[$field][] = sanitize_text_field(urldecode($value));
}
}
}
}
return $queryParamsValue;
}
public function handleFrontendRenderRequest($atts)
{
$formType = isset($atts['type']) ? $atts['type'] : 'classic';
$formPreview = isset($atts['form_preview']) ? $atts['form_preview'] : false;
if (isset($atts['form_id'])) {
$formID = intval($atts['form_id']);
}
if (isset($atts['entry_id'])) {
$entryId = intval($atts['entry_id']);
// Read-only: entry ID from query string for shortcode render. No state mutation.
} elseif (isset($_GET['bf_entry_id']) && !is_array($_GET['bf_entry_id'])) {
$entryId = intval(sanitize_text_field(wp_unslash($_GET['bf_entry_id'])));
} else {
$entryId = false;
}
if (isset($atts['id'])) {
$atts = shortcode_atts(['id' => 0], $atts);
$formID = intval($atts['id']);
}
if (!$formID) {
return __('Form ID cannot be empty', 'bit-form');
}
if (!$this->isExist($formID)) {
/* translators: %s: form ID */
return sprintf(__('#%s no. Form doesn\'t exists', 'bit-form'), $formID);
}
// Add-ons may detect whether the current visitor is resuming an abandoned entry.
$isAbandoned = (bool) apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts);
FrontendHelpers::setBfFrontendFormIds($formID);
$bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds;
$shortCodeCounter = count($bfFrontendFormIds);
$FrontendFormManager = FrontendFormManager::getInstance($formID, $shortCodeCounter);
if (!$FrontendFormManager->checkStatus()) {
/* translators: %s: form ID */
return sprintf(__('#%s no. Form is not active', 'bit-form'), $formID);
}
ob_start();
$this->loadAssets($formID, $formType);
$font = $FrontendFormManager->getFont();
if ($font && !$formPreview) {
wp_enqueue_style('bf-google-font', $font, '1.0.0', true);
}
// Read-only: password reset token from URL for display-time validation. No state written until form is submitted.
if (!empty($_GET['token']) && !empty($_GET['id'])) {
$this->validPassowordResetToken(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID);
}
$previousValue = $this->getValuesFromQueryParams();
$errorMessages = []; // delete
$FormIdentifier = esc_js($FrontendFormManager->getFormIdentifier());
$nonce = $FrontendFormManager->getFormToken();
$file = count($FrontendFormManager->getUploadFields()) > 0 ? $FrontendFormManager->getUploadFields() : false;
$FrontendFormManager->setViewCount();
$formContent = $FrontendFormManager->getFormContentWithValue($previousValue);
$fields = $formContent->fields;
$layout = $formContent->layout;
$nestedLayout = isset($formContent->nestedLayout) ? $formContent->nestedLayout : (object) [];
$buttons = !empty($formContent->buttons) ? $formContent->buttons : '';
$additional = $formContent->additional;
// $workFlowRunType = $entryId ? 'edit' : 'create';
if ($entryId && (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess'))) {
$workFlowRunType = 'edit';
$adminFormHandler = new AdminFormHandler();
$getEntry = $adminFormHandler->getSingleEntry($formID, $entryId);
if (FrontendHelpers::is_current_user_can_access($formID, 'entryEditAccess', '', $getEntry->__user_id)) {
$fields = $this->setFieldsValue($fields, $formID, $entryId);
} elseif (!$isAbandoned) {
$entryId = false;
$workFlowRunType = 'create';
}
} else {
$entryId = false;
$workFlowRunType = 'create';
}
// if ($entryId) {
// $fields = $this->setFieldsValue($fields, $formID, $entryId);
// }
$fields = apply_filters('bitform_filter_before_workflow_onload_fields', $fields, $formID);
$fields = $this->triggerWorkflowOnLoad($formID, $shortCodeCounter, $fields, $workFlowRunType);
$fields = apply_filters('bitform_filter_after_workflow_onload_fields', $fields, $formID);
do_action('bitform_onload_fields', $fields, $formID);
$workFlowreturnedOnUserInput = $this->executeOnUserInput($formID, $shortCodeCounter, $workFlowRunType);
// test for form before remove
$noLabelFieldTypes = ['decision-box', 'gdpr', 'html', 'shortcode', 'button', 'paypal', 'razorpay', 'recaptcha', 'turnstile', 'hcaptcha', 'stripe', 'spacer'];
foreach ($fields as $fldKey => $field) {
if (!in_array($field->typ, $noLabelFieldTypes) && isset($field->lbl)) {
$lblReplaceToBackslash = str_replace('$_bf_$', '\\', $field->lbl);
$fields->{$fldKey}->lbl = FieldValueHandler::replaceSmartTagWithValue($lblReplaceToBackslash);
}
}
$fieldsKey = $FrontendFormManager->getFieldsKey();
$captchaV3Settings = $FrontendFormManager->getCaptchaV3Settings();
if ($FrontendFormManager->getCaptchaSettings() || $captchaV3Settings || $FrontendFormManager->getTurnstileSettings() || $FrontendFormManager->isFieldTypeExist('hcaptcha')) {
$integrationHandler = new IntegrationHandler(0);
$allFormIntegrations = $integrationHandler->getAllIntegration('app');
if (!is_wp_error($allFormIntegrations)) {
foreach ($allFormIntegrations as $integration) {
if (
$FrontendFormManager->getCaptchaSettings()
&& !is_null($integration->integration_type)
&& 'gReCaptcha' === $integration->integration_type
) {
$integrationDetails = Utilities::jsonObj($integration->integration_details);
if ($integrationDetails) {
$integrationDetails->id = $integration->id;
$reCAPTCHA = $integrationDetails;
$reCAPTCHAVersion = 'v2';
}
}
if (
$FrontendFormManager->getTurnstileSettings()
&& !is_null($integration->integration_type)
&& 'turnstileCaptcha' === $integration->integration_type
) {
$integrationDetails = Utilities::jsonObj($integration->integration_details);
$turnstileSiteKey = $integrationDetails->siteKey ?? '';
}
if (
$FrontendFormManager->isFieldTypeExist('hcaptcha')
&& !is_null($integration->integration_type)
&& 'hcaptcha' === $integration->integration_type
) {
$integrationDetails = Utilities::jsonObj($integration->integration_details);
$hCaptchaSiteKey = $integrationDetails->siteKey ?? '';
}
if ($captchaV3Settings) {
if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
$integrationDetails = Utilities::jsonObj($integration->integration_details);
if ($integrationDetails) {
$integrationDetails->id = $integration->id;
$reCAPTCHA = $integrationDetails;
$reCAPTCHAVersion = 'v3';
}
}
}
}
}
}
if ($captchaV3Settings && !empty($reCAPTCHA->siteKey)) {
// DANGER: no matter what, DONT CHANGE THE SCRIPT ID OF THIS SCRIPT
$scriptId = BITFORMS_PREFIX . 'recaptcha';
// External Google reCAPTCHA script; version managed by URL query param. Loaded in header because
// standalone form views do not render wp_footer(), making footer enqueue unreliable.
wp_enqueue_script($scriptId, "https://www.google.com/recaptcha/api.js?render={$reCAPTCHA->siteKey}", [], null, false);
}
$configs = [
'bf_separator' => BITFORMS_BF_SEPARATOR,
];
// check if fields has paypal or razorpay
$paymentFields = ['paypal', 'razorpay', 'stripe'];
$paymentFieldData = [];
foreach ($fields as $key => $field) {
if (in_array($field->typ, $paymentFields)) {
$paymentFieldData[$key] = $field;
}
}
if (!empty($paymentFieldData)) {
$integrationHandler = new IntegrationHandler(0);
foreach ($paymentFieldData as $fldKey => $fldData) {
$paymentIntegration = $integrationHandler->getAIntegration($fldData->payIntegID);
if (is_wp_error($paymentIntegration)) {
continue;
}
$paymentIntegrationRow = Utilities::firstRow($paymentIntegration);
if ('paypal' === $fldData->typ) {
$integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
$clientID = $integrationDetails->clientID ?? '';
$fields->{$fldKey}->clientId = $clientID;
} elseif ('razorpay' === $fldData->typ) {
$integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
$clientID = $integrationDetails->apiKey ?? '';
$fields->{$fldKey}->clientId = $clientID;
} elseif ('stripe' === $fldData->typ) {
$integrationDetails = Utilities::jsonObj($paymentIntegrationRow->integration_details ?? '');
$publishableKey = $integrationDetails->publishableKey ?? '';
$fields->{$fldKey}->publishableKey = $publishableKey;
}
}
}
$bitFormFrontArr = [
'ajaxURL' => admin_url('admin-ajax.php'),
'nonce' => $nonce,
'version' => BITFORMS_VERSION,
'layout' => $layout,
'nestedLayout' => $nestedLayout,
'fields' => $fields,
'buttons' => $buttons,
'fieldsKey' => $fieldsKey,
'file' => $file,
'configs' => $configs,
'formId' => $formID,
'appID' => "bitforms_{$formID}",
'GCLID' => $FrontendFormManager->isGCLIDEnabled(),
'assetUrl' => BITFORMS_ASSET_URI,
'onfieldCondition' => !empty($workFlowreturnedOnUserInput['onfield_input_conditions']) ? $workFlowreturnedOnUserInput['onfield_input_conditions'] : false,
'smartTags' => $this->buildFrontendSmartTags($formID, $workFlowreturnedOnUserInput, $fields),
'paymentCallbackUrl' => get_rest_url() . 'bitform/v1/payments/razorpay',
'gRecaptchaSiteKey' => !empty($reCAPTCHA->siteKey) ? $reCAPTCHA->siteKey : null,
'gRecaptchaVersion' => !empty($reCAPTCHAVersion) ? $reCAPTCHAVersion : null,
'turnstileSiteKey' => !empty($turnstileSiteKey) ? $turnstileSiteKey : null,
'hCaptchaSiteKey' => !empty($hCaptchaSiteKey) ? $hCaptchaSiteKey : null,
];
if ($entryId) {
$bitFormFrontArr['entryId'] = $entryId;
self::markResponseUncacheable();
}
if (isset($additional->enabled->validateFocusLost)) {
$bitFormFrontArr['validateFocusLost'] = true;
}
if (!empty($isAbandoned)) {
// One visitor's typed values, so this response must not be page-cached.
$bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $isAbandoned);
self::markResponseUncacheable();
if (empty($entryId)) {
$bitFormFrontArr['entryId'] = $entryId;
}
}
$formInfo = $FrontendFormManager->getFormInfo();
$bitFormFrontArr['formName'] = $formInfo->formName ?? '';
if (is_array($layout) && count($layout) > 1) {
$multiStepSettings = isset($formInfo->multiStepSettings) ? $formInfo->multiStepSettings : null;
$newTempSettings = (object) [
'validateOnStepChange' => isset($multiStepSettings->validateOnStepChange) ? $multiStepSettings->validateOnStepChange : false,
'maintainStepHistory' => isset($multiStepSettings->maintainStepHistory) ? $multiStepSettings->maintainStepHistory : false,
'saveProgress' => isset($multiStepSettings->saveProgress) ? $multiStepSettings->saveProgress : false,
'showPercentage' => isset($multiStepSettings->progressSettings->showPercentage) ? $multiStepSettings->progressSettings->showPercentage : false,
];
$bitFormFrontArr['formInfo'] = (object) [
'multiStepSettings' => $newTempSettings
];
}
if (Helpers::property_exists_nested($formInfo, 'conversationalSettings->enable', true)) {
if (!isset($bitFormFrontArr['formInfo'])) {
$bitFormFrontArr['formInfo'] = new \stdClass();
}
$bitFormFrontArr['formInfo']->conversationalSettings = $formInfo->conversationalSettings;
}
$formAbandonmentSettings = $FrontendFormManager->getFormAbandonmentSettings();
if (Helpers::property_exists_nested($formAbandonmentSettings, 'active', true)) {
$bitFormFrontArr['formSettings'] = (object)[
'formAbandonment' => $formAbandonmentSettings
];
}
$layout = wp_json_encode($layout);
$buttons = wp_json_encode($buttons);
$frontArr = wp_json_encode($bitFormFrontArr);
$bfGlobals = sprintf('
if(!window.bf_globals) {
window.bf_globals = {}
} if(!window.bf_globals.%1$s) {
window.bf_globals.%1$s = {}
}
window.bf_globals.%1$s = {
...window.bf_globals.%1$s,
...%2$s
};
if (typeof window.bitformInit === "function") { window.bitformInit("%1$s"); }', $FormIdentifier, $frontArr);
// Inert copy of the config. Optimizers only rewrite executable scripts, so
// this survives and travels with the markup; the runtime hydrates from it
// whenever bf_globals is missing.
$configTag = self::buildFormConfigTag($FormIdentifier, $bitFormFrontArr);
if ('conversational' === $formType
&& isset($formContent->formInfo->conversationalSettings->enable)
&& $formContent->formInfo->conversationalSettings->enable) {
$html = $FrontendFormManager->conversationalFormView($fields, $file, $errorMessages, null, !empty($entryId));
} else {
$html = $FrontendFormManager->formView($fields, $file, $errorMessages, null, !empty($entryId));
}
// if form preview then return html otherwise echo with output buffer
if ($formPreview) {
ob_clean();
$formViewObject = new \stdClass();
$formViewObject->html = $html;
$formViewObject->font = $font;
$formViewObject->bfGlobals = $bfGlobals;
$formViewObject->configTag = $configTag;
$formViewObject->formContent = $formContent;
return $formViewObject;
}
$bfGlobalsHandle = 'bitform-bf-globals-' . sanitize_key($FormIdentifier);
$this->addInlineScript($bfGlobals, $bfGlobalsHandle, 'after');
$this->emitShowPickerBridge();
// Printed outside wp_kses rather than allowing ',
esc_attr($formIdentifier),
$json
);
}
/**
* Build the smart-tag map exposed to the browser in window.bf_globals[formId].smartTags.
*
* Security: the legacy code shipped the ENTIRE ~43-tag map to every visitor, leaking
* PII (admin/user/author email) and freezing per-visitor request data (IP, time,
* browser, referer) into cacheable HTML. We now emit ONLY tags that are (a) actually
* referenced by this form's client-evaluated surfaces — conditional logic, payment
* notes, admin custom JS — AND (b) flagged frontend-safe in the registry (static/post
* context only). Sensitive (identity) and request/visitor tags are never emitted; they
* resolve server-side at submit time instead.
*
* @param int|string $formID
* @param mixed $workflowConditions on-field input conditions (client-evaluated)
* @param mixed $fields form fields object (carries payment notes, etc.)
* @return array
*/
private function buildFrontendSmartTags($formID, $workflowConditions, $fields)
{
// Haystack = only surfaces the browser actually evaluates against smartTags.
$haystack = wp_json_encode($workflowConditions) . ' ' . wp_json_encode($fields);
$customJs = FrontEndScriptGenerator::getCustomCodes($formID)['JavaScript'];
if (is_string($customJs) && '' !== $customJs) {
$haystack .= ' ' . $customJs;
}
$ctx = SmartTags::getPostUserData();
$frontendSmartTags = [];
$referenced = [];
foreach (SmartTags::smartTagFieldKeys() as $key) {
if (!SmartTagRegistry::isFrontendExposable($key)) {
continue; // identity / request / param tags never travel to the browser
}
// Match '${' . key prefix so keys containing spaces/slashes/commas are handled.
if (false !== strpos($haystack, '${' . $key)) {
$referenced[] = $key;
$frontendSmartTags[$key] = SmartTagRegistry::resolve($key, $ctx);
}
}
/**
* Escape hatch: a site that genuinely needs an extra tag client-side can opt it
* back in explicitly here, rather than core shipping everything by default.
*
* @param array $frontendSmartTags resolved frontend-safe smart tags
* @param int|string $formID
* @param string[] $referenced keys detected in client surfaces
*/
return apply_filters('bitform_frontend_smarttags', $frontendSmartTags, $formID, $referenced);
}
/**
* Delegated listener that opens the native picker on date/time inputs marked
* with data-bf-show-picker. Replaces the legacy hardcoded onclick attribute.
* Registered as inline script once per request via wp_add_inline_script so
* the markup never travels through wp_kses().
*/
private function emitShowPickerBridge()
{
static $emitted = false;
if ($emitted) {
return;
}
$emitted = true;
$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(_){}}});}';
$this->addInlineScript($code, 'bitform-show-picker-bridge', 'after');
}
/**
* Does this form row exist?
*
* @param int $formID
*
* @return bool
*/
private function isExist($formID)
{
$formModel = new FormModel();
$form = $formModel->get(
[
'id'
],
[
'id' => $formID,
]
);
if (is_wp_error($form)) {
if ('result_empty' !== $form->get_error_code()) {
Log::debug_log([
'message' => 'Form lookup failed — reported to the visitor as a missing form',
'formID' => $formID,
'code' => $form->get_error_code(),
'error' => $form->get_error_message(),
]);
}
return false;
}
if (empty($form)) {
Log::debug_log([
'message' => 'Form lookup returned no rows without an error (is the form table present?)',
'formID' => $formID,
]);
return false;
}
return true;
}
private function getFieldsValue($formID, $entryID)
{
$FrontendFormManager = FrontendFormManager::getInstance($formID, 1);
$formEntryModel = new FormEntryMetaModel();
$metaValues = $formEntryModel->get(
[
'meta_key',
'meta_value'
],
[
'bitforms_form_entry_id' => $entryID,
]
);
$formFields = $FrontendFormManager->getFields();
$fldsData = (object) [];
if (!is_wp_error($metaValues)) {
foreach ($metaValues as $metaValue) {
$metaKey = $metaValue->meta_key;
$metaVal = $metaValue->meta_value;
// if meta value is array then convert to string
if (preg_match('/^\[.*\]$/', $metaVal)) {
$metaVal = json_decode($metaVal);
//check is it array of objects
if (is_array($metaVal) && is_object($metaVal[0])) {
$metaVal = $metaValue->meta_value;
} else {
$metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
}
}
if (!isset($fldsData->{$metaKey})) {
$fldsData->{$metaKey} = '';
}
$fldsData->{$metaKey} = $metaVal;
if (isset($formFields[$metaKey]['type']) && in_array($formFields[$metaKey]['type'], ['file-up', 'advanced-file-up'])) {
$fldsData->{$metaKey} = $metaValue->meta_value;
}
}
}
return $fldsData;
}
public function setFieldsValue($fields, $formID, $entryID)
{
$formEntryModel = new FormEntryMetaModel();
$metaValues = $formEntryModel->get(
[
'meta_key',
'meta_value'
],
[
'bitforms_form_entry_id' => $entryID,
]
);
if (!is_wp_error($metaValues)) {
$urlQuery = wp_parse_url(FileDownloadProvider::getBaseDownloadURL(), PHP_URL_QUERY);
$baseDLURL = FileDownloadProvider::getBaseDownloadURL();
$baseDLURL = empty($urlQuery) ? $baseDLURL . '?' : $baseDLURL . '&';
$baseDLURL .= "formID={$formID}&entryID={$entryID}";
foreach ($fields as $field) {
if ('file-up' === $field->typ || 'advanced-file-up' === $field->typ) {
if (!isset($field->config)) {
$field->config = (object) [];
} elseif (is_array($field->config)) {
$field->config = (object) $field->config;
}
$field->config->baseDLURL = $baseDLURL;
}
}
foreach ($metaValues as $metaValue) {
$metaKey = $metaValue->meta_key;
$metaVal = $metaValue->meta_value;
// if meta value is array then convert to string
if (preg_match('/^\[.*\]$/', $metaVal)) {
$metaVal = json_decode($metaVal);
//check is it array of objects
if (is_array($metaVal) && is_object($metaVal[0])) {
$metaVal = $metaValue->meta_value;
} else {
$metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal);
}
}
if (property_exists($fields, $metaKey)) {
$fields->{$metaKey}->val = $metaVal;
if ('file-up' === $fields->{$metaKey}->typ || 'advanced-file-up' === $fields->{$metaKey}->typ) {
$fields->{$metaKey}->val = $metaValue->meta_value;
$fields->{$metaKey}->config->oldFiles = $metaValue->meta_value;
}
if ('signature' === $fields->{$metaKey}->typ) {
$this->setOldSignature($fields->{$metaKey}, $formID, $entryID, $metaValue->meta_value);
}
}
}
}
return $fields;
}
/** Give the signature field its stored signature: a data URI to redraw, and the name it posts back as `_old`. */
private function setOldSignature($field, $formID, $entryID, $storedValue)
{
$fileName = is_string($storedValue) ? trim($storedValue) : '';
$decoded = json_decode($fileName, true);
if (is_array($decoded)) {
$fileName = empty($decoded) ? '' : trim((string) reset($decoded));
}
// signature-failed.png means the stored signature was never usable.
if ('' === $fileName || 'signature-failed.png' === $fileName) {
return;
}
$fileName = sanitize_file_name($fileName);
if (!isset($field->config)) {
$field->config = (object) [];
} elseif (is_array($field->config)) {
$field->config = (object) $field->config;
}
$field->config->oldSignatureFile = $fileName;
$filePath = FileHandler::getEntriesFileUploadDir($formID, $entryID) . DIRECTORY_SEPARATOR . $fileName;
if (!is_file($filePath) || !is_readable($filePath)) {
return;
}
// The types getSignatureFilePath() writes; wp_check_filetype() reports none for SVG.
$signatureMimeTypes = ['png' => 'image/png', 'jpg' => 'image/jpeg', 'svg' => 'image/svg+xml'];
$extension = strtolower((string) pathinfo($fileName, PATHINFO_EXTENSION));
if (!isset($signatureMimeTypes[$extension])) {
return;
}
$mimeType = $signatureMimeTypes[$extension];
// A hand-drawn signature is a few KB; a larger file is not worth inlining.
if (filesize($filePath) > self::MAX_INLINE_SIGNATURE_BYTES) {
return;
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents -- local upload dir read, inlined as a data URI for the signature pad.
$contents = file_get_contents($filePath);
if (false === $contents || '' === $contents) {
return;
}
$field->config->oldSignature = 'data:' . $mimeType . ';base64,' . base64_encode($contents);
}
public function loadAssets($formID = 0, $fromType = 'classic')
{
$bfUniqFormIds = FrontendHelpers::getAllFormIdsInPage();
$isPageBuilder = FrontendHelpers::$isPageBuilder;
$bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;
if (!empty($formID)) {
$formIds = [$formID];
} else {
$formIds = $bfUniqFormIds;
}
foreach ($formIds as $formID) {
global $bitform_dequeued_styles;
if (is_array($bitform_dequeued_styles) && in_array($formID, $bitform_dequeued_styles)) {
continue;
}
if ($bfMultipleFormsExists) {
$newFormId = $formID . '-formid';
} else {
$newFormId = $formID;
}
$formUpdateVersion = get_option('bitform_form_update_version');
if (!wp_style_is('bitform-style-' . $newFormId) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css')) {
wp_enqueue_style(
'bitform-style-' . $newFormId,
BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-{$newFormId}.css",
[],
$formUpdateVersion
);
if ($isPageBuilder) {
$formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css');
echo '';
}
}
if (!wp_style_is('bitform-style-custom-' . $formID) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css')) {
wp_enqueue_style(
'bitform-style-custom-' . $formID,
BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-custom-{$formID}.css",
[],
$formUpdateVersion
);
if ($isPageBuilder) {
$formStyle = file_get_contents(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css');
echo '';
}
}
// load conversational form css
if ('conversational' === $fromType) {
if (!wp_style_is('bitform-conversational-style-' . $formID) &&
is_readable(BITFORMS_CONTENT_DIR . "/form-styles/bitform-conversational-{$formID}.css")) {
wp_enqueue_style(
'bitform-conversational-style',
BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-conversational-{$formID}.css",
[],
$formUpdateVersion
);
}
}
}
}
}