_formID = $formID; static::$_integrationModel = new IntegrationModel(); $this->_user_details = $user_details; } public function getAIntegration($integrationID, $integrationCategory = null, $integrationType = null) { $conditions = [ 'form_id' => $this->_formID, 'id' => $integrationID, ]; if (!is_null($integrationType)) { $conditions = array_merge($conditions, ['integration_type' => $integrationType]); } if (!is_null($integrationCategory)) { $conditions = array_merge($conditions, ['category' => $integrationCategory]); } return static::$_integrationModel->get( [ 'id', 'integration_name', 'integration_type', 'integration_details', 'status', 'form_id', ], $conditions ); } public function getAllIntegration($integrationCategory = null, $integrationType = null, $status = null, $id = null) { $conditions = [ 'form_id' => $this->_formID, ]; if (!is_null($integrationType)) { $conditions = array_merge($conditions, ['integration_type' => $integrationType]); } if (!is_null($integrationCategory)) { $conditions = array_merge($conditions, ['category' => $integrationCategory]); } if (!is_null($status)) { $conditions = array_merge($conditions, ['status' => 1]); } if (!is_null($id)) { $conditions = array_merge($conditions, ['id' => $id]); } return static::$_integrationModel->get( [ 'id', 'form_id', 'integration_name', 'integration_type', 'integration_details', 'status', ], $conditions ); } public function getIntegrationWithoutFormId($integrationCategory = null, $integrationType = null, $status = null, $id = null) { $conditions = []; if (!is_null($integrationType)) { $conditions = array_merge($conditions, ['integration_type' => $integrationType]); } if (!is_null($integrationCategory)) { $conditions = array_merge($conditions, ['category' => $integrationCategory]); } if (!is_null($status)) { $conditions = array_merge($conditions, ['status' => 1]); } if (!is_null($id)) { $conditions = array_merge($conditions, ['id' => $id]); } return static::$_integrationModel->get( [ 'id', 'form_id', 'integration_name', 'integration_type', 'integration_details', 'status', ], $conditions ); } public function saveIntegration($integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null) { if (null === $status) { $status = 1; } return static::$_integrationModel->insert( [ 'integration_name' => $integrationName, 'integration_type' => $integrationType, 'integration_details' => $integrationDetails, 'category' => $integrationCategory, 'form_id' => $this->_formID, 'user_id' => $this->_user_details['id'], 'user_ip' => $this->_user_details['ip'], 'status' => $status, 'created_at' => $this->_user_details['time'], 'updated_at' => $this->_user_details['time'], ] ); } public function updateIntegration($integrationID, $integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null) { if (null === $status) { $status = 1; } return static::$_integrationModel->update( [ 'integration_name' => $integrationName, 'integration_type' => $integrationType, 'integration_details' => $integrationDetails, 'category' => $integrationCategory, 'form_id' => $this->_formID, 'user_id' => $this->_user_details['id'], 'user_ip' => $this->_user_details['ip'], 'status' => $status, 'updated_at' => $this->_user_details['time'], ], [ 'id' => $integrationID, ] ); } public function updateIntegrationStatus($integrationID, $status) { $data = ['status' => (int) $status]; if (!empty($this->_user_details['time'])) { $data['updated_at'] = $this->_user_details['time']; } return static::$_integrationModel->update( $data, [ 'id' => $integrationID, 'form_id' => $this->_formID, ] ); } public function duplicateAllInAForm($oldFormId) { $integCols = ['integration_name', 'integration_type', 'integration_details', 'category', 'form_id', 'user_id', 'user_ip', 'status', 'created_at', 'updated_at']; $integDupData = [ 'integration_name', 'integration_type', 'integration_details', 'category', $this->_formID, $this->_user_details['id'], $this->_user_details['ip'], 'status', $this->_user_details['time'], $this->_user_details['time'], ]; return static::$_integrationModel->duplicate($integCols, $integDupData, ['form_id' => $oldFormId]); } public function deleteIntegration($integrationID) { return static::$_integrationModel->delete( [ 'id' => $integrationID, 'form_id' => $this->_formID, ] ); } public static function replaceFieldWithValue($dataToReplaceField, $fieldValues) { if (empty($dataToReplaceField)) { return false; } if (is_string($dataToReplaceField)) { $dataToReplaceField = static::replaceFieldWithValueHelper($dataToReplaceField, $fieldValues); } elseif (is_array($dataToReplaceField)) { foreach ($dataToReplaceField as $field => $value) { if (is_array($value) && 1 === count($value)) { $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value[0], $fieldValues); } elseif (is_array($value)) { $dataToReplaceField[$field] = static::replaceFieldWithValue($value, $fieldValues); } else { $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value, $fieldValues); } } } return $dataToReplaceField; } private static function replaceFieldWithValueHelper($stringToReplaceField, $fieldValues) { if (empty($stringToReplaceField)) { return $stringToReplaceField; } $fieldPattern = '/\${\w[^ ${}]*}/'; preg_match_all($fieldPattern, $stringToReplaceField, $matchedField); $uniqueFieldsInStr = array_unique($matchedField[0]); foreach ($uniqueFieldsInStr as $value) { $fieldName = substr($value, 2, strlen($value) - 3); $repeaterArr = explode('.', $fieldName); if (isset($fieldValues[$fieldName])) { $stringToReplaceField = is_string($fieldValues[$fieldName]) ? str_replace($value, $fieldValues[$fieldName], $stringToReplaceField) : wp_json_encode($fieldValues[$fieldName]); } elseif (2 === count($repeaterArr) && isset($fieldValues[$repeaterArr[0]])) { $repeaterValues = []; $repeaterFieldValues = $fieldValues[$repeaterArr[0]]; foreach ($repeaterFieldValues as $value) { if (isset($value[$repeaterArr[1]])) { $repeaterValues[] = $value[$repeaterArr[1]]; } } $stringToReplaceField = $repeaterValues; } else { $stringToReplaceField = FieldValueHandler::replaceSmartTagWithValue($value); } } return $stringToReplaceField; } public static function updatedHiddenFieldValue($formID) { $hiddenFields = []; $frontendFormManger = FrontendFormManager::getInstance($formID); if ($frontendFormManger->isHoneypotActive()) { $str = '_bitform' . '_' . $formID . '_' . time() . '_'; $honpotToken = Helpers::honeypotEncryptedToken($str); $hiddenFields[] = [ 'name' => 'b_h_t', 'value' => $honpotToken ]; } $csrfTokens = Helpers::csrfEecrypted(); $hiddenFields[] = [ 'name' => 't_identity', 'value' => $csrfTokens['t_identity'], ]; $hiddenFields[] = [ 'name' => 'csrf', 'value' => $csrfTokens['csrf'] ]; return $hiddenFields; } public static function maybeSetCronForIntegration($workFlowReturnedData, $opType, $isFormRequest = true) { $responseData = []; $entryId = $workFlowReturnedData['triggerData']['entryID']; $responseData['entry_id'] = $entryId; if (isset($workFlowReturnedData['message'])) { $formID = $workFlowReturnedData['triggerData']['formID']; $responseData['message'] = $workFlowReturnedData['message']; $responseData['hidden_fields'] = self::updatedHiddenFieldValue($formID); $responseData['msg_id'] = isset($workFlowReturnedData['msg_id']) ? $workFlowReturnedData['msg_id'] : 0; if (isset($workFlowReturnedData['msg_duration'])) { $responseData['msg_duration'] = $workFlowReturnedData['msg_duration']; } if (isset($workFlowReturnedData['afterSubmit'])) { $responseData['afterSubmit'] = $workFlowReturnedData['afterSubmit']; } } if (isset($workFlowReturnedData['redirectPage'])) { $responseData['redirectPage'] = $workFlowReturnedData['redirectPage']; } if ('update' === $opType && isset($workFlowReturnedData['updatedData'])) { $responseData['updatedData'] = $workFlowReturnedData['updatedData']; } if (isset($workFlowReturnedData['new_nonce'])) { $responseData['new_nonce'] = $workFlowReturnedData['new_nonce']; } if (!isset($workFlowReturnedData['triggerData'])) { return $responseData; } $triggerData = $workFlowReturnedData['triggerData']; $trnasientData = get_transient("bitform_trigger_transient_{$entryId}"); $trnasientData = is_string($trnasientData) ? json_decode($trnasientData) : $trnasientData; if (!empty($trnasientData['fields'])) { $fieldData = isset($workFlowReturnedData['fields']) ? $workFlowReturnedData['fields'] : $workFlowReturnedData['updatedData']; $workFlowReturnedData['fields'] = array_merge($fieldData, $trnasientData['fields']); } if (function_exists('fastcgi_finish_request') || !wp_doing_ajax()) { if (!headers_sent()) { header('Connection: close'); $contentType = wp_doing_ajax() || defined('REST_REQUEST') ? 'application/json' : 'text/html'; header('Content-Type: ' . $contentType . '; charset=' . get_option('blog_charset')); status_header(200); if (!$isFormRequest) { $response = [ 'status' => 200, 'code' => 4000, 'message' => 'Data Added Successfully!!', 'success' => true ]; } else { $response = [ 'success' => true, 'data' => $responseData, ]; } } ob_start(); echo wp_doing_ajax() || defined('REST_REQUEST') ? wp_json_encode($response) : esc_html($workFlowReturnedData['message']); ob_end_flush(); flush(); session_write_close(); if (isset($workFlowReturnedData['integrationRun']) && !$workFlowReturnedData['integrationRun']) { $entryModel = new FormEntryModel(); $updatedStatus = $entryModel->update( [ 'status' => 2, ], [ 'form_id' => $triggerData['formID'], 'id' => $triggerData['entryID'], ] ); $triggerData['fields'] = $workFlowReturnedData['fields']; if (!is_wp_error($updatedStatus)) { if ($workFlowReturnedData['dflt_template']) { // $triggerData['fields'] = $workFlowReturnedData['fields']; do_action('bitform_double_optin_confirmation', $workFlowReturnedData['integrationDetails'], $triggerData); } elseif (isset($triggerData['dblOptin'])) { foreach ($triggerData['dblOptin'] as $value) { try { MailNotifier::notify($value, $triggerData['formID'], $triggerData['fields'], $triggerData['entryID'], true, $triggerData['logID']); } catch (\Throwable $mailError) { // response already flushed — a mail failure here must not kill the rest of the run Log::debug_log('[+] Double opt-in mail failed: ' . $mailError->getMessage()); } } } } if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } if (defined('REST_REQUEST') || wp_doing_ajax()) { die; } return $workFlowReturnedData; } if (isset($triggerData['mail']) && !empty($triggerData['mail'])) { $formManager = new AdminFormManager($triggerData['formID']); $formContent = $formManager->getFormContent(); $submitted_fields = $formContent->fields; $fieldValueForMail = FieldValueHandler::formatFieldValueForMail($submitted_fields, $workFlowReturnedData['fields']); foreach ($triggerData['mail'] as $value) { try { MailNotifier::notify($value, $triggerData['formID'], $fieldValueForMail, $triggerData['entryID'], false, $triggerData['logID']); } catch (\Throwable $mailError) { // response already flushed — a mail failure here must not skip the integrations below Log::debug_log('[+] Mail notification failed: ' . $mailError->getMessage()); } } } do_action('bitforms_exec_integrations', $triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']); if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } if (defined('REST_REQUEST') || wp_doing_ajax()) { die; } return $workFlowReturnedData; } $entryID = $triggerData['entryID']; if (isset($workFlowReturnedData['cron']) && $workFlowReturnedData['cron'] && empty($triggerData['mail']) && isset($workFlowReturnedData['integrationRun']) && $workFlowReturnedData['integrationRun']) { $eventScheuled = wp_schedule_single_event(time(), 'bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]); $scheduleTime = wp_next_scheduled('bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]); $responseData['cron'] = get_site_url(null, "/wp-cron.php?doing_wp_cron&{$scheduleTime}"); } elseif (!empty($triggerData['integrations']) || !empty($triggerData['mail']) || isset($workFlowReturnedData['integrationRun']) && !$workFlowReturnedData['integrationRun']) { $triggerData['fields'] = $workFlowReturnedData['fields']; if (!$workFlowReturnedData['integrationRun'] && isset($workFlowReturnedData['integrationDetails'])) { $triggerData['dbl_opt_donf'] = $workFlowReturnedData['integrationDetails']; $triggerData['dbl_opt_dflt_template'] = $workFlowReturnedData['dflt_template']; $triggerData['integrationRun'] = $workFlowReturnedData['integrationRun']; } // Generate one-time trigger token for security $triggerToken = wp_hash($triggerData['entryID'] . $triggerData['logID'] . time() . wp_rand(), 'nonce'); $triggerData['trigger_token'] = $triggerToken; set_transient("bitform_trigger_transient_{$entryID}", $triggerData, HOUR_IN_SECONDS); $queuedAt = current_time('mysql'); $entryLog = new FormEntryLogModel(); $queueuEntry = $entryLog->log_history_insert( [ 'log_id' => $triggerData['logID'], 'integration_id' => 0, 'api_type' => wp_json_encode(['type' => 'trigger', 'type_name' => 'Workflow', 'on' => $opType]), 'response_type' => 'queued', // 'trigger' is a durable copy of the transient payload: object-cache hosts can // evict the transient (and it expires after 1h), which would otherwise lose the run 'response_obj' => wp_json_encode(['status' => 'queued', 'queued_at' => $queuedAt, 'trigger' => $triggerData]), 'created_at' => $queuedAt, ] ); if (\is_int($queueuEntry)) { // Safety net: the browser-fired bitforms_trigger_workflow request can be lost // (navigation abort, WAF, network drop). This event re-runs the queued workflow; // it no-ops if the browser trigger already claimed the run. wp_schedule_single_event( time() + 2 * MINUTE_IN_SECONDS, 'bitforms_reclaim_workflow', [(int) $entryID, (int) $triggerData['formID'], (int) $triggerData['logID'], $queueuEntry] ); if (!wp_next_scheduled('bitforms_reclaim_sweep')) { wp_schedule_event(time() + HOUR_IN_SECONDS, 'hourly', 'bitforms_reclaim_sweep'); } } $responseData['cronNotOk'] = [ $triggerData['entryID'], $triggerData['logID'], $queueuEntry, $triggerToken, // Add one-time token as 4th element ]; } return $responseData; } public static function repeaterFieldValueFormatter($fieldValues) { $formattedArray = []; foreach ($fieldValues as $key => $value) { if (is_array($value)) { foreach ($value as $index => $v) { if (is_array($v)) { foreach ($v as $k1 => $v1) { $formattedArray[$k1][] = $v1; } } else { $formattedArray[$key][] = $v; } } } else { $formattedArray[$key] = $value; } } return $formattedArray; } /** * * @param mixed $fieldValues * @param mixed $returnRepeaterValue default is array * @param mixed $formID form ID required for string formatting * @return array and repeater field value as array or string */ public static function formattedRepeaterValue($fieldValues, $returnRepeaterValue = '', $formID = null) { // get the repeater field and store it in an array $repeaterField = []; foreach ($fieldValues as $key => $value) { if (!preg_match('/\./', $key)) { continue; } $repeaterField[] = $key; } // put the value of the child key to the parent repeater field\ foreach ($repeaterField as $key) { list($parentKey, $childKey) = explode('.', $key); $repeatValues = isset($fieldValues[$parentKey]) ? $fieldValues[$parentKey] : $key; if (!is_array($repeatValues)) { $fieldValues[$key] = $fieldValues[$childKey]; continue; } foreach ($repeatValues as $value) { if (isset($value[$childKey])) { if (!isset($fieldValues[$key]) || !is_array($fieldValues[$key])) { $fieldValues[$key] = []; } if (!empty($value[$childKey])) { $fieldValues[$key][] = $value[$childKey]; } } } } // convert the array to string repeater field value if ('string' === $returnRepeaterValue) { $form = new FormManager($formID); foreach ($repeaterField as $key) { if (isset($fieldValues[$key]) && is_array($fieldValues[$key])) { $fieldValues[$key] = implode(', ', self::flattenArray($fieldValues[$key])); } } foreach ($fieldValues as $key=>$value) { if (!$form->isRepeaterField($key)) { continue; } $repeaterValues = $fieldValues[$key]; $newFieldValues = self::constructRepeaterValue($repeaterValues, $formID); $fieldValues[$key] = is_array($newFieldValues) ? json_encode(array_values($newFieldValues), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $fieldValues[$key]; } } return $fieldValues; } private static function flattenArray($array) { return array_map(function ($val) { if (is_array($val)) { return count($val) > 1 ? ('[' . implode(', ') . ']') : implode(', ', $val); } else { return $val; } }, $array); } /** * Replace file field value with web url * @param mixed $fieldValues * @param number $formId * @param number $entryId * @return mixed $fieldValues */ public static function handleFileUrl($fieldValues, $formId, $entryId) { if (!$formId || !$entryId) { return $fieldValues; } $form = FormManager::getInstance($formId); $formFields = $form->getFields(); $fldsIncludeFile = ['file-up', 'advanced-file-up', 'repeater', 'signature']; foreach ($fieldValues as $key =>$value) { $fldTyp = $formFields[$key]['type'] ?? null; if (!in_array($fldTyp, $fldsIncludeFile)) { continue; } if ('repeater' === $fldTyp && is_array($value)) { foreach ($value as $rIndex => $rValue) { foreach ($rValue as $subKey=>$subValue) { if (!in_array($formFields[$subKey]['type'], $fldsIncludeFile)) { continue; } // for file fields inside repeater if (is_array($subValue)) { foreach ($subValue as $fileIndex=>$fileValue) { $fieldValues[$key][$rIndex][$subKey][$fileIndex] = self::constructFileUrl($formId, $entryId, $fileValue); } } else { // for signature field inside repeater; $fieldValues[$key][$rIndex][$subKey] = self::constructFileUrl($formId, $entryId, $subValue); } } } } if (in_array($fldTyp, ['file-up', 'advanced-file-up', 'signature'], true)) { if (is_array($value)) { foreach ($value as $fldIndex=>$fldValue) { $fieldValues[$key][$fldIndex] = self::constructFileUrl($formId, $entryId, $fldValue); } } else { $fieldValues[$key] = self::constructFileUrl($formId, $entryId, $value); } } } return $fieldValues; } public static function assignRepeaterFieldValue($fieldValues, $formId) { if (!$formId) { return $fieldValues; } $form = new FormManager($formId); $formFields = $form->getFields(); foreach ($fieldValues as $key=>$value) { if (!$form->isRepeaterField($key)) { continue; } if ('repeater' === $formFields[$key]['type']) { if (is_array($value)) { foreach ($value as $rIndex=>$rValue) { foreach ($rValue as $subKey =>$subValue) { $fieldValues[$subKey] = $subValue; } } } } } return $fieldValues; } public static function constructRepeaterValue($repeaterValues, $formId) { if (!$formId || !is_array($repeaterValues)) { return $repeaterValues; } $form = FormManager::getInstance($formId); $formFields = $form->getFields(); $newRptrValue = []; foreach ($repeaterValues as $key=>$value) { $newRepeatedValue = []; foreach ($value as $rKey =>$rValue) { $fldLbl = isset($formFields[$rKey]) ? $formFields[$rKey]['label'] : $rKey; $newRepeatedValue[$fldLbl] = $rValue; } $newRptrValue[$key] = $newRepeatedValue; } return $newRptrValue; } private static function constructFileUrl($formId, $entryId, $fileName) { $webPath = Helpers::getWebPathWithEncryptedEntryId($formId, $entryId); return rtrim($webPath, '/') . '/' . ltrim($fileName, '/'); } // private static function constructFileUrl($formId, $entryId, $fileName) // { // $restBaseUrl = get_rest_url(); // if (!$restBaseUrl) { // return $fileName; // } // //http://bitlocal.local/wp-json/bitform/v1/bitform-file-download/?formID=1&entryID=16&fileID=bit-form-1005.pdf // $queryParam = build_query([ // 'formID' => $formId, // 'entryID' => $entryId, // 'fileID' => $fileName // ]); // $path = $restBaseUrl . 'bitform' . '/' . 'v1' . '/' . 'bitform-file-download' . '/' . '?' . $queryParam; // return rtrim($path, '/'); // } }