PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V3.0.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV3.0.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 2.10.1 All 138 releases
bit-form / includes / Frontend / Form / FrontendFormManager.php

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

956 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 $fromRestrictionSetitingsEnabled = empty($formContents->additional->enabled) ? [] : $formContents->additional->enabled;
720 $fromRestrictionSetitings = empty($formContents->additional->settings) ? null : $formContents->additional->settings;
721 if (is_null($formContents->additional->enabled) || is_null($formContents->additional->settings)) {
722 return false;
723 }
724 $restrictionMessage = [];
725 $ipTool = new IpTool();
726 $ipAddress = $ipTool->getIP();
727 $currentUserId = get_current_user_id();
728 // error_log(print_r(['ip address', $ipAddress, ip2long($ipAddress)], true));
729 // error_log(print_r(['restrictions', $fromRestrictionSetitings], true));
730 foreach ($fromRestrictionSetitingsEnabled as $restrictionKey => $isEnabled) {
731 if ($isEnabled) {
732 /**
733 * Allow add-ons to handle any restriction key (Pro-only restrictions
734 * should be implemented in the add-on, not shipped in the free plugin).
735 *
736 * Return a non-null string to block submission.
737 */
738 $addonMsg = apply_filters(
739 'bitform_submission_restriction',
740 null,
741 $restrictionKey,
742 $this->form_id,
743 $fromRestrictionSetitings,
744 $ipAddress,
745 $currentUserId
746 );
747 if (!is_null($addonMsg) && '' !== $addonMsg) {
748 $restrictionMessage[] = $addonMsg;
749 continue;
750 }
751
752 if ('onePerIp' === $restrictionKey) {
753 $formEntry = new FormEntryModel();
754
755 $getResult = $formEntry->get(
756 ['user_ip', 'status'],
757 [
758 'form_id' => $this->form_id,
759 'user_ip' => (int) ip2long((string) $ipAddress)
760 ],
761 );
762
763 $count = 0;
764 $status = 0;
765
766 if (!is_wp_error($getResult) && count($getResult) > 0) {
767 $count = count($getResult);
768
769 foreach ($getResult as $row) {
770 if (9 === (int) $row->status) {
771 $status = 9;
772 break;
773 }
774 }
775 }
776
777 if ($count > 0 && 9 !== (int) $status) {
778 $onePerIp = __('Sorry!! You have already submitted from this IP address', 'bit-form');
779
780 $onePerIp = apply_filters(
781 'bitform_filter_restriction_one_per_ip_message',
782 $onePerIp,
783 $this->form_id
784 );
785
786 $restrictionMessage[] = $onePerIp;
787 }
788 }
789 if ('is_login' === $restrictionKey && 0 === get_current_user_id()) {
790 $is_login_messages = $fromRestrictionSetitings->is_login->message;
791
792 $is_login_messages = apply_filters(
793 'bitform_filter_restriction_is_login_message',
794 $is_login_messages,
795 $this->form_id
796 );
797
798 $restrictionMessage[] = $is_login_messages;
799 }
800 if ($checkedEmptySubmitted && 'empty_submission' === $restrictionKey) {
801 $isEmpty = $this->checkEmptySubmission(wp_unslash($_POST), GlobalHelper::sanitize_files_input($_FILES));
802 if ($isEmpty) {
803 $restriction = $fromRestrictionSetitings->empty_submission->message;
804
805 $restriction = apply_filters(
806 'bitform_filter_restriction_empty_submission_message',
807 $restriction,
808 $this->form_id
809 );
810
811 $restrictionMessage[] = $restriction;
812 }
813 }
814 }
815 }
816 return $restrictionMessage;
817 }
818
819 /**
820 * Will check if form is submitted by a bot
821 *
822 * @return Boolean true - if submitted by bot else false
823 */
824 public function isTrappedInHoneypot()
825 {
826 // Honeypot is implemented by add-ons (e.g. Pro) via filter.
827 return (bool) apply_filters('bitform_check_honeypot', false, $this->_form_id, wp_unslash($_POST));
828 }
829
830 public function isHoneypotActive()
831 {
832 return (bool) apply_filters('bitform_is_honeypot_active', false, $this->_form_id, $this->getFormContent());
833 }
834
835 public function checkPaymentFields()
836 {
837 $formContents = $this->getFormContent();
838 $fields = $formContents->fields;
839
840 $payments = [];
841 foreach ($fields as $fldData) {
842 if ('paypal' === $fldData->typ && property_exists($fldData, 'payIntegID')) {
843 $payments['paypalKey'] = $this->getClientKey($fldData->payIntegID, 'clientID');
844 } elseif ('razorpay' === $fldData->typ && property_exists($fldData->options, 'payIntegID')) {
845 $payments['razorpayKey'] = $this->getClientKey($fldData->options->payIntegID, 'apiKey');
846 }
847 }
848
849 return $payments;
850 }
851
852 private function getClientKey($integID, $keyName)
853 {
854 $client = '';
855 if (!empty($integID)) {
856 $integrationHandler = new IntegrationHandler(0);
857 $integration = $integrationHandler->getAIntegration($integID, 'app', 'payments');
858 if (!is_wp_error($integration)) {
859 $integration_details = json_decode($integration[0]->integration_details);
860 $client = base64_encode($integration_details->{$keyName});
861 }
862 }
863 return $client;
864 }
865
866 public function getSuccessMessageMarkups()
867 {
868 if (is_null($this->_work_flows)) {
869 $workFlowManager = new WorkFlowHandler($this->form_id);
870 $this->_work_flows = $workFlowManager->getAllworkFlow();
871 }
872
873 $ids = [];
874 foreach ($this->_work_flows as $msgItem) {
875 foreach ($msgItem['conditions'] as $condition) {
876 if (isset($condition->actions->success)) {
877 foreach ($condition->actions->success as $msg) {
878 if ('successMsg' === $msg->type && isset($msg->details->id)) {
879 $idObj = json_decode(stripslashes($msg->details->id));
880 if (is_object($idObj) && !empty($idObj->id)) {
881 array_push($ids, $idObj->id);
882 }
883 }
884 }
885 }
886 if (isset($condition->actions->failure)) {
887 $idObj = json_decode(stripslashes($condition->actions->failure));
888 if (is_object($idObj) && !empty($idObj->id)) {
889 array_push($ids, $idObj->id);
890 }
891 }
892 }
893 }
894 $ids = array_unique($ids);
895 if (is_null($this->_conf_messages)) {
896 $successMsgHandler = new SuccessMessageHandler($this->form_id);
897 $this->_conf_messages = $successMsgHandler->getMessages($ids);
898 }
899
900 $messageMarkups = '';
901 if (is_wp_error($this->_conf_messages)) {
902 return $messageMarkups;
903 }
904
905 foreach ($this->_conf_messages as $msgItem) {
906 $messageMarkups .= $this->messageMarkup($msgItem);
907 }
908
909 return $messageMarkups;
910 }
911
912 public function getFormAbandonmentMessage()
913 {
914 $msg = apply_filters('bitform_form_abandonment_warning_markup', '', $this->form_id);
915 return is_string($msg) ? $msg : '';
916 }
917
918 public function getFormAbandonmentSettings()
919 {
920 return apply_filters('bitform_form_abandonment_settings', null, $this->form_id);
921 }
922
923 private function messageMarkup($msg)
924 {
925 $msgId = $msg->id;
926 $msgConfig = json_decode($msg->message_config);
927 $scrollClass = 'below' === $msgConfig->msgType ? 'scroll' : '';
928
929 return '<div
930 role="dialog"
931 aria-hidden="true"
932 data-modal-backdrop="true"
933 class="' . $this->getAtomicCls("msg-container-{$msgId}") . ' deactive ' . $scrollClass . '">
934 <div
935 data-contentid="' . $this->getFormIdentifier() . '"
936 data-msgid="' . $msgId . '"
937 role="button"
938 class="' . $this->getAtomicCls("msg-background-{$msgId}") . ' msg-backdrop">
939 <div class="bf-msg-content ' . $this->getAtomicCls("msg-content-{$msgId}") . '">
940 <button
941 data-contentid="' . $this->getFormIdentifier() . '"
942 data-msgid="' . $msgId . '"
943 class="' . $this->getAtomicCls("close-{$msgId}") . ' bf-msg-close"
944 type="button">
945 <svg class="' . $this->getAtomicCls("close-icn-{$msgId}") . '" viewBox="0 0 30 30">
946 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
947 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
948 </svg>
949 </button>
950 <div class="msg-content"></div>
951 </div>
952 </div>
953 </div>';
954 }
955 }
956