PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.3.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.3.1
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 +168 -12 V-3.3.03.3.1 View file →
@@ -17,9 +17,11 @@
17 17 use BitCode\BitForm\Core\Form\Validator\FormFieldValidator;
18 18 use BitCode\BitForm\Core\Integration\IntegrationHandler;
19 19 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
20 20 use BitCode\BitForm\Core\Util\ApiResponse as UtilApiResponse;
21 +use BitCode\BitForm\Core\Util\EscapingHelper;
21 22 use BitCode\BitForm\Core\Util\FieldValueHandler;
23 +use BitCode\BitForm\Core\Util\FrontendHelpers;
22 24 use BitCode\BitForm\Core\Util\HttpHelper;
23 25 use BitCode\BitForm\Core\Util\IpTool;
24 26 use BitCode\BitForm\Core\Util\Utilities;
25 27 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
@@ -228,8 +230,74 @@
228 230
229 231 return $post;
230 232 }
231 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 +
232 300 public function handleSubmission()
233 301 {
234 302 // CSRF verified via verifySubmissionNonce() before this method is called. All $_POST reads below occur after that verification.
235 303 $this->fieldNameReplaceOfPost();
@@ -239,9 +307,9 @@
239 307 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
240 308
241 309 if (true === $validated) {
242 310 do_action('bitform_validation_success', $this->_form_id);
243 - unset($_POST['hidden_fields']);
311 + $this->discardHiddenFieldValues();
244 312
245 313 $redirectPage = '';
246 314 $regSuccMsg = '';
247 315
@@ -251,17 +319,18 @@
251 319 $parameter = $this->getParams();
252 320 $existAuthFilter = has_filter('bitform_wp_user_auth');
253 321
254 322 if (true === $existAuthFilter) {
255 - $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $unslashed_post, $parameter);
323 + $authPostData = $this->resolveConfirmChildValues($unslashed_post);
324 + $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $authPostData, $parameter);
256 325
257 - $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $this->_form_id, $unslashed_post, $parameter);
326 + $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $this->_form_id, $authPostData, $parameter);
258 327
259 - do_action('bitform_wp_user_auth_response', $result, $this->_form_id, $unslashed_post, $parameter);
328 + do_action('bitform_wp_user_auth_response', $result, $this->_form_id, $authPostData, $parameter);
260 329
261 330 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
262 331 if (!$result['success']) {
263 - return new WP_Error('errors', esc_html($result['message']));
332 + return new WP_Error('errors', self::authErrorMessage($result['message']));
264 333 } elseif (isset($result['success'])) {
265 334 $redirectPage = $result['redirectPage'];
266 335 $regSuccMsg = $result['message'];
267 336 }
@@ -266,9 +335,9 @@
266 335 $regSuccMsg = $result['message'];
267 336 }
268 337 } else {
269 338 if (!$result['success']) {
270 - return new WP_Error('errors', esc_html($result['message']));
339 + return new WP_Error('errors', self::authErrorMessage($result['message']));
271 340 } else {
272 341 return $result;
273 342 }
274 343 }
@@ -350,9 +419,10 @@
350 419 return new WP_Error('empty_form', __('Entries id is invalid', 'bit-form'));
351 420 }
352 421 if (true === $validated) {
353 422 do_action('bitform_validation_success', $this->_form_id);
354 - unset($_POST['hidden_fields'], $_POST['entryID']);
423 + $this->discardHiddenFieldValues();
424 + unset($_POST['entryID']);
355 425
356 426 $redirectPage = '';
357 427 $regSuccMsg = '';
358 428 $postData = wp_unslash($_POST);
@@ -362,13 +432,14 @@
362 432 $parameter = $this->getParams();
363 433 $existAuthFilter = has_filter('bitform_wp_user_auth');
364 434
365 435 if (true === $existAuthFilter) {
366 - $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $postData, $parameter);
436 + $authPostData = $this->resolveConfirmChildValues($postData);
437 + $result = apply_filters('bitform_wp_user_auth', $existAuth[0], $authPostData, $parameter);
367 438
368 439 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
369 440 if (!$result['success']) {
370 - return new WP_Error('errors', esc_html($result['message']));
441 + return new WP_Error('errors', self::authErrorMessage($result['message']));
371 442 } elseif (isset($result['success'])) {
372 443 $redirectPage = $result['redirectPage'];
373 444 $regSuccMsg = $result['message'];
374 445 }
@@ -373,9 +444,9 @@
373 444 $regSuccMsg = $result['message'];
374 445 }
375 446 } else {
376 447 if (!$result['success']) {
377 - return new WP_Error('errors', esc_html($result['message']));
448 + return new WP_Error('errors', self::authErrorMessage($result['message']));
378 449 } else {
379 450 return $result;
380 451 }
381 452 }
@@ -444,11 +515,96 @@
444 515 do_action('bitform_validation_error', $this->_form_id, $validated);
445 516 return $validated;
446 517 }
447 518
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 +
448 604 public function validateFormSubmission($submitted_data)
449 605 {
450 - $hidden_fields = isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '';
606 + $hidden_fields = FrontendHelpers::parseHiddenFieldKeys(isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '');
451 607 $submitted_fields = $this->getSubmittedFields($submitted_data);
452 608 $form_fields = $this->getFields();
453 609 $form_fields_names = array_keys($form_fields);
454 610 if ($this->isGCLIDEnabled()) {
@@ -454,9 +610,9 @@
454 610 if ($this->isGCLIDEnabled()) {
455 611 array_push($form_fields_names, 'GCLID');
456 612 }
457 613 foreach ($submitted_fields as $field) {
458 - if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || false !== strpos($hidden_fields, $field)) {
614 + if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || FrontendHelpers::isFieldHidden($hidden_fields, $field)) {
459 615 unset($submitted_data[$field]);
460 616 }
461 617 }
462 618 return $submitted_data;