PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.4
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.4
V3.3.2 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 All 139 releases
bit-form / includes / Frontend / Ajax / FrontendAjax.php

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

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