PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.0
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 2.10.2 All 137 releases
bit-form / includes / Frontend / Form / FrontendFormManager.php

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

960 lines 36.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Get set Form,fields
5 */
6
7 namespace BitCode\BitForm\Frontend\Form;
8
9 /**
10 * FrontendFormManager class
11 */
12
13 use BitCode\BitForm\Admin\Form\AdminFormHandler;
14 use BitCode\BitForm\Admin\Form\Helpers;
15 use BitCode\BitForm\Core\Database\FormEntryModel;
16 use BitCode\BitForm\Core\Form\FormManager;
17 use BitCode\BitForm\Core\Form\Validator\FormFieldValidator;
18 use BitCode\BitForm\Core\Integration\IntegrationHandler;
19 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
20 use BitCode\BitForm\Core\Util\ApiResponse as UtilApiResponse;
21 use BitCode\BitForm\Core\Util\HttpHelper;
22 use BitCode\BitForm\Core\Util\IpTool;
23 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
24 use BitCode\BitForm\Core\WorkFlow\WorkFlowHandler;
25 use BitCode\BitForm\Frontend\Form\View\FormViewer;
26 use BitCode\BitForm\GlobalHelper;
27 use WP_Error;
28
29 final class FrontendFormManager extends FormManager
30 {
31 private $_form_identifier;
32 private $_form_token;
33 private $_form_id;
34 private $_work_flows;
35 private $_conf_messages;
36 private static $_instance = [];
37
38 // private $_has_upload = false;
39 public function __construct($form_id, $shortCodeCounter = null)
40 {
41 parent::__construct($form_id);
42 $this->_form_identifier = 'bitforms_' . $form_id;
43 $this->_form_identifier .= !empty(get_post()->ID) ? '_' . get_post()->ID : '';
44 $this->_form_identifier .= !empty($shortCodeCounter) ? "_$shortCodeCounter" : '';
45 $this->_form_token = wp_create_nonce('bitforms_' . $form_id);
46 $this->_form_id = $form_id;
47 }
48
49 public static function getInstance($form_id, $shortCodeCounter = null)
50 {
51 $key = $form_id . ':' . ($shortCodeCounter ?? 'default');
52
53 if (!isset(self::$_instance[$key])) {
54 self::$_instance[$key] = new self($form_id, $shortCodeCounter);
55 }
56
57 return self::$_instance[$key];
58 }
59
60 public function getFormIdentifier()
61 {
62 return $this->_form_identifier;
63 }
64
65 public function getFormID()
66 {
67 return $this->_form_id;
68 }
69
70 public function getFormToken()
71 {
72 return $this->_form_token;
73 }
74
75 public function getSubmittedFields($submitted_data)
76 {
77 unset($submitted_data[$this->_form_identifier]);
78 // unset($submitted_data['bit-form-submit-btn']);
79 return array_keys($submitted_data);
80 }
81
82 public function formView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
83 {
84 $formContents = $this->getFormContent();
85 $formAtomicClsMap = $this->getAtomicClsMap();
86 if (!empty($fields)) {
87 $formContents->fields = is_string($fields) ? json_decode($fields) : $fields;
88 } else {
89 $workFlowRunHelper = new WorkFlow($this->form_id);
90 $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
91 'create',
92 $formContents->fields
93 );
94 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
95 }
96 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
97 $isRestricted = $this->checkSubmissionRestriction(false);
98 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
99 return $formViewer->getView($hasFile, $msg);
100 }
101
102 public function conversationalFormView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
103 {
104 $formContents = $this->getFormContent();
105 $formAtomicClsMap = $this->getAtomicClsMap();
106 if (!empty($fields)) {
107 $formContents->fields = is_string($fields) ? json_decode($fields) : $fields;
108 } else {
109 $workFlowRunHelper = new WorkFlow($this->form_id);
110 $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
111 'create',
112 $formContents->fields
113 );
114 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
115 }
116 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
117 $isRestricted = $this->checkSubmissionRestriction(false);
118 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
119 return $formViewer->getConversationalView($hasFile, $msg);
120 }
121
122 public function checkEmptySubmission($data, $file)
123 {
124 $formFields = $this->getFields();
125 foreach ($formFields as $key => $field) {
126 $fieldType = $field['type'];
127 if ('button' === $fieldType) {
128 continue;
129 }
130 $fileUploadFieldTypes = ['file-up', 'advanced-file-up'];
131 if ('decision-box' === $fieldType || 'gdpr' === $fieldType) {
132 continue;
133 }
134 $isFileType = in_array($fieldType, $fileUploadFieldTypes);
135 if ($this->isRepeatedField($key)) {
136 $fileData = !empty($file[$key]) ? $file[$key] : [];
137 $dataVal = !empty($data[$key]) ? $data[$key] : [];
138 if (!$this->checkRepeatedFieldEmptySubmission($isFileType, $dataVal, $fileData)) {
139 return false;
140 }
141 continue;
142 }
143 if (!$isFileType && (!empty($data[$key]) || (isset($data[$key]) && is_numeric($data[$key])))) {
144 return false;
145 }
146 if ($isFileType && !empty($file[$key]['name']) && is_string($file[$key]['name'])) {
147 return false;
148 }
149 if ($isFileType && !empty($file[$key]['name'][0])) {
150 return false;
151 }
152 }
153 return true;
154 }
155
156 private function checkRepeatedFieldEmptySubmission($isFileType, $data, $file = [])
157 {
158 if (!$isFileType) {
159 foreach ($data as $value) {
160 if (!empty($value)) {
161 return false;
162 }
163 }
164 }
165 if ($isFileType) {
166 foreach ($file['name'] as $value) {
167 if (!empty($value) && is_string($value)) {
168 return false;
169 }
170 if (is_array($value) && !empty($value[0])) {
171 return false;
172 }
173 }
174 }
175 return true;
176 }
177
178 private function getParams()
179 {
180 $url = wp_parse_url(wp_get_referer());
181 $parameter = [];
182 if (isset($url['query'])) {
183 $queries = explode('&', $url['query']);
184 foreach ($queries as $query) {
185 list($field, $value) = explode('=', $query);
186 $parameter[$field] = $value;
187 }
188 }
189 return $parameter;
190 }
191
192 private function getFormFields($formID)
193 {
194 $adminFormHandler = new AdminFormHandler();
195 $post = new \stdClass();
196 $post = (object) [
197 'id' => $formID
198 ];
199 $getForm = $adminFormHandler->getAForm('', $post);
200 $formContainer = $getForm['form_content'];
201
202 return $formContainer['fields'];
203 }
204
205 private function transformDrpdwnValue($post)
206 {
207 $formFields = $this->getFormFields($this->_form_id);
208
209 foreach ($post as $key => $value) {
210 if (!str_starts_with($key, 'repeater') && 'select' === $formFields->{$key}->typ) {
211 if (is_array($value)) {
212 foreach ($value as $k => $v) {
213 $post[$key][$k] = !is_array($v) && is_string($v) ? explode(BITFORMS_BF_SEPARATOR, $v) : $v;
214 }
215 } else {
216 $post[$key] = explode(BITFORMS_BF_SEPARATOR, $value);
217 }
218 };
219 }
220
221 return $post;
222 }
223
224 public function handleSubmission()
225 {
226 // CSRF verified via verifySubmissionNonce() before this method is called. All $_POST reads below occur after that verification.
227 $this->fieldNameReplaceOfPost();
228
229 $validated = $this->beforeSubmittedValidate();
230
231 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
232
233 if (true === $validated) {
234 do_action('bitform_validation_success', $this->_form_id);
235 unset($_POST['hidden_fields']);
236
237 $redirectPage = '';
238 $regSuccMsg = '';
239
240 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
241 $unslashed_post = wp_unslash($_POST);
242 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
243 $parameter = $this->getParams();
244 $existAuthFilter = has_filter('bitform_wp_user_auth');
245
246 if (true === $existAuthFilter) {
247 $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $unslashed_post, $parameter);
248
249 $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $this->_form_id, $unslashed_post, $parameter);
250
251 do_action('bitform_wp_user_auth_response', $result, $this->_form_id, $unslashed_post, $parameter);
252
253 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
254 if (!$result['success']) {
255 return new WP_Error('errors', esc_html($result['message']));
256 } elseif (isset($result['success'])) {
257 $redirectPage = $result['redirectPage'];
258 $regSuccMsg = $result['message'];
259 }
260 } else {
261 if (!$result['success']) {
262 return new WP_Error('errors', esc_html($result['message']));
263 } else {
264 return $result;
265 }
266 }
267 }
268 }
269
270 $saveResponse = $this->saveFormEntry($unslashed_post);
271 if (is_wp_error($saveResponse)) {
272 return $saveResponse;
273 }
274
275 $entryID = $saveResponse['entry_id'];
276
277 // transformed dropdown value from string to array
278 $newPost = $this->transformDrpdwnValue($unslashed_post);
279 $filesData = GlobalHelper::sanitize_files_input($_FILES);
280 do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $filesData);
281
282 $captchaV3Settings = $this->getCaptchaV3Settings();
283 if ($captchaV3Settings) {
284 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
285 $integrationHandler = new IntegrationHandler(0);
286 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
287 if (!is_wp_error($allFormIntegrations)) {
288 foreach ($allFormIntegrations as $integration) {
289 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
290 $integrationDetails = json_decode($integration->integration_details);
291 $integrationDetails->id = $integration->id;
292 $reCAPTCHA = $integrationDetails;
293 }
294 }
295 }
296 if (!empty($reCAPTCHA->secretKey)) {
297 $gRecaptchaResponse = HttpHelper::post(
298 'https://www.google.com/recaptcha/api/siteverify',
299 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
300 );
301 if ($captchaV3Settings && !empty($saveResponse['triggerData'])) {
302 $logID = $saveResponse['triggerData']['logID'];
303 $integId = $reCAPTCHA->id;
304 $saveApiResponse = new UtilApiResponse();
305 $saveApiResponse->apiResponse($logID, $integId, ['type_name' => 'ReCaptcha', 'type' => 'v3'], 'success', $gRecaptchaResponse);
306 }
307 }
308 unset($_POST['g-recaptcha-response']);
309 }
310 if (!empty($redirectPage) && empty($saveResponse['redirectPage']) || null === $saveResponse['redirectPage']) {
311 $saveResponse['redirectPage'] = $redirectPage;
312 }
313 if (!empty($regSuccMsg) && isset($saveResponse['dflt_message'])) {
314 $saveResponse['message'] = $regSuccMsg;
315 }
316 $saveResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
317
318 $saveResponse = IntegrationHandler::maybeSetCronForIntegration($saveResponse, 'create');
319 $entryId = $saveResponse['entry_id'];
320
321 $responseMsg = is_array($saveResponse) && !empty($saveResponse) ? $saveResponse : __('Form Submitted Successfully', 'bit-form');
322 $_POST = [];
323 $responseMsg['entry_id'] = $entryId;
324 return $responseMsg;
325 }
326 do_action('bitform_validation_error', $this->_form_id, $validated);
327 return $validated;
328 }
329
330 public function handleUpdateEntry()
331 {
332 // Entry token or capability verified by caller (FrontendAjax::update_entry). All $_POST reads occur after that check.
333 $this->fieldNameReplaceOfPost();
334 $validated = $this->beforeSubmittedValidate();
335 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
336
337 $entryID = isset($_REQUEST['entryID']) ? sanitize_text_field(wp_unslash($_REQUEST['entryID'])) : null;
338 $GLOBALS['bitform_entry_id'] = $entryID;
339 if (is_null($entryID)) {
340 return new WP_Error('empty_form', __('Entries id is invalid', 'bit-form'));
341 }
342 if (true === $validated) {
343 do_action('bitform_validation_success', $this->_form_id);
344 unset($_POST['hidden_fields'], $_POST['entryID']);
345
346 $redirectPage = '';
347 $regSuccMsg = '';
348 $postData = wp_unslash($_POST);
349
350 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
351 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
352 $parameter = $this->getParams();
353 $existAuthFilter = has_filter('bitform_wp_user_auth');
354
355 if (true === $existAuthFilter) {
356 $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $postData, $parameter);
357
358 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
359 if (!$result['success']) {
360 return new WP_Error('errors', esc_html($result['message']));
361 } elseif (isset($result['success'])) {
362 $redirectPage = $result['redirectPage'];
363 $regSuccMsg = $result['message'];
364 }
365 } else {
366 if (!$result['success']) {
367 return new WP_Error('errors', esc_html($result['message']));
368 } else {
369 return $result;
370 }
371 }
372 }
373 }
374
375 $updateResponse = $this->updateFormEntry(wp_unslash($_POST), $this->getFormID(), $entryID);
376 if (is_wp_error($updateResponse)) {
377 return $updateResponse;
378 }
379
380 // transformed dropdown value from string to array
381 $newPost = $this->transformDrpdwnValue($postData);
382 $filesData = GlobalHelper::sanitize_files_input($_FILES);
383
384 //TO DO:: submit success action temporarily added for solution of a issue
385 do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $filesData);
386 do_action('bitform_update_success', $this->_form_id, $entryID, $newPost, $filesData);
387
388 $captchaV3Settings = $this->getCaptchaV3Settings();
389 if ($captchaV3Settings) {
390 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
391 $integrationHandler = new IntegrationHandler(0);
392 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
393 if (!is_wp_error($allFormIntegrations)) {
394 foreach ($allFormIntegrations as $integration) {
395 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
396 $integrationDetails = json_decode($integration->integration_details);
397 $integrationDetails->id = $integration->id;
398 $reCAPTCHA = $integrationDetails;
399 }
400 }
401 }
402 if (!empty($reCAPTCHA->secretKey)) {
403 $gRecaptchaResponse = HttpHelper::post(
404 'https://www.google.com/recaptcha/api/siteverify',
405 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
406 );
407 if ($captchaV3Settings && !empty($updateResponse['triggerData'])) {
408 $logID = $updateResponse['triggerData']['logID'];
409 $integId = $reCAPTCHA->id;
410 $saveApiResponse = new UtilApiResponse();
411 $saveApiResponse->apiResponse($logID, $integId, ['type_name' => 'ReCaptcha', 'type' => 'v3'], 'success', $gRecaptchaResponse);
412 }
413 }
414 unset($_POST['g-recaptcha-response']);
415 }
416 if (!empty($redirectPage) && empty($updateResponse['redirectPage']) || null === $updateResponse['redirectPage']) {
417 $updateResponse['redirectPage'] = $redirectPage;
418 }
419 if (!empty($regSuccMsg) && isset($updateResponse['dflt_message'])) {
420 $updateResponse['message'] = $regSuccMsg;
421 }
422 $updateResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
423 $updateResponse = IntegrationHandler::maybeSetCronForIntegration($updateResponse, 'update');
424 $entryId = $updateResponse['entry_id'];
425
426 $responseMsg = is_array($updateResponse) && !empty($updateResponse) ? $updateResponse : __('Entry Update Successfully', 'bit-form');
427
428 $_POST = [];
429 $responseMsg['entry_id'] = $entryId;
430 return $responseMsg;
431 }
432 do_action('bitform_validation_error', $this->_form_id, $validated);
433 return $validated;
434 }
435
436 public function validateFormSubmission($submitted_data)
437 {
438 $hidden_fields = isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '';
439 $submitted_fields = $this->getSubmittedFields($submitted_data);
440 $form_fields = $this->getFields();
441 $form_fields_names = array_keys($form_fields);
442 if ($this->isGCLIDEnabled()) {
443 array_push($form_fields_names, 'GCLID');
444 }
445 foreach ($submitted_fields as $field) {
446 if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || false !== strpos($hidden_fields, $field)) {
447 unset($submitted_data[$field]);
448 }
449 }
450 return $submitted_data;
451 }
452
453 public function beforeSubmittedValidate($verifyCaptcha = true)
454 {
455 if ($this->verifySubmissionNonce()) {
456 if ($this->isExist()) {
457 $isRestricted = $this->checkSubmissionRestriction();
458 if ($isRestricted && !empty($isRestricted)) {
459 return new WP_Error('spam_detection', $isRestricted[0]);
460 }
461 $postData = wp_unslash($_POST);
462 $filesData = GlobalHelper::sanitize_files_input($_FILES);
463 $isHoneypot = apply_filters('bitform_check_honeypot', false, $this->_form_id, $postData);
464 if ($isHoneypot) {
465 return new WP_Error('spam_detection', __('Token verification failed', 'bit-form'));
466 }
467 $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
468 // TODO: Temporary parameter to skip captcha verification in step change of multi step form
469 if ($verifyCaptcha) {
470 $verifyGRecaptchaResult = $this->verifyGRecaptcha();
471 if (is_wp_error($verifyGRecaptchaResult)) {
472 return $verifyGRecaptchaResult;
473 }
474 $verifyHCaptchaResult = $this->verifyHCaptcha();
475 if (is_wp_error($verifyHCaptchaResult)) {
476 return $verifyHCaptchaResult;
477 }
478 /* Implement Turnstile Captcha start */
479 $verifyTurnstileCaptchaResult = $this->verifyTurnstileCaptcha();
480 if (is_wp_error($verifyTurnstileCaptchaResult)) {
481 return $verifyTurnstileCaptchaResult;
482 }
483 }
484 /* Implement Turnstile Captcha end */
485
486 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
487
488 // check if user is already logged in and form has auth integration
489 do_action('bitform_checked_exist_auth', $this->_form_id, $existAuth);
490 if (!is_wp_error($existAuth) && count($existAuth) > 0 && is_user_logged_in()) {
491 return new WP_Error('auth_error', __('You are already logged in', 'bit-form'));
492 }
493 $validateForm = $this->validateFormSubmission($postData);
494 $validateFormFiles = $this->validateFormSubmission($filesData);
495 $validateForm = array_merge($validateForm, $validateFormFiles);
496 $form_fields = $this->getFields();
497 // check if form-current-step is set and form is multi-step
498 $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
499 if (!is_null($formCurrentStep)) {
500 $formContents = $this->getFormContent();
501 $layout = $formContents->layout;
502 $stepIndex = (int) $formCurrentStep - 1;
503 $stepLayout = $layout[$stepIndex]->layout->lg;
504 $nestedLayout = $formContents->nestedLayout;
505 $step_fields = [];
506 foreach ($stepLayout as $lay) {
507 $fk = $lay->i;
508 if (isset($nestedLayout->{$fk})) {
509 $nestedLg = $nestedLayout->{$fk}->lg;
510 foreach ($nestedLg as $nestedLay) {
511 $nestedFk = $nestedLay->i;
512 $step_fields[$nestedFk] = $form_fields[$nestedFk];
513 }
514 }
515 $step_fields[$fk] = $form_fields[$fk];
516 }
517 $form_fields = $step_fields;
518 }
519 $formFieldValidator = new FormFieldValidator($form_fields, $postData, $filesData);
520 $validUniuqFields = [];
521 $existFilter = has_filter('bitform_check_duplicate_entry');
522 if (true === $existFilter) {
523 $validUniuqFields = apply_filters('bitform_check_duplicate_entry', $form_fields, $postData);
524
525 $fieldKeys = array_keys($validUniuqFields);
526 $form_fields_keys = array_keys($form_fields);
527 $uniqueFields = [];
528 foreach ($fieldKeys as $key) {
529 if (in_array($key, $form_fields_keys)) {
530 $uniqueFields[] = $form_fields[$key];
531 }
532 }
533 do_action('bitform_Unique_entry', $uniqueFields, $validUniuqFields, $this->_form_id, $postData);
534 }
535 $validateField = $formFieldValidator->validate('create', $this->_form_id);
536
537 if ($validateForm && $validateField && 0 === count($validUniuqFields)) {
538 return true;
539 } else {
540 $error = __('Please submit form with valid fields', 'bit-form');
541 if (!$validateForm) {
542 $errorMessages = $error;
543 } elseif (count($formFieldValidator->getMessage()) > 0) {
544 $errorMessages = $formFieldValidator->getMessage();
545 } else {
546 $errorMessages = 0 === count($validUniuqFields) ? $error : $validUniuqFields;
547 }
548 return new WP_Error('validation_error', $errorMessages);
549 }
550 }
551 return new WP_Error('unknown_form', __('Form does not exist', 'bit-form'));
552 } else {
553 return new WP_Error('token_expired', __('Token expired', 'bit-form'));
554 }
555 }
556
557 private function verifyGRecaptcha()
558 {
559 $captchaSettings = $this->getCaptchaSettings();
560 $captchaV3Settings = $this->getCaptchaV3Settings();
561 if ($captchaSettings || $captchaV3Settings) {
562 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
563 if (!isset($_POST['g-recaptcha-response'])) {
564 return new WP_Error('spam_detection', __('Please recheck your reCaptcha Configuration', 'bit-form'));
565 }
566 $integrationHandler = new IntegrationHandler(0);
567 $allFormIntegrations = $integrationHandler->getAllIntegration('app', $captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3');
568 if (!is_wp_error($allFormIntegrations)) {
569 foreach ($allFormIntegrations as $integration) {
570 if (!is_null($integration->integration_type) && $integration->integration_type === ($captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3')) {
571 $integrationDetails = json_decode($integration->integration_details);
572 $integrationDetails->id = $integration->id;
573 $reCAPTCHA = $integrationDetails;
574 }
575 }
576 }
577 if (!empty($reCAPTCHA->secretKey)) {
578 $gRecaptchaResponse = HttpHelper::post(
579 'https://www.google.com/recaptcha/api/siteverify',
580 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
581 );
582 $isgReCaptchaVerified = false;
583 if (!is_wp_error($gRecaptchaResponse)) {
584 if (
585 $captchaV3Settings
586 && !empty($gRecaptchaResponse->score)
587 && ((float) $gRecaptchaResponse->score < (float) $captchaV3Settings->score)
588 ) {
589 wp_send_json_error(
590 sanitize_text_field((string) $captchaV3Settings->message)
591 );
592 }
593
594 $isgReCaptchaVerified = $gRecaptchaResponse->success;
595 }
596 if (!$isgReCaptchaVerified) {
597 return new WP_Error('spam_detection', __('Please verify reCAPTCHA', 'bit-form'));
598 }
599 }
600 }
601 }
602
603 private function verifyHCaptcha()
604 {
605 $hCaptchaExist = $this->isFieldTypeExist('hcaptcha'); // You can rename this to getHCaptchaSettings() if needed
606 if ($hCaptchaExist) {
607 if (!isset($_POST['h-captcha-response'])) {
608 return new WP_Error('spam_detection', __('Please verify hCaptcha', 'bit-form'));
609 }
610
611 $token = sanitize_text_field(wp_unslash($_POST['h-captcha-response']));
612
613 $integrationHandler = new IntegrationHandler(0);
614 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'hcaptcha');
615
616 if (!is_wp_error($allFormIntegrations)) {
617 foreach ($allFormIntegrations as $integration) {
618 if (!is_null($integration->integration_type) && 'hcaptcha' === $integration->integration_type) {
619 $integrationDetails = json_decode($integration->integration_details);
620 $integrationDetails->id = $integration->id;
621 $hCaptcha = $integrationDetails;
622 }
623 }
624 }
625
626 if (!empty($hCaptcha->secretKey)) {
627 $hCaptchaResponse = HttpHelper::post(
628 'https://api.hcaptcha.com/siteverify',
629 [
630 'secret' => $hCaptcha->secretKey,
631 'response' => $token,
632 'remoteip' => (isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '')
633 ]
634 );
635
636 $isVerified = false;
637 if (!is_wp_error($hCaptchaResponse)) {
638 $isVerified = $hCaptchaResponse->success;
639 }
640
641 if (!$isVerified) {
642 return new WP_Error('spam_detection', __('hCaptcha verification failed', 'bit-form'));
643 }
644 }
645 }
646 }
647
648 private function verifyTurnstileCaptcha()
649 {
650 $turnstileExist = $this->isFieldTypeExist('turnstile');
651 if ($turnstileExist) {
652 if (!isset($_POST['cf-turnstile-response'])) {
653 return new WP_Error('spam_detection', __('Please verify Cloudflare Turnstile Captcha', 'bit-form'));
654 }
655 $token = sanitize_text_field(wp_unslash($_POST['cf-turnstile-response']));
656 $turnstileCaptcha = null;
657 $integrationHandler = new IntegrationHandler(0);
658 $turnstileIntegration = $integrationHandler->getAllIntegration('app', 'turnstileCaptcha')[0];
659 if (!is_wp_error($turnstileIntegration && !is_null($turnstileIntegration->integration_type))) {
660 $turnstileCaptcha = json_decode($turnstileIntegration->integration_details);
661 // $integrationDetails->id = $turnstileIntegration->id;
662 // $turnstileCaptcha = $integrationDetails;
663 }
664 if (!is_null($turnstileCaptcha)) {
665 $isTurnstileCaptchaVerified = false;
666 $turnstileRecaptchaResponse = HttpHelper::post(
667 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
668 ['secret' => $turnstileCaptcha->secretKey, 'response' => $token]
669 );
670 if (!is_wp_error($turnstileRecaptchaResponse)) {
671 if (!$turnstileRecaptchaResponse->success) {
672 $errorCodes = implode(', ', (array) ($turnstileRecaptchaResponse->{'error-codes'} ?? []));
673 wp_send_json_error(
674 sprintf(
675 /* translators: %s: dynamic value. */
676 __('Cloudflare Turnstile Validation Error: %s', 'bit-form'),
677 $errorCodes
678 )
679 );
680 }
681
682 $isTurnstileCaptchaVerified = $turnstileRecaptchaResponse->success;
683 }
684 if (!$isTurnstileCaptchaVerified) {
685 return new WP_Error('spam_detection', __('Please verify Cloudflare Turnstile Captcha', 'bit-form'));
686 }
687 }
688 }
689 }
690
691 public function verifySubmissionNonce()
692 {
693 if (!isset($_POST['t_identity']) || !isset($_POST['csrf'])) {
694 return false;
695 }
696 $tIdenty = sanitize_text_field(wp_unslash($_POST['t_identity']));
697 $csrf = sanitize_text_field(wp_unslash($_POST['csrf']));
698 unset($_POST['t_identity'], $_POST['action'], $_POST['bitforms_id'], $_POST['csrf']);
699 return Helpers::csrfDecrypted($tIdenty, $csrf);
700 }
701
702 public function setViewCount()
703 {
704 if (!current_user_can('manage_options')) {
705 $update_status = $this->formModel->update(
706 [
707 'views' => intval(static::$form[0]->views) + 1
708 ],
709 [
710 'id' => $this->form_id
711 ]
712 );
713 }
714 }
715
716 public function checkSubmissionRestriction($checkedEmptySubmitted = true)
717 {
718 $formContents = $this->getFormContent();
719 $additionalSettings = isset($formContents->additional) ? $formContents->additional : null;
720 $fromRestrictionSetitingsEnabled = empty($additionalSettings->enabled) ? [] : $additionalSettings->enabled;
721 $fromRestrictionSetitings = empty($additionalSettings->settings) ? null : $additionalSettings->settings;
722
723 if (is_null($additionalSettings) || is_null($fromRestrictionSetitings) || empty((array) $fromRestrictionSetitingsEnabled)) {
724 return false;
725 }
726
727 $restrictionMessage = [];
728 $ipTool = new IpTool();
729 $ipAddress = $ipTool->getIP();
730 $currentUserId = get_current_user_id();
731
732 foreach ($fromRestrictionSetitingsEnabled as $restrictionKey => $isEnabled) {
733 if ($isEnabled) {
734 /**
735 * Allow add-ons to handle any restriction key (Pro-only restrictions
736 * should be implemented in the add-on, not shipped in the free plugin).
737 *
738 * Return a non-null string to block submission.
739 */
740 $addonMsg = apply_filters(
741 'bitform_submission_restriction',
742 null,
743 $restrictionKey,
744 $this->form_id,
745 $fromRestrictionSetitingsEnabled,
746 $fromRestrictionSetitings,
747 $ipAddress,
748 $currentUserId
749 );
750
751 if (!is_null($addonMsg) && '' !== $addonMsg) {
752 $restrictionMessage[] = $addonMsg;
753 continue;
754 }
755
756 if ('onePerIp' === $restrictionKey) {
757 $formEntry = new FormEntryModel();
758
759 $getResult = $formEntry->get(
760 ['user_ip', 'status'],
761 [
762 'form_id' => $this->form_id,
763 'user_ip' => (int) ip2long((string) $ipAddress)
764 ],
765 );
766
767 $count = 0;
768 $status = 0;
769
770 if (!is_wp_error($getResult) && count($getResult) > 0) {
771 $count = count($getResult);
772
773 foreach ($getResult as $row) {
774 if (9 === (int) $row->status) {
775 $status = 9;
776 break;
777 }
778 }
779 }
780
781 if ($count > 0 && 9 !== (int) $status) {
782 $onePerIp = __('Sorry!! You have already submitted from this IP address', 'bit-form');
783
784 $onePerIp = apply_filters(
785 'bitform_filter_restriction_one_per_ip_message',
786 $onePerIp,
787 $this->form_id
788 );
789
790 $restrictionMessage[] = $onePerIp;
791 }
792 }
793 if ('is_login' === $restrictionKey && 0 === get_current_user_id()) {
794 $is_login_messages = $fromRestrictionSetitings->is_login->message;
795
796 $is_login_messages = apply_filters(
797 'bitform_filter_restriction_is_login_message',
798 $is_login_messages,
799 $this->form_id
800 );
801
802 $restrictionMessage[] = $is_login_messages;
803 }
804 if ($checkedEmptySubmitted && 'empty_submission' === $restrictionKey) {
805 $isEmpty = $this->checkEmptySubmission(wp_unslash($_POST), GlobalHelper::sanitize_files_input($_FILES));
806 if ($isEmpty) {
807 $restriction = $fromRestrictionSetitings->empty_submission->message;
808
809 $restriction = apply_filters(
810 'bitform_filter_restriction_empty_submission_message',
811 $restriction,
812 $this->form_id
813 );
814
815 $restrictionMessage[] = $restriction;
816 }
817 }
818 }
819 }
820 return $restrictionMessage;
821 }
822
823 /**
824 * Will check if form is submitted by a bot
825 *
826 * @return Boolean true - if submitted by bot else false
827 */
828 public function isTrappedInHoneypot()
829 {
830 // Honeypot is implemented by add-ons (e.g. Pro) via filter.
831 return (bool) apply_filters('bitform_check_honeypot', false, $this->_form_id, wp_unslash($_POST));
832 }
833
834 public function isHoneypotActive()
835 {
836 return (bool) apply_filters('bitform_is_honeypot_active', false, $this->_form_id, $this->getFormContent());
837 }
838
839 public function checkPaymentFields()
840 {
841 $formContents = $this->getFormContent();
842 $fields = $formContents->fields;
843
844 $payments = [];
845 foreach ($fields as $fldData) {
846 if ('paypal' === $fldData->typ && property_exists($fldData, 'payIntegID')) {
847 $payments['paypalKey'] = $this->getClientKey($fldData->payIntegID, 'clientID');
848 } elseif ('razorpay' === $fldData->typ && property_exists($fldData->options, 'payIntegID')) {
849 $payments['razorpayKey'] = $this->getClientKey($fldData->options->payIntegID, 'apiKey');
850 }
851 }
852
853 return $payments;
854 }
855
856 private function getClientKey($integID, $keyName)
857 {
858 $client = '';
859 if (!empty($integID)) {
860 $integrationHandler = new IntegrationHandler(0);
861 $integration = $integrationHandler->getAIntegration($integID, 'app', 'payments');
862 if (!is_wp_error($integration)) {
863 $integration_details = json_decode($integration[0]->integration_details);
864 $client = base64_encode($integration_details->{$keyName});
865 }
866 }
867 return $client;
868 }
869
870 public function getSuccessMessageMarkups()
871 {
872 if (is_null($this->_work_flows)) {
873 $workFlowManager = new WorkFlowHandler($this->form_id);
874 $this->_work_flows = $workFlowManager->getAllworkFlow();
875 }
876
877 $ids = [];
878 foreach ($this->_work_flows as $msgItem) {
879 foreach ($msgItem['conditions'] as $condition) {
880 if (isset($condition->actions->success)) {
881 foreach ($condition->actions->success as $msg) {
882 if ('successMsg' === $msg->type && isset($msg->details->id)) {
883 $idObj = json_decode(stripslashes($msg->details->id));
884 if (is_object($idObj) && !empty($idObj->id)) {
885 array_push($ids, $idObj->id);
886 }
887 }
888 }
889 }
890 if (isset($condition->actions->failure)) {
891 $idObj = json_decode(stripslashes($condition->actions->failure));
892 if (is_object($idObj) && !empty($idObj->id)) {
893 array_push($ids, $idObj->id);
894 }
895 }
896 }
897 }
898 $ids = array_unique($ids);
899 if (is_null($this->_conf_messages)) {
900 $successMsgHandler = new SuccessMessageHandler($this->form_id);
901 $this->_conf_messages = $successMsgHandler->getMessages($ids);
902 }
903
904 $messageMarkups = '';
905 if (is_wp_error($this->_conf_messages)) {
906 return $messageMarkups;
907 }
908
909 foreach ($this->_conf_messages as $msgItem) {
910 $messageMarkups .= $this->messageMarkup($msgItem);
911 }
912
913 return $messageMarkups;
914 }
915
916 public function getFormAbandonmentMessage()
917 {
918 $msg = apply_filters('bitform_form_abandonment_warning_markup', '', $this->form_id);
919 return is_string($msg) ? $msg : '';
920 }
921
922 public function getFormAbandonmentSettings()
923 {
924 return apply_filters('bitform_form_abandonment_settings', null, $this->form_id);
925 }
926
927 private function messageMarkup($msg)
928 {
929 $msgId = $msg->id;
930 $msgConfig = json_decode($msg->message_config);
931 $scrollClass = 'below' === $msgConfig->msgType ? 'scroll' : '';
932
933 return '<div
934 role="dialog"
935 aria-hidden="true"
936 data-modal-backdrop="true"
937 class="' . $this->getAtomicCls("msg-container-{$msgId}") . ' deactive ' . $scrollClass . '">
938 <div
939 data-contentid="' . $this->getFormIdentifier() . '"
940 data-msgid="' . $msgId . '"
941 role="button"
942 class="' . $this->getAtomicCls("msg-background-{$msgId}") . ' msg-backdrop">
943 <div class="bf-msg-content ' . $this->getAtomicCls("msg-content-{$msgId}") . '">
944 <button
945 data-contentid="' . $this->getFormIdentifier() . '"
946 data-msgid="' . $msgId . '"
947 class="' . $this->getAtomicCls("close-{$msgId}") . ' bf-msg-close"
948 type="button">
949 <svg class="' . $this->getAtomicCls("close-icn-{$msgId}") . '" viewBox="0 0 30 30">
950 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
951 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
952 </svg>
953 </button>
954 <div class="msg-content"></div>
955 </div>
956 </div>
957 </div>';
958 }
959 }
960