PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.15.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.15.3
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
← All changes | includes/Frontend/Form/FrontendFormManager.php +333 -561 3.3.12.15.3 View file →
@@ -9,9 +9,8 @@
9 9 /**
10 10 * FrontendFormManager class
11 11 */
12 12
13 -use BitCode\BitForm\Admin\Form\AdminFormHandler;
14 13 use BitCode\BitForm\Admin\Form\Helpers;
15 14 use BitCode\BitForm\Core\Database\FormEntryModel;
16 15 use BitCode\BitForm\Core\Form\FormManager;
17 16 use BitCode\BitForm\Core\Form\Validator\FormFieldValidator;
@@ -17,17 +16,15 @@
17 16 use BitCode\BitForm\Core\Form\Validator\FormFieldValidator;
18 17 use BitCode\BitForm\Core\Integration\IntegrationHandler;
19 18 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
20 19 use BitCode\BitForm\Core\Util\ApiResponse as UtilApiResponse;
21 -use BitCode\BitForm\Core\Util\EscapingHelper;
22 -use BitCode\BitForm\Core\Util\FieldValueHandler;
23 -use BitCode\BitForm\Core\Util\FrontendHelpers;
20 +use BitCode\BitForm\Core\Util\DateTimeHelper;
24 21 use BitCode\BitForm\Core\Util\HttpHelper;
25 22 use BitCode\BitForm\Core\Util\IpTool;
26 -use BitCode\BitForm\Core\Util\Utilities;
27 23 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
24 +use BitCode\BitForm\Core\WorkFlow\WorkFlowHandler;
28 25 use BitCode\BitForm\Frontend\Form\View\FormViewer;
29 -use BitCode\BitForm\GlobalHelper;
26 +use BitCode\BitFormPro\Admin\FormSettings\FormAbandonment;
30 27 use WP_Error;
31 28
32 29 final class FrontendFormManager extends FormManager
33 30 {
@@ -33,10 +30,10 @@
33 30 {
34 31 private $_form_identifier;
35 32 private $_form_token;
36 33 private $_form_id;
34 + private $_work_flows;
37 35 private $_conf_messages;
38 - private static $_instance = [];
39 36
40 37 // private $_has_upload = false;
41 38 public function __construct($form_id, $shortCodeCounter = null)
42 39 {
@@ -47,19 +44,8 @@
47 44 $this->_form_token = wp_create_nonce('bitforms_' . $form_id);
48 45 $this->_form_id = $form_id;
49 46 }
50 47
51 - public static function getInstance($form_id, $shortCodeCounter = null)
52 - {
53 - $key = $form_id . ':' . ($shortCodeCounter ?? 'default');
54 -
55 - if (!isset(self::$_instance[$key])) {
56 - self::$_instance[$key] = new self($form_id, $shortCodeCounter);
57 - }
58 -
59 - return self::$_instance[$key];
60 - }
61 -
62 48 public function getFormIdentifier()
63 49 {
64 50 return $this->_form_identifier;
65 51 }
@@ -73,8 +59,14 @@
73 59 {
74 60 return $this->_form_token;
75 61 }
76 62
63 + public function isSubmitted()
64 + {
65 + // return isset($_POST[$this->_form_identifier]) ? true : false;
66 + return (isset($_POST['bitforms_id']) && $_POST['bitforms_id'] === $this->_form_identifier) ? true : false;
67 + }
68 +
77 69 public function getSubmittedFields($submitted_data)
78 70 {
79 71 unset($submitted_data[$this->_form_identifier]);
80 72 // unset($submitted_data['bit-form-submit-btn']);
@@ -80,9 +72,9 @@
80 72 // unset($submitted_data['bit-form-submit-btn']);
81 73 return array_keys($submitted_data);
82 74 }
83 75
84 - public function formView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null, $isEntryEdit = false)
76 + public function formView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
85 77 {
86 78 $formContents = $this->getFormContent();
87 79 $formAtomicClsMap = $this->getAtomicClsMap();
88 80 if (!empty($fields)) {
@@ -95,14 +87,14 @@
95 87 );
96 88 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
97 89 }
98 90 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
99 - $isRestricted = $this->checkSubmissionRestriction(false, $isEntryEdit);
91 + $isRestricted = $this->checkSubmissionRestriction(false);
100 92 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
101 93 return $formViewer->getView($hasFile, $msg);
102 94 }
103 95
104 - public function conversationalFormView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null, $isEntryEdit = false)
96 + public function conversationalFormView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
105 97 {
106 98 $formContents = $this->getFormContent();
107 99 $formAtomicClsMap = $this->getAtomicClsMap();
108 100 if (!empty($fields)) {
@@ -115,14 +107,14 @@
115 107 );
116 108 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
117 109 }
118 110 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
119 - $isRestricted = $this->checkSubmissionRestriction(false, $isEntryEdit);
111 + $isRestricted = $this->checkSubmissionRestriction(false);
120 112 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
121 113 return $formViewer->getConversationalView($hasFile, $msg);
122 114 }
123 115
124 - public function checkEmptySubmission($data, $file, $isEntryEdit = false)
116 + public function checkEmptySubmission($data, $file)
125 117 {
126 118 $formFields = $this->getFields();
127 119 foreach ($formFields as $key => $field) {
128 120 $fieldType = $field['type'];
@@ -129,20 +121,12 @@
129 121 if ('button' === $fieldType) {
130 122 continue;
131 123 }
132 124 $fileUploadFieldTypes = ['file-up', 'advanced-file-up'];
133 - if ('decision-box' === $fieldType || 'gdpr' === $fieldType) {
125 + if ('decision-box' === $fieldType) {
134 126 continue;
135 127 }
136 128 $isFileType = in_array($fieldType, $fileUploadFieldTypes);
137 - // An edit keeps an untouched file/signature as `<fieldKey>_old`, not as an upload.
138 - if (
139 - $isEntryEdit
140 - && ($isFileType || 'signature' === $fieldType)
141 - && !empty(FieldValueHandler::retainedOldValues($data, $key))
142 - ) {
143 - return false;
144 - }
145 129 if ($this->isRepeatedField($key)) {
146 130 $fileData = !empty($file[$key]) ? $file[$key] : [];
147 131 $dataVal = !empty($data[$key]) ? $data[$key] : [];
148 132 if (!$this->checkRepeatedFieldEmptySubmission($isFileType, $dataVal, $fileData)) {
@@ -198,146 +182,45 @@
198 182 }
199 183 return $parameter;
200 184 }
201 185
202 - private function getFormFields($formID)
203 - {
204 - $adminFormHandler = new AdminFormHandler();
205 - $post = new \stdClass();
206 - $post = (object) [
207 - 'id' => $formID
208 - ];
209 - $getForm = $adminFormHandler->getAForm('', $post);
210 - $formContainer = $getForm['form_content'];
211 -
212 - return $formContainer['fields'];
213 - }
214 -
215 - private function transformDrpdwnValue($post)
216 - {
217 - $formFields = $this->getFormFields($this->_form_id);
218 -
219 - foreach ($post as $key => $value) {
220 - if (!str_starts_with($key, 'repeater') && isset($formFields->{$key}) && 'select' === $formFields->{$key}->typ) {
221 - if (is_array($value)) {
222 - foreach ($value as $k => $v) {
223 - $post[$key][$k] = !is_array($v) && is_string($v) ? explode(BITFORMS_BF_SEPARATOR, $v) : $v;
224 - }
225 - } else {
226 - $post[$key] = explode(BITFORMS_BF_SEPARATOR, $value);
227 - }
228 - };
229 - }
230 -
231 - return $post;
232 - }
233 -
234 - /**
235 - * WP auth errors carry markup and the confirmation box paints them with innerHTML,
236 - * so esc_html() would show the tags as text. kses keeps only the safe markup.
237 - *
238 - * @param mixed $message
239 - *
240 - * @return string
241 - */
242 - private static function authErrorMessage($message)
243 - {
244 - return wp_kses(is_string($message) ? $message : '', EscapingHelper::getAllowedHtmlTags());
245 - }
246 -
247 - /**
248 - * A confirm-enabled email/password field posts as one composite and the validator collapses it
249 - * to the primary value, so the confirm child's own field key never reaches $_POST. WP auth
250 - * integrations map fields by key, so fill those child keys on a copy for the auth filter.
251 - *
252 - * @param mixed $postData
253 - *
254 - * @return mixed
255 - */
256 - private function resolveConfirmChildValues($postData)
257 - {
258 - if (!is_array($postData)) {
259 - return $postData;
260 - }
261 - $fields = $this->getFields();
262 - foreach ($fields as $fieldKey => $fieldData) {
263 - if (
264 - empty($fieldData['childFields'])
265 - || !isset($fieldData['type'])
266 - || !in_array($fieldData['type'], ['email', 'password'], true)
267 - || !empty($fieldData['repeated'])
268 - || !isset($postData[$fieldKey])
269 - ) {
270 - continue;
271 - }
272 - $parentValue = $postData[$fieldKey];
273 - foreach ((array) $fieldData['childFields'] as $childFieldRef) {
274 - $childKey = is_object($childFieldRef) && isset($childFieldRef->fldKey) ? $childFieldRef->fldKey : '';
275 - if (
276 - empty($childKey)
277 - || !isset($fields[$childKey])
278 - || !empty($fields[$childKey]['isDeactive'])
279 - || isset($postData[$childKey])
280 - ) {
281 - continue;
282 - }
283 - if (is_array($parentValue)) {
284 - if (array_key_exists('confirm', $parentValue)) {
285 - $postData[$childKey] = $parentValue['confirm'];
286 - }
287 - continue;
288 - }
289 - // Validation matched primary against confirm before collapsing, so this is that value.
290 - $postData[$childKey] = $parentValue;
291 - }
292 - if (is_array($parentValue) && array_key_exists('primary', $parentValue)) {
293 - $postData[$fieldKey] = $parentValue['primary'];
294 - }
295 - }
296 -
297 - return $postData;
298 - }
299 -
300 186 public function handleSubmission()
301 187 {
302 - // CSRF verified via verifySubmissionNonce() before this method is called. All $_POST reads below occur after that verification.
303 188 $this->fieldNameReplaceOfPost();
304 189
305 190 $validated = $this->beforeSubmittedValidate();
306 -
307 191 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
308 192
309 193 if (true === $validated) {
310 194 do_action('bitform_validation_success', $this->_form_id);
311 - $this->discardHiddenFieldValues();
195 + unset($_POST['hidden_fields']);
312 196
313 197 $redirectPage = '';
314 198 $regSuccMsg = '';
315 199
316 200 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
317 - $unslashed_post = wp_unslash($_POST);
318 201 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
319 202 $parameter = $this->getParams();
320 - $existAuthFilter = has_filter('bitform_wp_user_auth');
203 + $existAuthFilter = has_filter('bf_wp_user_auth');
321 204
322 205 if (true === $existAuthFilter) {
323 - $authPostData = $this->resolveConfirmChildValues($unslashed_post);
324 - $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $authPostData, $parameter);
206 + $result = apply_filters('bf_wp_user_auth', $existAuth[0], $_POST, $parameter);
325 207
326 - $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $this->_form_id, $authPostData, $parameter);
208 + $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $_POST, $parameter);
327 209
328 - do_action('bitform_wp_user_auth_response', $result, $this->_form_id, $authPostData, $parameter);
210 + do_action('bitform_wp_user_auth_response', $result, $_POST, $parameter);
329 211
330 212 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
331 213 if (!$result['success']) {
332 - return new WP_Error('errors', self::authErrorMessage($result['message']));
214 + return new WP_Error('errors', __($result['message'], 'bit-form'));
333 215 } elseif (isset($result['success'])) {
334 216 $redirectPage = $result['redirectPage'];
335 217 $regSuccMsg = $result['message'];
218 + $newNonce = wp_create_nonce('bitforms_' . $this->_form_id);
336 219 }
337 220 } else {
338 221 if (!$result['success']) {
339 - return new WP_Error('errors', self::authErrorMessage($result['message']));
222 + return new WP_Error('errors', __($result['message'], 'bit-form'));
340 223 } else {
341 224 return $result;
342 225 }
343 226 }
@@ -343,33 +226,38 @@
343 226 }
344 227 }
345 228 }
346 229
347 - $saveResponse = $this->saveFormEntry($unslashed_post);
230 + $saveResponse = $this->saveFormEntry($_POST);
348 231 if (is_wp_error($saveResponse)) {
349 232 return $saveResponse;
350 233 }
351 234
352 235 $entryID = $saveResponse['entry_id'];
236 + do_action('bitform_submit_success', $this->_form_id, $entryID, $_POST);
353 237
354 - // transformed dropdown value from string to array
355 - $newPost = $this->transformDrpdwnValue($unslashed_post);
356 - $filesData = GlobalHelper::sanitize_files_input($_FILES);
357 - do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $filesData);
238 + // check and replace signature field value
239 + $formFields = $this->getFields();
240 + $uploadPath = BITFORMS_UPLOAD_BASE_URL . "/uploads/{$this->_form_id}/{$entryID}";
358 241
242 + foreach ($formFields as $key => $field) {
243 + if ('signature' === $field['type']) {
244 + $saveResponse['fields'][$key] = $uploadPath . '/' . $saveResponse['fields'][$key];
245 + break;
246 + }
247 + }
248 +
359 249 $captchaV3Settings = $this->getCaptchaV3Settings();
360 250 if ($captchaV3Settings) {
361 - $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
251 + $token = $_POST['g-recaptcha-response'];
362 252 $integrationHandler = new IntegrationHandler(0);
363 253 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
364 254 if (!is_wp_error($allFormIntegrations)) {
365 255 foreach ($allFormIntegrations as $integration) {
366 256 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
367 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
368 - if ($integrationDetails) {
369 - $integrationDetails->id = $integration->id;
370 - $reCAPTCHA = $integrationDetails;
371 - }
257 + $integrationDetails = json_decode($integration->integration_details);
258 + $integrationDetails->id = $integration->id;
259 + $reCAPTCHA = $integrationDetails;
372 260 }
373 261 }
374 262 }
375 263 if (!empty($reCAPTCHA->secretKey)) {
@@ -391,14 +279,16 @@
391 279 }
392 280 if (!empty($regSuccMsg) && isset($saveResponse['dflt_message'])) {
393 281 $saveResponse['message'] = $regSuccMsg;
394 282 }
395 - $saveResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
396 283
397 284 $saveResponse = IntegrationHandler::maybeSetCronForIntegration($saveResponse, 'create');
398 285 $entryId = $saveResponse['entry_id'];
399 286
400 287 $responseMsg = is_array($saveResponse) && !empty($saveResponse) ? $saveResponse : __('Form Submitted Successfully', 'bit-form');
288 + if (isset($newNonce)) {
289 + $responseMsg['new_nonce'] = $newNonce;
290 + }
401 291 $_POST = [];
402 292 $responseMsg['entry_id'] = $entryId;
403 293 return $responseMsg;
404 294 }
@@ -407,46 +297,43 @@
407 297 }
408 298
409 299 public function handleUpdateEntry()
410 300 {
411 - // Entry token or capability verified by caller (FrontendAjax::update_entry). All $_POST reads occur after that check.
412 301 $this->fieldNameReplaceOfPost();
413 - $validated = $this->beforeSubmittedValidate(true, true);
302 + $validated = $this->beforeSubmittedValidate();
414 303 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
415 304
416 - $entryID = isset($_REQUEST['entryID']) ? sanitize_text_field(wp_unslash($_REQUEST['entryID'])) : null;
417 - $GLOBALS['bitform_entry_id'] = $entryID;
305 + $entryID = $_REQUEST['entryID'];
306 + $GLOBALS['bf_entry_id'] = $entryID;
418 307 if (is_null($entryID)) {
419 308 return new WP_Error('empty_form', __('Entries id is invalid', 'bit-form'));
420 309 }
421 310 if (true === $validated) {
422 311 do_action('bitform_validation_success', $this->_form_id);
423 - $this->discardHiddenFieldValues();
424 - unset($_POST['entryID']);
312 + unset($_POST['hidden_fields'], $_POST['entryID']);
425 313
426 314 $redirectPage = '';
427 315 $regSuccMsg = '';
428 - $postData = wp_unslash($_POST);
429 316
430 317 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
431 318 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
432 319 $parameter = $this->getParams();
433 - $existAuthFilter = has_filter('bitform_wp_user_auth');
320 + $existAuthFilter = has_filter('bf_wp_user_auth');
434 321
435 322 if (true === $existAuthFilter) {
436 - $authPostData = $this->resolveConfirmChildValues($postData);
437 - $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $authPostData, $parameter);
323 + $result = apply_filters('bf_wp_user_auth', $existAuth[0], $_POST, $parameter);
438 324
439 325 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
440 326 if (!$result['success']) {
441 - return new WP_Error('errors', self::authErrorMessage($result['message']));
327 + return new WP_Error('errors', __($result['message'], 'bit-form'));
442 328 } elseif (isset($result['success'])) {
443 329 $redirectPage = $result['redirectPage'];
444 330 $regSuccMsg = $result['message'];
331 + $newNonce = wp_create_nonce('bitforms_' . $this->_form_id);
445 332 }
446 333 } else {
447 334 if (!$result['success']) {
448 - return new WP_Error('errors', self::authErrorMessage($result['message']));
335 + return new WP_Error('errors', __($result['message'], 'bit-form'));
449 336 } else {
450 337 return $result;
451 338 }
452 339 }
@@ -452,34 +339,26 @@
452 339 }
453 340 }
454 341 }
455 342
456 - $updateResponse = $this->updateFormEntry(wp_unslash($_POST), $this->getFormID(), $entryID);
343 + $updateResponse = $this->updateFormEntry($_POST, $this->getFormID(), $entryID);
457 344 if (is_wp_error($updateResponse)) {
458 345 return $updateResponse;
459 346 }
460 347
461 - // transformed dropdown value from string to array
462 - $newPost = $this->transformDrpdwnValue($postData);
463 - $filesData = GlobalHelper::sanitize_files_input($_FILES);
348 + do_action('bitform_submit_success', $this->_form_id, $entryID, $_POST);
464 349
465 - //TO DO:: submit success action temporarily added for solution of a issue
466 - do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $filesData);
467 - do_action('bitform_update_success', $this->_form_id, $entryID, $newPost, $filesData);
468 -
469 350 $captchaV3Settings = $this->getCaptchaV3Settings();
470 351 if ($captchaV3Settings) {
471 - $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
352 + $token = $_POST['g-recaptcha-response'];
472 353 $integrationHandler = new IntegrationHandler(0);
473 354 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
474 355 if (!is_wp_error($allFormIntegrations)) {
475 356 foreach ($allFormIntegrations as $integration) {
476 357 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
477 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
478 - if ($integrationDetails) {
479 - $integrationDetails->id = $integration->id;
480 - $reCAPTCHA = $integrationDetails;
481 - }
358 + $integrationDetails = json_decode($integration->integration_details);
359 + $integrationDetails->id = $integration->id;
360 + $reCAPTCHA = $integrationDetails;
482 361 }
483 362 }
484 363 }
485 364 if (!empty($reCAPTCHA->secretKey)) {
@@ -501,14 +380,15 @@
501 380 }
502 381 if (!empty($regSuccMsg) && isset($updateResponse['dflt_message'])) {
503 382 $updateResponse['message'] = $regSuccMsg;
504 383 }
505 - $updateResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
506 - $updateResponse = IntegrationHandler::maybeSetCronForIntegration($updateResponse, 'update');
384 + $updateResponse = IntegrationHandler::maybeSetCronForIntegration($updateResponse, 'create');
507 385 $entryId = $updateResponse['entry_id'];
508 386
509 387 $responseMsg = is_array($updateResponse) && !empty($updateResponse) ? $updateResponse : __('Entry Update Successfully', 'bit-form');
510 -
388 + if (isset($newNonce)) {
389 + $responseMsg['new_nonce'] = $newNonce;
390 + }
511 391 $_POST = [];
512 392 $responseMsg['entry_id'] = $entryId;
513 393 return $responseMsg;
514 394 }
@@ -515,96 +395,11 @@
515 395 do_action('bitform_validation_error', $this->_form_id, $validated);
516 396 return $validated;
517 397 }
518 398
519 - /**
520 - * Drop the posted `hidden_fields` transport key and, when the form opts in, the values of
521 - * the fields it names.
522 - *
523 - * A hidden field keeps its typed value in the DOM, so the browser still submits it. Runs
524 - * here because it is the last point before entry, notifications and integrations are built
525 - * from $_POST.
526 - *
527 - * @return void
528 - */
529 - private function discardHiddenFieldValues()
530 - {
531 - // CSRF verified upstream via verifySubmissionNonce(); $_POST is only being narrowed here.
532 - $rawHiddenFields = isset($_POST['hidden_fields']) ? wp_unslash($_POST['hidden_fields']) : '';
533 - unset($_POST['hidden_fields']);
534 -
535 - if (!$this->shouldDiscardHiddenFieldValues()) {
536 - return;
537 - }
538 - $hiddenFieldKeys = FrontendHelpers::parseHiddenFieldKeys($rawHiddenFields);
539 - if (empty($hiddenFieldKeys)) {
540 - return;
541 - }
542 -
543 - $formFields = $this->getFields();
544 - foreach ($hiddenFieldKeys as $fieldKey) {
545 - if (!isset($formFields[$fieldKey])) {
546 - continue;
547 - }
548 - $field = $formFields[$fieldKey];
549 - // The posted list also names builder-hidden and hidden-type fields, which carry a value
550 - // on purpose. Only what conditional logic hid is discarded.
551 - if ('hidden' === $field['type'] || !empty($field['valid']['hide'])) {
552 - continue;
553 - }
554 - // Hiding flags a repeater child once, not per row, so discarding would wipe the column
555 - // in every row.
556 - if (!empty($field['repeated'])) {
557 - continue;
558 - }
559 - // Calculation and tracking fields opt out.
560 - if (!empty($field['valid']['keepValueWhenHidden'])) {
561 - continue;
562 - }
563 - // A composite child (name/address/confirm) posts nested under its parent key.
564 - if (!empty($field['parentFieldKey'])) {
565 - $this->discardCompositeChildValue($formFields, $field, $fieldKey);
566 - continue;
567 - }
568 - unset($_POST[$fieldKey], $_FILES[$fieldKey]);
569 - }
570 - }
571 -
572 - /**
573 - * @param array $formFields
574 - * @param array $field the child field's config
575 - * @param string $fieldKey the child field's key
576 - *
577 - * @return void
578 - */
579 - private function discardCompositeChildValue($formFields, $field, $fieldKey)
580 - {
581 - $parentKey = $field['parentFieldKey'];
582 - if (!isset($_POST[$parentKey]) || !is_array($_POST[$parentKey])) {
583 - return;
584 - }
585 - $parentName = isset($formFields[$parentKey]['name']) ? $formFields[$parentKey]['name'] : '';
586 - $childName = FieldValueHandler::deriveChildName(isset($field['name']) ? $field['name'] : '', $parentName);
587 - unset($_POST[$parentKey][$childName], $_POST[$parentKey][$fieldKey]);
588 - }
589 -
590 - /**
591 - * @return bool
592 - */
593 - private function shouldDiscardHiddenFieldValues()
594 - {
595 - $formInfo = $this->getFormInfo();
596 - if (!is_object($formInfo) || !isset($formInfo->submissionSettings)) {
597 - return false;
598 - }
599 - $submissionSettings = (object) $formInfo->submissionSettings;
600 -
601 - return !empty($submissionSettings->discardHiddenFieldValues);
602 - }
603 -
604 399 public function validateFormSubmission($submitted_data)
605 400 {
606 - $hidden_fields = FrontendHelpers::parseHiddenFieldKeys(isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '');
401 + $hidden_fields = isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '';
607 402 $submitted_fields = $this->getSubmittedFields($submitted_data);
608 403 $form_fields = $this->getFields();
609 404 $form_fields_names = array_keys($form_fields);
610 405 if ($this->isGCLIDEnabled()) {
@@ -610,9 +405,9 @@
610 405 if ($this->isGCLIDEnabled()) {
611 406 array_push($form_fields_names, 'GCLID');
612 407 }
613 408 foreach ($submitted_fields as $field) {
614 - if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || FrontendHelpers::isFieldHidden($hidden_fields, $field)) {
409 + if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || false !== strpos($hidden_fields, $field)) {
615 410 unset($submitted_data[$field]);
616 411 }
617 412 }
618 413 return $submitted_data;
@@ -617,39 +412,29 @@
617 412 }
618 413 return $submitted_data;
619 414 }
620 415
621 - public function beforeSubmittedValidate($verifyCaptcha = true, $isEntryEdit = false)
416 + public function beforeSubmittedValidate()
622 417 {
623 418 if ($this->verifySubmissionNonce()) {
624 419 if ($this->isExist()) {
625 - $isRestricted = $this->checkSubmissionRestriction(true, $isEntryEdit);
420 + $isRestricted = $this->checkSubmissionRestriction();
626 421 if ($isRestricted && !empty($isRestricted)) {
627 422 return new WP_Error('spam_detection', $isRestricted[0]);
628 423 }
629 - $postData = wp_unslash($_POST);
630 - $filesData = GlobalHelper::sanitize_files_input($_FILES);
631 - $isHoneypot = apply_filters('bitform_check_honeypot', false, $this->_form_id, $postData);
632 - if ($isHoneypot) {
424 + if ($this->isTrappedInHoneypot()) {
633 425 return new WP_Error('spam_detection', __('Token verification failed', 'bit-form'));
634 426 }
635 - $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
636 - // TODO: Temporary parameter to skip captcha verification in step change of multi step form
637 - if ($verifyCaptcha) {
638 - $verifyGRecaptchaResult = $this->verifyGRecaptcha();
639 - if (is_wp_error($verifyGRecaptchaResult)) {
640 - return $verifyGRecaptchaResult;
641 - }
642 - $verifyHCaptchaResult = $this->verifyHCaptcha();
643 - if (is_wp_error($verifyHCaptchaResult)) {
644 - return $verifyHCaptchaResult;
645 - }
646 - /* Implement Turnstile Captcha start */
647 - $verifyTurnstileCaptchaResult = $this->verifyTurnstileCaptcha();
648 - if (is_wp_error($verifyTurnstileCaptchaResult)) {
649 - return $verifyTurnstileCaptchaResult;
650 - }
427 + $formCurrentStep = isset($_POST['form-current-step']) ? $_POST['form-current-step'] : null;
428 +
429 + if (is_wp_error($this->verifyGRecaptcha())) {
430 + return $this->verifyGRecaptcha();
651 431 }
432 +
433 + /* Implement Turnstile Captcha start */
434 + if (is_wp_error($this->verifyTurnstileCaptcha())) {
435 + return $this->verifyTurnstileCaptcha();
436 + }
652 437 /* Implement Turnstile Captcha end */
653 438
654 439 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
655 440
@@ -657,80 +442,39 @@
657 442 do_action('bitform_checked_exist_auth', $this->_form_id, $existAuth);
658 443 if (!is_wp_error($existAuth) && count($existAuth) > 0 && is_user_logged_in()) {
659 444 return new WP_Error('auth_error', __('You are already logged in', 'bit-form'));
660 445 }
661 - $validateForm = $this->validateFormSubmission($postData);
662 - $validateFormFiles = $this->validateFormSubmission($filesData);
446 + $validateForm = $this->validateFormSubmission($_POST);
447 + $validateFormFiles = $this->validateFormSubmission($_FILES);
663 448 $validateForm = array_merge($validateForm, $validateFormFiles);
664 - // Validate only provably-rendered fields: a field stranded in form_content->fields
665 - // with no layout entry (orphan) is never shown to the user and must not block
666 - // submission. getRenderedFields() unions ALL breakpoints × steps × nested layouts
667 - // + childFields of rendered parents, derives only from DB-stored form_content,
668 - // and fails closed (returns all fields) when the layout is unusable.
669 - $form_fields = $this->getRenderedFields();
449 + $form_fields = $this->getFields();
670 450 // check if form-current-step is set and form is multi-step
671 - $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
451 + $formCurrentStep = isset($_POST['form-current-step']) ? $_POST['form-current-step'] : null;
672 452 if (!is_null($formCurrentStep)) {
673 - // Narrow validation to the current step's fields. SECURITY: the step
674 - // key set unions ALL breakpoints (lg/md/sm) — an md/sm-only field was
675 - // previously null-skipped by the validator (silent bypass). A forged
676 - // step index or malformed layout skips the narrowing entirely so every
677 - // rendered field stays validated (fail closed).
678 453 $formContents = $this->getFormContent();
679 - $layout = isset($formContents->layout) ? $formContents->layout : null;
454 + $layout = $formContents->layout;
680 455 $stepIndex = (int) $formCurrentStep - 1;
681 - if (is_array($layout) && isset($layout[$stepIndex]->layout) && is_object($layout[$stepIndex]->layout)) {
682 - $stepLayout = $layout[$stepIndex]->layout;
683 - $nestedLayout = isset($formContents->nestedLayout) && is_object($formContents->nestedLayout)
684 - ? $formContents->nestedLayout : null;
685 - $stepKeys = [];
686 - foreach (['lg', 'md', 'sm'] as $brkpnt) {
687 - if (!isset($stepLayout->{$brkpnt}) || !is_array($stepLayout->{$brkpnt})) {
688 - continue;
456 + $stepLayout = $layout[$stepIndex]->layout->lg;
457 + $nestedLayout = $formContents->nestedLayout;
458 + $step_fields = [];
459 + foreach ($stepLayout as $lay) {
460 + $fk = $lay->i;
461 + if (isset($nestedLayout->{$fk})) {
462 + $nestedLg = $nestedLayout->{$fk}->lg;
463 + foreach ($nestedLg as $nestedLay) {
464 + $nestedFk = $nestedLay->i;
465 + $step_fields[$nestedFk] = $form_fields[$nestedFk];
689 466 }
690 - foreach ($stepLayout->{$brkpnt} as $lay) {
691 - if (!is_object($lay) || !isset($lay->i)) {
692 - continue;
693 - }
694 - $fk = $lay->i;
695 - $stepKeys[$fk] = true;
696 - if (!is_null($nestedLayout) && isset($nestedLayout->{$fk})) {
697 - foreach (['lg', 'md', 'sm'] as $nBrkpnt) {
698 - if (!isset($nestedLayout->{$fk}->{$nBrkpnt}) || !is_array($nestedLayout->{$fk}->{$nBrkpnt})) {
699 - continue;
700 - }
701 - foreach ($nestedLayout->{$fk}->{$nBrkpnt} as $nestedLay) {
702 - if (is_object($nestedLay) && isset($nestedLay->i)) {
703 - $stepKeys[$nestedLay->i] = true;
704 - }
705 - }
706 - }
707 - }
708 - }
709 467 }
710 - // Name/Address/Email/Password children live outside layouts; a child
711 - // is part of this step iff its parent is.
712 - self::expandChildFieldKeys($stepKeys, $form_fields);
713 - if (!empty($stepKeys)) {
714 - $step_fields = [];
715 - foreach (array_keys($stepKeys) as $fk) {
716 - if (isset($form_fields[$fk])) {
717 - $step_fields[$fk] = $form_fields[$fk];
718 - }
719 - }
720 - $form_fields = $step_fields;
721 - }
468 + $step_fields[$fk] = $form_fields[$fk];
722 469 }
470 + $form_fields = $step_fields;
723 471 }
724 - // Only an edit may satisfy a required upload/signature from a `_old` marker.
725 - $editedEntryID = $isEntryEdit && isset($_REQUEST['entryID'])
726 - ? sanitize_text_field(wp_unslash($_REQUEST['entryID']))
727 - : null;
728 - $formFieldValidator = new FormFieldValidator($form_fields, $postData, $filesData, $editedEntryID);
472 + $formFieldValidator = new FormFieldValidator($form_fields, $_POST, $_FILES);
729 473 $validUniuqFields = [];
730 - $existFilter = has_filter('bitform_check_duplicate_entry');
474 + $existFilter = has_filter('bf_check_duplicate_entry');
731 475 if (true === $existFilter) {
732 - $validUniuqFields = apply_filters('bitform_check_duplicate_entry', $form_fields, $postData);
476 + $validUniuqFields = apply_filters('bf_check_duplicate_entry', $form_fields, $_POST);
733 477
734 478 $fieldKeys = array_keys($validUniuqFields);
735 479 $form_fields_keys = array_keys($form_fields);
736 480 $uniqueFields = [];
@@ -738,9 +482,9 @@
738 482 if (in_array($key, $form_fields_keys)) {
739 483 $uniqueFields[] = $form_fields[$key];
740 484 }
741 485 }
742 - do_action('bitform_Unique_entry', $uniqueFields, $validUniuqFields, $this->_form_id, $postData);
486 + do_action('bitform_Unique_entry', $uniqueFields, $validUniuqFields, $this->_form_id, $_POST);
743 487 }
744 488 $validateField = $formFieldValidator->validate('create', $this->_form_id);
745 489
746 490 if ($validateForm && $validateField && 0 === count($validUniuqFields)) {
@@ -767,11 +511,11 @@
767 511 {
768 512 $captchaSettings = $this->getCaptchaSettings();
769 513 $captchaV3Settings = $this->getCaptchaV3Settings();
770 514 if ($captchaSettings || $captchaV3Settings) {
771 - $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
515 + $token = $_POST['g-recaptcha-response'];
772 516 if (!isset($_POST['g-recaptcha-response'])) {
773 - return new WP_Error('spam_detection', __('Please recheck your reCaptcha Configuration', 'bit-form'));
517 + return new WP_Error('spam_detection', __('Please verify reCAPTCHA', 'bit-form'));
774 518 }
775 519 $integrationHandler = new IntegrationHandler(0);
776 520 $allFormIntegrations = $integrationHandler->getAllIntegration('app', $captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3');
777 521 if (!is_wp_error($allFormIntegrations)) {
@@ -776,13 +520,11 @@
776 520 $allFormIntegrations = $integrationHandler->getAllIntegration('app', $captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3');
777 521 if (!is_wp_error($allFormIntegrations)) {
778 522 foreach ($allFormIntegrations as $integration) {
779 523 if (!is_null($integration->integration_type) && $integration->integration_type === ($captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3')) {
780 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
781 - if ($integrationDetails) {
782 - $integrationDetails->id = $integration->id;
783 - $reCAPTCHA = $integrationDetails;
784 - }
524 + $integrationDetails = json_decode($integration->integration_details);
525 + $integrationDetails->id = $integration->id;
526 + $reCAPTCHA = $integrationDetails;
785 527 }
786 528 }
787 529 }
788 530 if (!empty($reCAPTCHA->secretKey)) {
@@ -797,9 +539,12 @@
797 539 && !empty($gRecaptchaResponse->score)
798 540 && ((float) $gRecaptchaResponse->score < (float) $captchaV3Settings->score)
799 541 ) {
800 542 wp_send_json_error(
801 - sanitize_text_field((string) $captchaV3Settings->message)
543 + __(
544 + $captchaV3Settings->message,
545 + 'bit-form'
546 + )
802 547 );
803 548 }
804 549
805 550 $isgReCaptchaVerified = $gRecaptchaResponse->success;
@@ -810,63 +555,16 @@
810 555 }
811 556 }
812 557 }
813 558
814 - private function verifyHCaptcha()
815 - {
816 - $hCaptchaExist = $this->isFieldTypeExist('hcaptcha'); // You can rename this to getHCaptchaSettings() if needed
817 - if ($hCaptchaExist) {
818 - if (!isset($_POST['h-captcha-response'])) {
819 - return new WP_Error('spam_detection', __('Please verify hCaptcha', 'bit-form'));
820 - }
821 -
822 - $token = sanitize_text_field(wp_unslash($_POST['h-captcha-response']));
823 -
824 - $integrationHandler = new IntegrationHandler(0);
825 - $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'hcaptcha');
826 -
827 - if (!is_wp_error($allFormIntegrations)) {
828 - foreach ($allFormIntegrations as $integration) {
829 - if (!is_null($integration->integration_type) && 'hcaptcha' === $integration->integration_type) {
830 - $integrationDetails = Utilities::jsonObj($integration->integration_details);
831 - if ($integrationDetails) {
832 - $integrationDetails->id = $integration->id;
833 - $hCaptcha = $integrationDetails;
834 - }
835 - }
836 - }
837 - }
838 -
839 - if (!empty($hCaptcha->secretKey)) {
840 - $hCaptchaResponse = HttpHelper::post(
841 - 'https://api.hcaptcha.com/siteverify',
842 - [
843 - 'secret' => $hCaptcha->secretKey,
844 - 'response' => $token,
845 - 'remoteip' => (isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '')
846 - ]
847 - );
848 -
849 - $isVerified = false;
850 - if (!is_wp_error($hCaptchaResponse)) {
851 - $isVerified = $hCaptchaResponse->success;
852 - }
853 -
854 - if (!$isVerified) {
855 - return new WP_Error('spam_detection', __('hCaptcha verification failed', 'bit-form'));
856 - }
857 - }
858 - }
859 - }
860 -
861 559 private function verifyTurnstileCaptcha()
862 560 {
863 - $turnstileExist = $this->isFieldTypeExist('turnstile');
864 - if ($turnstileExist) {
561 + $turnstileSetting = $this->getTurnstileSettings();
562 + if ($turnstileSetting) {
865 563 if (!isset($_POST['cf-turnstile-response'])) {
866 564 return new WP_Error('spam_detection', __('Please verify Cloudflare Turnstile Captcha', 'bit-form'));
867 565 }
868 - $token = sanitize_text_field(wp_unslash($_POST['cf-turnstile-response']));
566 + $token = $_POST['cf-turnstile-response'];
869 567 $turnstileCaptcha = null;
870 568 $integrationHandler = new IntegrationHandler(0);
871 569 $turnstileIntegration = $integrationHandler->getAllIntegration('app', 'turnstileCaptcha')[0];
872 570 if (!is_wp_error($turnstileIntegration && !is_null($turnstileIntegration->integration_type))) {
@@ -881,14 +579,12 @@
881 579 ['secret' => $turnstileCaptcha->secretKey, 'response' => $token]
882 580 );
883 581 if (!is_wp_error($turnstileRecaptchaResponse)) {
884 582 if (!$turnstileRecaptchaResponse->success) {
885 - $errorCodes = implode(', ', (array) ($turnstileRecaptchaResponse->{'error-codes'} ?? []));
886 583 wp_send_json_error(
887 - sprintf(
888 - /* translators: %s: dynamic value. */
889 - __('Cloudflare Turnstile Validation Error: %s', 'bit-form'),
890 - $errorCodes
584 + __(
585 + 'Cloudflare Turnstile Validation Error: ' . implode(', ', $turnstileRecaptchaResponse->{'error-codes'}),
586 + 'bit-form'
891 587 )
892 588 );
893 589 }
894 590
@@ -902,13 +598,13 @@
902 598 }
903 599
904 600 public function verifySubmissionNonce()
905 601 {
906 - if (!isset($_POST['t_identity']) || !isset($_POST['csrf'])) {
602 + if (!isset($_POST['t_identity']) && !isset($_POST['csrf'])) {
907 603 return false;
908 604 }
909 - $tIdenty = sanitize_text_field(wp_unslash($_POST['t_identity']));
910 - $csrf = sanitize_text_field(wp_unslash($_POST['csrf']));
605 + $tIdenty = sanitize_text_field($_POST['t_identity']);
606 + $csrf = sanitize_text_field($_POST['csrf']);
911 607 unset($_POST['t_identity'], $_POST['action'], $_POST['bitforms_id'], $_POST['csrf']);
912 608 return Helpers::csrfDecrypted($tIdenty, $csrf);
913 609 }
914 610
@@ -916,9 +612,9 @@
916 612 {
917 613 if (!current_user_can('manage_options')) {
918 614 $update_status = $this->formModel->update(
919 615 [
920 - 'views' => intval($this->form[0]->views) + 1
616 + 'views' => intval(static::$form[0]->views) + 1
921 617 ],
922 618 [
923 619 'id' => $this->form_id
924 620 ]
@@ -925,126 +621,155 @@
925 621 );
926 622 }
927 623 }
928 624
929 - /**
930 - * @param bool $checkedEmptySubmitted whether the empty-submission rule applies here
931 - * @param bool $isEntryEdit true when an existing entry is being updated
932 - */
933 - public function checkSubmissionRestriction($checkedEmptySubmitted = true, $isEntryEdit = false)
625 + public function checkSubmissionRestriction($checkedEmptySubmitted = true)
934 626 {
935 627 $formContents = $this->getFormContent();
936 - $additionalSettings = isset($formContents->additional) ? $formContents->additional : null;
937 - $fromRestrictionSetitingsEnabled = empty($additionalSettings->enabled) ? [] : $additionalSettings->enabled;
938 - $fromRestrictionSetitings = empty($additionalSettings->settings) ? null : $additionalSettings->settings;
939 -
940 - if (is_null($additionalSettings) || is_null($fromRestrictionSetitings) || empty((array) $fromRestrictionSetitingsEnabled)) {
628 + $fromRestrictionSetitingsEnabled = empty($formContents->additional->enabled) ? [] : $formContents->additional->enabled;
629 + $fromRestrictionSetitings = empty($formContents->additional->settings) ? null : $formContents->additional->settings;
630 + if (is_null($formContents->additional->enabled) || is_null($formContents->additional->settings)) {
941 631 return false;
942 632 }
943 -
944 633 $restrictionMessage = [];
945 634 $ipTool = new IpTool();
946 635 $ipAddress = $ipTool->getIP();
947 - $currentUserId = get_current_user_id();
948 -
949 636 foreach ($fromRestrictionSetitingsEnabled as $restrictionKey => $isEnabled) {
950 637 if ($isEnabled) {
951 - // Quota rules gate creating an entry, so an edit skips them; access-control keys stay.
952 - $skippableOnEdit = ['onePerIp', 'entry_limit', 'entry_limit_by_user', 'restrict_form'];
953 - if ($isEntryEdit && in_array($restrictionKey, $skippableOnEdit, true)) {
954 - $skipOnEdit = apply_filters(
955 - 'bitform_skip_restriction_on_entry_edit',
956 - true,
957 - $restrictionKey,
958 - $this->form_id
638 + if ('entry_limit' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
639 + $formEntry = new FormEntryModel();
640 + $countResult = $formEntry->count(
641 + [
642 + 'form_id' => $this->form_id
643 + ]
959 644 );
960 - if ($skipOnEdit) {
961 - continue;
645 + $count = !empty($countResult[0]) && !empty($countResult[0]->count) ? $countResult[0]->count : false;
646 + if ($count && $count >= intval($fromRestrictionSetitings->{$restrictionKey})) {
647 + $restrictionMessage[] = __('Sorry!! Entry limit exceeded', 'bit-form');
962 648 }
963 649 }
964 - /**
965 - * Allow add-ons to handle any restriction key (Pro-only restrictions
966 - * should be implemented in the add-on, not shipped in the free plugin).
967 - *
968 - * Return a non-null string to block submission.
969 - */
970 - $addonMsg = apply_filters(
971 - 'bitform_submission_restriction',
972 - null,
973 - $restrictionKey,
974 - $this->form_id,
975 - $fromRestrictionSetitingsEnabled,
976 - $fromRestrictionSetitings,
977 - $ipAddress,
978 - $currentUserId
979 - );
980 -
981 - if (!is_null($addonMsg) && '' !== $addonMsg) {
982 - $restrictionMessage[] = $addonMsg;
983 - continue;
984 - }
985 -
986 650 if ('onePerIp' === $restrictionKey) {
987 651 $formEntry = new FormEntryModel();
988 -
989 - $getResult = $formEntry->get(
990 - ['user_ip', 'status'],
652 + $countResult = $formEntry->count(
991 653 [
992 654 'form_id' => $this->form_id,
993 - 'user_ip' => (int) ip2long((string) $ipAddress)
994 - ],
655 + 'user_ip' => ip2long($ipAddress)
656 + ]
995 657 );
658 + $count = !empty($countResult[0]) && !empty($countResult[0]->count) ? $countResult[0]->count : false;
996 659
997 - $count = 0;
998 - $status = 0;
660 + if ($count && $count > 0) {
661 + $restrictionMessage[] = __('Sorry!! You have already submitted', 'bit-form');
662 + }
663 + }
664 + if ('is_login' === $restrictionKey && 0 === get_current_user_id()) {
665 + $restrictionMessage[] = __($fromRestrictionSetitings->is_login->message, 'bit-form');
666 + }
667 + if ($checkedEmptySubmitted && 'empty_submission' === $restrictionKey) {
668 + $isEmpty = $this->checkEmptySubmission($_POST, $_FILES);
669 + if ($isEmpty) {
670 + $restrictionMessage[] = __($fromRestrictionSetitings->empty_submission->message, 'bit-form');
671 + }
672 + }
673 + if ('restrict_form' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
674 + $day = empty($fromRestrictionSetitings->{$restrictionKey}->day) ? null : $fromRestrictionSetitings->{$restrictionKey}->day;
675 + $date = empty($fromRestrictionSetitings->{$restrictionKey}->date) ? null : $fromRestrictionSetitings->{$restrictionKey}->date;
676 + $time = empty($fromRestrictionSetitings->{$restrictionKey}->time) ? null : $fromRestrictionSetitings->{$restrictionKey}->time;
999 677
1000 - if (!is_wp_error($getResult) && count($getResult) > 0) {
1001 - $count = count($getResult);
1002 -
1003 - foreach ($getResult as $row) {
1004 - if (9 === (int) $row->status) {
1005 - $status = 9;
1006 - break;
678 + $isdayOk = $isdateOk = $istimeOk = true;
679 + $dayNotOkMsg = $dateNotOkMsg = $timeNotOkMsg = '';
680 + $dateTimeHelper = new DateTimeHelper();
681 + if (
682 + !empty($day)
683 + && is_array($day)
684 + && (in_array('Friday', $day)
685 + || in_array('Saturday', $day)
686 + || in_array('Sunday', $day)
687 + || in_array('Monday', $day)
688 + || in_array('Tuesday', $day)
689 + || in_array('Wednesday', $day)
690 + || in_array('Thursday', $day))
691 + && (!in_array($dateTimeHelper->getDay('full-name'), $day))
692 + ) {
693 + $isdayOk = false;
694 + $dayMsgVarsFormat = '';
695 + foreach ($day as $dayIndex => $dayValue) {
696 + if ($dayIndex > 0) {
697 + $dayMsgVarsFormat .= ', ';
1007 698 }
699 + $dayMsgVarsFormat .= '%s';
1008 700 }
701 + $dayNotOkMsg = vsprintf(__("in $dayMsgVarsFormat", 'bit-form'), $day);
1009 702 }
703 + if (
704 + !empty($day)
705 + && is_array($day)
706 + && (in_array('Custom', $day))
707 + ) {
708 + $startDate = empty($date->from) ? '00-00-0000' : $date->from;
709 + $endDate = empty($date->to) ? '00-00-0000' : $date->to;
710 + $dateFormat = preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $startDate) ? 'Y-m-d' : 'm-d-Y';
711 + if (!empty($date->from) && false !== strpos($startDate, 'T')) {
712 + $startDate = $dateTimeHelper->getDate($startDate, false, null, $dateFormat);
713 + }
714 + if (!empty($date->to) && false !== strpos($endDate, 'T')) {
715 + $endDate = $dateTimeHelper->getDate($endDate, false, null, $dateFormat);
716 + }
717 + $currentDate = $dateTimeHelper->getDate(null, null, null, $dateFormat);
718 + if (!($currentDate >= $startDate && $currentDate <= $endDate)) {
719 + $isdateOk = false;
720 + $dateNotOkMsg = sprintf(__('within %s to %s', 'bit-form'), $startDate, $endDate);
721 + }
722 + }
1010 723
1011 - if ($count > 0 && 9 !== (int) $status) {
1012 - $onePerIp = __('Sorry!! You have already submitted from this IP address', 'bit-form');
724 + if (!empty($time)) {
725 + $startTime = empty($time->from) ? '00:00' : $time->from;
726 + $endTime = empty($time->to) ? '23:59.999' : $time->to;
727 + $currentTime = $dateTimeHelper->getTime(null, null, null, 'H:i');
728 + if (!($currentTime >= $startTime && $currentTime <= $endTime)) {
729 + $istimeOk = false;
730 + $startTime = $dateTimeHelper->getTime($startTime, 'H:i', null);
731 + $endTime = $dateTimeHelper->getTime($endTime, 'H:i', null);
732 + $isTimeOk = false;
733 + $timeNotOkMsg = sprintf(__('%s to %s', 'bit-form'), $startTime, $endTime);
734 + }
735 + }
1013 736
1014 - $onePerIp = apply_filters(
1015 - 'bitform_filter_restriction_one_per_ip_message',
1016 - $onePerIp,
1017 - $this->form_id
1018 - );
1019 -
1020 - $restrictionMessage[] = $onePerIp;
737 + if (!($isdateOk && $isdayOk && $istimeOk)) {
738 + if (!$isdayOk) {
739 + $restrictionMessage[] = !empty($timeNotOkMsg) ? sprintf(__('Form is available %s From %s', 'bit-form'), $dayNotOkMsg, $timeNotOkMsg) :
740 + sprintf(__('Form is available %s', 'bit-form'), $dayNotOkMsg, $timeNotOkMsg);
741 + } elseif (!$isdateOk) {
742 + $restrictionMessage[] = !empty($timeNotOkMsg) ? sprintf(__('Form is available %s From %s', 'bit-form'), $dateNotOkMsg, $timeNotOkMsg) :
743 + sprintf(__('Form is available %s', 'bit-form'), $dateNotOkMsg, $timeNotOkMsg);
744 + } elseif (!$istimeOk) {
745 + $restrictionMessage[] = sprintf(__('Form is available on %s', 'bit-form'), $timeNotOkMsg);
746 + }
1021 747 }
1022 748 }
1023 - if ('is_login' === $restrictionKey && 0 === get_current_user_id()) {
1024 - $is_login_messages = $fromRestrictionSetitings->is_login->message;
1025 -
1026 - $is_login_messages = apply_filters(
1027 - 'bitform_filter_restriction_is_login_message',
1028 - $is_login_messages,
1029 - $this->form_id
1030 - );
1031 -
1032 - $restrictionMessage[] = $is_login_messages;
749 + if ('blocked_ip' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
750 + $isIpBlocked = false;
751 + foreach ($fromRestrictionSetitings->{$restrictionKey} as $ipIndex => $ipDetails) {
752 + if (!empty($ipDetails->status) && $ipDetails->status && !empty($ipDetails->ip) && $ipDetails->ip === $ipAddress) {
753 + $isIpBlocked = true;
754 + break;
755 + }
756 + }
757 + if ($isIpBlocked) {
758 + $restrictionMessage[] = sprintf(__('Sorry!! Your IP address is %s, Blocked from submitting the form', 'bit-form'), $ipAddress);
759 + }
1033 760 }
1034 - if ($checkedEmptySubmitted && 'empty_submission' === $restrictionKey) {
1035 - $isEmpty = $this->checkEmptySubmission(wp_unslash($_POST), GlobalHelper::sanitize_files_input($_FILES), $isEntryEdit);
1036 - if ($isEmpty) {
1037 - $restriction = $fromRestrictionSetitings->empty_submission->message;
1038 -
1039 - $restriction = apply_filters(
1040 - 'bitform_filter_restriction_empty_submission_message',
1041 - $restriction,
1042 - $this->form_id
1043 - );
1044 -
1045 - $restrictionMessage[] = $restriction;
761 + if ('private_ip' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
762 + $isIpWhiteListed = false;
763 + foreach ($fromRestrictionSetitings->{$restrictionKey} as $ipIndex => $ipDetails) {
764 + if (!empty($ipDetails->status) && $ipDetails->status && !empty($ipDetails->ip) && $ipDetails->ip === $ipAddress) {
765 + $isIpWhiteListed = true;
766 + break;
767 + }
1046 768 }
769 + if (!$isIpWhiteListed) {
770 + $restrictionMessage[] = sprintf(__('Sorry!! Your IP address is %s, Blocked from submitting the form', 'bit-form'), $ipAddress);
771 + }
1047 772 }
1048 773 }
1049 774 }
1050 775 return $restrictionMessage;
@@ -1050,21 +775,51 @@
1050 775 return $restrictionMessage;
1051 776 }
1052 777
1053 778 /**
1054 - * Will check if form is submitted by a bot
1055 - *
1056 - * @return Boolean true - if submitted by bot else false
1057 - */
779 + * Will check if form is submitted by a bot
780 + *
781 + * @return Boolean true - if submitted by bot else false
782 + */
1058 783 public function isTrappedInHoneypot()
1059 784 {
1060 - // Honeypot is implemented by add-ons (e.g. Pro) via filter.
1061 - return (bool) apply_filters('bitform_check_honeypot', false, $this->_form_id, wp_unslash($_POST));
785 + $isHoneyPot = false;
786 +
787 + if (!$this->isHoneypotActive()) {
788 + return false;
789 + }
790 +
791 + $token = $_POST['b_h_t'];
792 + $pattern = '/^([a-zA-Z0-9]*_[a-zA-Z0-9]*){4}$/';
793 + $decryptedToken = base64_decode(base64_decode($token));
794 +
795 + preg_match($pattern, $decryptedToken, $validToken);
796 +
797 + if ($validToken) {
798 + if (isset($_POST[$token]) && empty($_POST[$token])) {
799 + $isHoneyPot = false;
800 + } else {
801 + $isHoneyPot = true;
802 + }
803 + } else {
804 + $isHoneyPot = true;
805 + }
806 +
807 + if (isset($_POST[$token])) {
808 + unset($_POST[$token]);
809 + }
810 + unset($_POST['b_h_t']);
811 + return $isHoneyPot;
1062 812 }
1063 813
1064 814 public function isHoneypotActive()
1065 815 {
1066 - return (bool) apply_filters('bitform_is_honeypot_active', false, $this->_form_id, $this->getFormContent());
816 + $formContents = $this->getFormContent();
817 + $enabled = empty($formContents->additional->enabled) ? null : $formContents->additional->enabled;
818 + if (!empty($enabled->honeypot) && $enabled->honeypot) {
819 + return true;
820 + }
821 + return false;
1067 822 }
1068 823
1069 824 public function checkPaymentFields()
1070 825 {
@@ -1072,14 +827,11 @@
1072 827 $fields = $formContents->fields;
1073 828
1074 829 $payments = [];
1075 830 foreach ($fields as $fldData) {
1076 - if (!is_object($fldData)) {
1077 - continue;
1078 - }
1079 831 if ('paypal' === $fldData->typ && property_exists($fldData, 'payIntegID')) {
1080 832 $payments['paypalKey'] = $this->getClientKey($fldData->payIntegID, 'clientID');
1081 - } elseif ('razorpay' === $fldData->typ && isset($fldData->options) && is_object($fldData->options) && property_exists($fldData->options, 'payIntegID')) {
833 + } elseif ('razorpay' === $fldData->typ && property_exists($fldData->options, 'payIntegID')) {
1082 834 $payments['razorpayKey'] = $this->getClientKey($fldData->options->payIntegID, 'apiKey');
1083 835 }
1084 836 }
1085 837
@@ -1092,13 +844,10 @@
1092 844 if (!empty($integID)) {
1093 845 $integrationHandler = new IntegrationHandler(0);
1094 846 $integration = $integrationHandler->getAIntegration($integID, 'app', 'payments');
1095 847 if (!is_wp_error($integration)) {
1096 - $integrationRow = Utilities::firstRow($integration);
1097 - $integration_details = Utilities::jsonObj($integrationRow->integration_details ?? '');
1098 - if ($integration_details && isset($integration_details->{$keyName})) {
1099 - $client = base64_encode($integration_details->{$keyName});
1100 - }
848 + $integration_details = json_decode($integration[0]->integration_details);
849 + $client = base64_encode($integration_details->{$keyName});
1101 850 }
1102 851 }
1103 852 return $client;
1104 853 }
@@ -1104,11 +853,38 @@
1104 853 }
1105 854
1106 855 public function getSuccessMessageMarkups()
1107 856 {
857 + if (is_null($this->_work_flows)) {
858 + $workFlowManager = new WorkFlowHandler($this->form_id);
859 + $this->_work_flows = $workFlowManager->getAllworkFlow();
860 + }
861 +
862 + $ids = [];
863 + foreach ($this->_work_flows as $msgItem) {
864 + foreach ($msgItem['conditions'] as $condition) {
865 + if (isset($condition->actions->success)) {
866 + foreach ($condition->actions->success as $msg) {
867 + if ('successMsg' === $msg->type && isset($msg->details->id)) {
868 + $idObj = json_decode(stripslashes($msg->details->id));
869 + if (is_object($idObj) && !empty($idObj->id)) {
870 + array_push($ids, $idObj->id);
871 + }
872 + }
873 + }
874 + }
875 + if (isset($condition->actions->failure)) {
876 + $idObj = json_decode(stripslashes($condition->actions->failure));
877 + if (is_object($idObj) && !empty($idObj->id)) {
878 + array_push($ids, $idObj->id);
879 + }
880 + }
881 + }
882 + }
883 + $ids = array_unique($ids);
1108 884 if (is_null($this->_conf_messages)) {
1109 885 $successMsgHandler = new SuccessMessageHandler($this->form_id);
1110 - $this->_conf_messages = $successMsgHandler->getAllMessage();
886 + $this->_conf_messages = $successMsgHandler->getMessages($ids);
1111 887 }
1112 888
1113 889 $messageMarkups = '';
1114 890 if (is_wp_error($this->_conf_messages)) {
@@ -1115,12 +891,8 @@
1115 891 return $messageMarkups;
1116 892 }
1117 893
1118 894 foreach ($this->_conf_messages as $msgItem) {
1119 - $msgConfig = json_decode($msgItem->message_config);
1120 - if (is_object($msgConfig) && property_exists($msgConfig, 'status') && empty($msgConfig->status)) {
1121 - continue;
1122 - }
1123 895 $messageMarkups .= $this->messageMarkup($msgItem);
1124 896 }
1125 897
1126 898 return $messageMarkups;
@@ -1127,15 +899,26 @@
1127 899 }
1128 900
1129 901 public function getFormAbandonmentMessage()
1130 902 {
1131 - $msg = apply_filters('bitform_form_abandonment_warning_markup', '', $this->form_id);
1132 - return is_string($msg) ? $msg : '';
903 + if (class_exists('\BitCode\BitFormPro\Admin\FormSettings\FormAbandonment')) {
904 + $formAbandonmentSettings = FormAbandonment::getFormAbandonmentSettings($this->form_id);
905 + $msg = '';
906 + if (isset($formAbandonmentSettings->showWarningMsg) && $formAbandonmentSettings->showWarningMsg && !empty($formAbandonmentSettings->warningMsg)) {
907 + $msg = $formAbandonmentSettings->warningMsg;
908 + $msg = '<div class="bf-form-msg active warning">' . wp_kses_post($msg) . '</div>';
909 + }
910 + return $msg;
911 + }
1133 912 }
1134 913
1135 914 public function getFormAbandonmentSettings()
1136 915 {
1137 - return apply_filters('bitform_form_abandonment_settings', null, $this->form_id);
916 + if (class_exists('\BitCode\BitFormPro\Admin\FormSettings\FormAbandonment')) {
917 + $formAbandonmentSettings = FormAbandonment::getFormAbandonmentSettings($this->form_id);
918 + return $formAbandonmentSettings;
919 + }
920 + return null;
1138 921 }
1139 922
1140 923 private function messageMarkup($msg)
1141 924 {
@@ -1140,34 +923,23 @@
1140 923 private function messageMarkup($msg)
1141 924 {
1142 925 $msgId = $msg->id;
1143 926 $msgConfig = json_decode($msg->message_config);
1144 - $msgType = (is_object($msgConfig) && isset($msgConfig->msgType)) ? $msgConfig->msgType : 'below';
1145 - $scrollClass = 'below' === $msgType ? 'scroll' : '';
927 + $scrollClass = 'below' === $msgConfig->msgType ? 'scroll' : '';
1146 928
1147 - return '<div
1148 - role="dialog"
1149 - aria-hidden="true"
1150 - data-modal-backdrop="true"
1151 - class="' . $this->getAtomicCls("msg-container-{$msgId}") . ' deactive ' . $scrollClass . '">
1152 - <div
1153 - data-contentid="' . $this->getFormIdentifier() . '"
1154 - data-msgid="' . $msgId . '"
1155 - role="button"
1156 - class="' . $this->getAtomicCls("msg-background-{$msgId}") . ' msg-backdrop">
1157 - <div class="bf-msg-content ' . $this->getAtomicCls("msg-content-{$msgId}") . '">
1158 - <button
1159 - data-contentid="' . $this->getFormIdentifier() . '"
1160 - data-msgid="' . $msgId . '"
1161 - class="' . $this->getAtomicCls("close-{$msgId}") . ' bf-msg-close"
1162 - type="button">
1163 - <svg class="' . $this->getAtomicCls("close-icn-{$msgId}") . '" viewBox="0 0 30 30">
1164 - <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
1165 - <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
1166 - </svg>
1167 - </button>
1168 - <div class="msg-content"></div>
1169 - </div>
1170 - </div>
1171 - </div>';
929 + return <<<SUCCESSMSG
930 + <div role="dialog" aria-hidden="true" data-modal-backdrop="true" class="{$this->getAtomicCls("msg-container-{$msgId}")} deactive {$scrollClass}">
931 + <div data-contentid="{$this->getFormIdentifier()}" data-msgid="{$msgId}" role="button" class="{$this->getAtomicCls("msg-background-{$msgId}")} msg-backdrop">
932 + <div class="bf-msg-content {$this->getAtomicCls("msg-content-{$msgId}")}">
933 + <button data-contentid="{$this->getFormIdentifier()}" data-msgid="{$msgId}" class="{$this->getAtomicCls("close-{$msgId}")} bf-msg-close" type="button">
934 + <svg class="{$this->getAtomicCls("close-icn-{$msgId}")}" viewBox="0 0 30 30">
935 + <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
936 + <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
937 + </svg>
938 + </button>
939 + <div class="msg-content"></div>
940 + </div>
941 + </div>
942 + </div>
943 +SUCCESSMSG;
1172 944 }
1173 945 }