PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V_3.0.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV_3.0.0
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
bit-form / includes / Frontend / Ajax / FrontendAjax.php

FrontendAjax.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder V_3.0.0, at includes/Frontend/Ajax/FrontendAjax.php

351 lines 14.3 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\Ajax;
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 use BitCode\BitForm\Admin\Form\AdminFormManager;
10 use BitCode\BitForm\Admin\Form\Helpers;
11 use BitCode\BitForm\Core\Database\FormEntryLogModel;
12 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
13 use BitCode\BitForm\Core\Database\FormEntryModel;
14 use BitCode\BitForm\Core\Util\FieldValueHandler;
15 use BitCode\BitForm\Core\Util\FrontendHelpers;
16 use BitCode\BitForm\Core\Util\Log;
17 use BitCode\BitForm\Core\Util\MailNotifier;
18 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
19 use BitCode\BitForm\Frontend\Form\FrontendFormManager;
20 use WP_Error;
21
22 final class FrontendAjax
23 {
24 public function register()
25 {
26 add_action('wp_ajax_nopriv_bitforms_submit_form', [$this, 'submit_form']);
27 add_action('wp_ajax_bitforms_submit_form', [$this, 'submit_form']);
28 add_action('wp_ajax_bitforms_entry_update', [$this, 'update_entry']);
29 add_action('wp_ajax_nopriv_bitforms_entry_update', [$this, 'update_entry']);
30 add_action('wp_ajax_bitforms_update_form_entry', [$this, 'update_entry']);
31 add_action('wp_ajax_nopriv_bitforms_update_form_entry', [$this, 'update_entry']);
32 add_action('wp_ajax_bitforms_before_submit_validate', [$this, 'beforeSubmittedValidate']);
33 add_action('wp_ajax_nopriv_bitforms_before_submit_validate', [$this, 'beforeSubmittedValidate']);
34 add_action('wp_ajax_nopriv_bitforms_trigger_workflow', [$this, 'triggerWorkFlow']);
35 add_action('wp_ajax_bitforms_trigger_workflow', [$this, 'triggerWorkFlow']);
36 add_action('wp_ajax_bitforms_onload_added_field_and_property', [$this, 'addHiddenFieldAndProperty']);
37 add_action('wp_ajax_nopriv_bitforms_onload_added_field_and_property', [$this, 'addHiddenFieldAndProperty']);
38 }
39
40 public function beforeSubmittedValidate()
41 {
42 // CSRF verified inside FrontendFormManager::handleSubmission() via verifySubmissionNonce() using HMAC-SHA256 token (Helpers::csrfDecrypted).
43 $form_id = isset($_POST['bitforms_id']) ? str_replace('bitforms_', '', sanitize_text_field(wp_unslash($_POST['bitforms_id']))) : '';
44 if (empty($form_id)) {
45 wp_send_json_error(__('Form ID not found', 'bit-form'), 400);
46 }
47 $FrontendFormManager = FrontendFormManager::getInstance($form_id);
48 $FrontendFormManager->fieldNameReplaceOfPost();
49 $validateStatus = $FrontendFormManager->beforeSubmittedValidate(false);
50 if (is_wp_error($validateStatus)) {
51 wp_send_json_error($validateStatus->get_error_message(), 400);
52 } else {
53 wp_send_json_success($validateStatus);
54 }
55 }
56
57 public function submit_form()
58 {
59 \ignore_user_abort();
60 // CSRF verified inside FrontendFormManager::handleSubmission() via verifySubmissionNonce() using HMAC-SHA256 token (Helpers::csrfDecrypted).
61 $form_id = isset($_POST['bitforms_id']) ? str_replace('bitforms_', '', sanitize_text_field(wp_unslash($_POST['bitforms_id']))) : '';
62 $FrontendFormManager = FrontendFormManager::getInstance($form_id);
63 $submitSatus = $FrontendFormManager->handleSubmission();
64 if (is_wp_error($submitSatus)) {
65 do_action('bitform_submit_error', $form_id, $submitSatus);
66 wp_send_json_error($submitSatus->get_error_message(), 400);
67 } elseif (true !== $submitSatus && !\is_array($submitSatus)) {
68 // Validation failed (e.g. OTP) — $submitSatus is the error message string
69 do_action('bitform_submit_error', $form_id, $submitSatus);
70 wp_send_json_error(\is_string($submitSatus) ? $submitSatus : __('Validation failed.', 'bit-form'), 400);
71 } else {
72 wp_send_json_success($submitSatus);
73 }
74 }
75
76 public function update_entry()
77 {
78 \ignore_user_abort();
79 // Entry token validated via Helpers::validateEntryTokenAndUser() or capability check; CSRF covered by HMAC-SHA256 token (Helpers::csrfDecrypted).
80 $form_id = isset($_POST['bitforms_id']) ? str_replace('bitforms_', '', sanitize_text_field(wp_unslash($_POST['bitforms_id']))) : '';
81 if (empty($form_id)) {
82 wp_send_json_error(__('Form ID not found', 'bit-form'), 400);
83 }
84 $entryId = isset($_REQUEST['entryID']) ? sanitize_text_field(wp_unslash($_REQUEST['entryID'])) : '';
85 $entryToken = isset($_REQUEST['entryToken']) ? sanitize_text_field(wp_unslash($_REQUEST['entryToken'])) : '';
86 $GLOBALS['bitform_entry_id'] = $entryId;
87 if (Helpers::validateEntryTokenAndUser($entryToken, $entryId) || FrontendHelpers::is_current_user_can_access($form_id, 'entryEditAccess')) {
88 $FrontendFormManager = FrontendFormManager::getInstance($form_id);
89 $updateStatus = $FrontendFormManager->handleUpdateEntry();
90 if (is_wp_error($updateStatus)) {
91 do_action('bitform_update_error', $form_id, $updateStatus);
92 wp_send_json_error($updateStatus->get_error_message(), 400);
93 } elseif (true !== $updateStatus && !\is_array($updateStatus)) {
94 // Validation failed (e.g. OTP) — $updateStatus is the error message string
95 do_action('bitform_update_error', $form_id, $updateStatus);
96 wp_send_json_error(\is_string($updateStatus) ? $updateStatus : __('Validation failed.', 'bit-form'), 400);
97 } else {
98 wp_send_json_success($updateStatus);
99 }
100 } else {
101 wp_send_json_error('Entry Token or User is not Authorized', 401);
102 }
103 }
104
105 public function hiddenFields($formId)
106 {
107 $tokens = Helpers::csrfEecrypted();
108 $fields = [
109 [
110 'name' => 'csrf',
111 'value' => $tokens['csrf'],
112 ],
113 [
114 'name' => 't_identity',
115 'value' => $tokens['t_identity'],
116 ]
117 ];
118 $frontendFormManger = FrontendFormManager::getInstance($formId);
119 if ($frontendFormManger->isHoneypotActive()) {
120 $time = time();
121 $honeypodFldName = Helpers::honeypotEncryptedToken("_bitforms_{$formId}_{$time}_");
122 $fields[] = [
123 'name' => 'b_h_t',
124 'value' => $honeypodFldName,
125 ];
126 }
127 return $fields;
128 }
129
130 public function hiddenPropeties($formId)
131 {
132 $properties = [];
133 $properties[] = [
134 'name' => 'nonce',
135 'value' => wp_create_nonce('bitforms_' . $formId),
136 ];
137 return $properties;
138 }
139
140 public function addHiddenFieldAndProperty()
141 {
142 \ignore_user_abort();
143 $rawInput = file_get_contents('php://input');
144 if ($rawInput) {
145 $request = is_string($rawInput) ? sanitize_text_field($rawInput) : $rawInput;
146 $data = is_string($request) ? \json_decode($request) : $request;
147 if (!isset($data->formId)) {
148 wp_send_json_error('Form Id not found', 400);
149 } else {
150 $formId = absint($data->formId);
151 $fields = $this->hiddenFields($formId);
152 $properties = $this->hiddenPropeties($formId);
153 wp_send_json_success(['hidden_fields'=>$fields, 'hidden_properties'=>$properties]);
154 }
155 }
156 }
157
158 public function triggerWorkFlow()
159 {
160 \ignore_user_abort(true);
161
162 $rawInput = file_get_contents('php://input');
163
164 if ($rawInput) {
165 $inputJSON = is_string($rawInput) ? sanitize_text_field($rawInput) : $rawInput;
166 $request = is_string($inputJSON) ? \json_decode($inputJSON) : $inputJSON;
167 $submitted_fields = [];
168 if (isset($request->id, $request->cronNotOk)) {
169 $formID = absint(str_replace('bitforms_', '', sanitize_text_field($request->id)));
170 $cronNotOk = $request->cronNotOk;
171
172 // Validate and sanitize entry ID and log ID
173 if (!isset($cronNotOk[0]) || !is_numeric($cronNotOk[0]) || !isset($cronNotOk[1]) || !is_numeric($cronNotOk[1])) {
174 Log::debug_log('Invalid cronNotOk data for formID=' . $formID);
175 wp_send_json_error(['message' => 'Invalid request data'], 400);
176 }
177
178 $entryID = absint($cronNotOk[0]);
179 $logID = absint($cronNotOk[1]);
180 $GLOBALS['bitform_entry_id'] = $entryID;
181 $entryLog = new FormEntryLogModel();
182
183 // Quick admin check to allow retry of workflows
184 $isAdmin = false;
185 if (is_user_logged_in()) {
186 $user = wp_get_current_user();
187 $isAdmin = in_array('administrator', $user->roles) || current_user_can('manage_bitform');
188 }
189
190 // Check if already processed (skip for administrators to allow retries)
191 if (!$isAdmin) {
192 if (isset($cronNotOk[2]) && \is_int($cronNotOk[2])) {
193 $queueudEntry = $entryLog->get(
194 'response_obj',
195 ['id' => $cronNotOk[2]]
196 );
197 if ($queueudEntry) {
198 if (!empty($queueudEntry[0]->response_obj) && \strpos($queueudEntry[0]->response_obj, 'processed') > 0) {
199 Log::debug_log('Cron Not Ok[2] Already Processed');
200 wp_send_json_error();
201 }
202 } else {
203 Log::debug_log('Cron Not Ok[2] Query Entry data not found');
204 wp_send_json_error();
205 }
206 } else {
207 Log::debug_log('Cron Not Ok[2](Log Id) data not found');
208 wp_send_json_error();
209 }
210 } else {
211 Log::debug_log('Admin bypass: Skipping "already processed" check for workflow retry');
212 }
213
214 // SECURITY CHECK: Validate trigger token using helper function
215 $validation = Helpers::validateWorkflowTriggerToken($request, $formID);
216
217 if (!$validation['valid']) {
218 wp_send_json_error(['message' => $validation['error']], 403);
219 }
220
221 // Use validated trigger data if available (prevents transient overwrite bug)
222 $triggerData = null;
223 if ($validation['triggerData']) {
224 // Token was valid and transient data retrieved
225 $triggerData = $validation['triggerData'];
226 } else {
227 // Admin bypass or transient not found - fetch from transient/database
228 $trnasientData = get_transient("bitform_trigger_transient_{$entryID}");
229
230 if (!empty($trnasientData)) {
231 delete_transient("bitform_trigger_transient_{$entryID}");
232 $triggerData = is_string($trnasientData) ? json_decode($trnasientData) : $trnasientData;
233 } else {
234 $formManager = new AdminFormManager($formID);
235 if (!$formManager->isExist()) {
236 Log::debug_log('provided form does not exists');
237 return wp_send_json(new WP_Error('trigger_empty_form', __('provided form does not exists', 'bit-form')));
238 }
239 $formEntryModel = new FormEntryModel();
240 $entryMeta = new FormEntryMetaModel();
241
242 $formEntry = $formEntryModel->get(
243 '*',
244 [
245 'form_id' => $formID,
246 'id' => $entryID,
247 ]
248 );
249
250 if (!$formEntry) {
251 Log::debug_log('provided form entries does not exists. EntryId=' . $entryID . ', FormId=' . $formID);
252 return new WP_Error('trigger_empty_form', __('provided form entries does not exists', 'bit-form'));
253 }
254 $formEntryMeta = $entryMeta->get(
255 [
256 'meta_key',
257 'meta_value',
258 ],
259 [
260 'bitforms_form_entry_id' => $entryID,
261 ]
262 );
263 $entries = [];
264 foreach ($formEntryMeta as $key => $value) {
265 $entries[$value->meta_key] = $value->meta_value;
266 }
267 $formContent = $formManager->getFormContent();
268 $submitted_fields = $formContent->fields;
269 foreach ($submitted_fields as $key => $value) {
270 if (isset($entries[$key])) {
271 $submitted_fields->{$key}->val = $entries[$key];
272 $submitted_fields->{$key}->name = $key;
273 }
274 }
275
276 $workFlowRunHelper = new WorkFlow($formID);
277 $workFlowreturnedOnSubmit = $workFlowRunHelper->executeOnSubmit(
278 'create',
279 $submitted_fields,
280 $entries,
281 $entryID,
282 $logID
283 );
284
285 $triggerData = isset($workFlowreturnedOnSubmit['triggerData']) ? $workFlowreturnedOnSubmit['triggerData'] : null;
286 $triggerData['fields'] = $entries;
287 }
288 } // Close else block
289
290 if (!empty($triggerData)) {
291 if (isset($triggerData['integrationRun']) && !$triggerData['integrationRun']) {
292 $entryModel = new FormEntryModel();
293 $updatedStatus = $entryModel->update(
294 [
295 'status' => 2,
296 ],
297 [
298 'form_id' => $triggerData['formID'],
299 'id' => $entryID,
300 ]
301 );
302 if (is_wp_error($updatedStatus)) {
303 wp_send_json_error($updatedStatus->get_error_message(), 411);
304 } else {
305 if ($triggerData['dbl_opt_dflt_template']) {
306 do_action('bitform_double_optin_confirmation', $triggerData['dbl_opt_donf'], $triggerData);
307 } elseif (isset($triggerData['dblOptin'])) {
308 foreach ($triggerData['dblOptin'] as $value) {
309 MailNotifier::notify($value, $triggerData['formID'], $triggerData['fields'], $entryID, true, $logID);
310 }
311 }
312 wp_send_json_success();
313 }
314 }
315
316 if (isset($triggerData['mail'])) {
317 $formManager = new AdminFormManager($formID);
318 $formContent = $formManager->getFormContent();
319 $submitted_fields = $formContent->fields;
320 $fieldValueForMail = FieldValueHandler::formatFieldValueForMail($submitted_fields, $triggerData['fields']);
321 foreach ($triggerData['mail'] as $value) {
322 MailNotifier::notify($value, $triggerData['formID'], $fieldValueForMail, $entryID);
323 }
324 }
325
326 do_action('bitforms_exec_integrations', $triggerData['integrations'], $triggerData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']);
327 if (isset($cronNotOk[2]) && \is_int($cronNotOk[2])) {
328 $queueuEntry = $entryLog->update(
329 [
330 'response_type' => 'success',
331 'response_obj' => wp_json_encode(['status' => 'processed']),
332 ],
333 ['id' => $cronNotOk[2]]
334 );
335 }
336 } else {
337 Log::debug_log('No Trigger Data Found');
338 }
339 } else {
340 Log::debug_log('Cron Not Ok data not found');
341 wp_send_json_error('Cron Not Ok data found', 400);
342 }
343 } else {
344 Log::debug_log('No Input data found');
345 wp_send_json_error('Invalid Request', 400);
346 }
347
348 wp_send_json_success();
349 }
350 }
351