| @@ -8,8 +8,9 @@ | ||
| 8 | 8 | |
| 9 | 9 | use BitCode\BitForm\Admin\Form\AdminFormHandler; |
| 10 | 10 | use BitCode\BitForm\Admin\Form\FrontEndScriptGenerator; |
| 11 | 11 | use BitCode\BitForm\Admin\Form\Helpers; |
| 12 | +use BitCode\BitForm\Core\Cryptography\Cryptography; | |
| 12 | 13 | use BitCode\BitForm\Core\Database\FormEntryMetaModel; |
| 13 | 14 | use BitCode\BitForm\Core\Database\FormModel; |
| 14 | 15 | use BitCode\BitForm\Core\Form\FormManager; |
| 15 | 16 | use BitCode\BitForm\Core\Integration\IntegrationHandler; |
| @@ -40,26 +41,32 @@ | ||
| 40 | 41 | // their formID), before wp_print_footer_scripts at 20. |
| 41 | 42 | add_action('wp_footer', [$this, 'generateJS'], 15); |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | - private function validPassowordResetToken($token, $userID, $formId) | |
| 45 | + /** | |
| 46 | + * A reset form opened from a used or expired link cannot succeed, so show why instead of | |
| 47 | + * the form. Returns the notice markup, or '' when the link is fine or the form is not a | |
| 48 | + * reset form. Rendered in place of the form (the old exit() cut the page off mid-theme). | |
| 49 | + * | |
| 50 | + * @param string $token key from the reset link | |
| 51 | + * @param string $userID user ID from the reset link | |
| 52 | + * @param int $formId form being rendered | |
| 53 | + * | |
| 54 | + * @return string | |
| 55 | + */ | |
| 56 | + private function invalidPasswordResetNotice($token, $userID, $formId) | |
| 45 | 57 | { |
| 46 | 58 | $existResetInteg = (new IntegrationHandler($formId))->getAllIntegration('wp_user_auth', 'wp_auth', 1); |
| 47 | - if (!is_wp_error($existResetInteg) && count($existResetInteg) > 0) { | |
| 48 | - if ('reset' === $existResetInteg[0]->integration_name) { | |
| 49 | - $user = get_userdata($userID); | |
| 50 | - if ($user) { | |
| 51 | - $validKey = check_password_reset_key($token, $user->user_login); | |
| 52 | - if (is_wp_error($validKey)) { | |
| 53 | - echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>This password reset token is invalid.</div>"; | |
| 54 | - exit(); | |
| 55 | - } | |
| 56 | - } else { | |
| 57 | - echo "<div id='bf-resp' style='display:grid;justify-content:center;color:#860000;'>Invalid User!!</div>"; | |
| 58 | - exit(); | |
| 59 | - } | |
| 60 | - } | |
| 59 | + if (is_wp_error($existResetInteg) || empty($existResetInteg) || 'reset' !== $existResetInteg[0]->integration_name) { | |
| 60 | + return ''; | |
| 61 | 61 | } |
| 62 | + $user = get_userdata((int) $userID); | |
| 63 | + if ($user && !is_wp_error(check_password_reset_key($token, $user->user_login))) { | |
| 64 | + return ''; | |
| 65 | + } | |
| 66 | + return "<div id='bf-resp' role='alert' style='display:grid;justify-content:center;color:#860000;'>" | |
| 67 | + . esc_html__('This password reset link is invalid or has expired. Please request a new one.', 'bit-form') | |
| 68 | + . '</div>'; | |
| 62 | 69 | } |
| 63 | 70 | |
| 64 | 71 | private function getJSFileSrc($postId) |
| 65 | 72 | { |
| @@ -410,10 +417,13 @@ | ||
| 410 | 417 | /* translators: %s: form ID */ |
| 411 | 418 | return sprintf(__('#%s no. Form doesn\'t exists', 'bit-form'), $formID); |
| 412 | 419 | } |
| 413 | 420 | |
| 414 | - // Add-ons may detect whether the current visitor is resuming an abandoned entry. | |
| 415 | - $isAbandoned = (bool) apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts); | |
| 421 | + // Add-ons return the id of the abandoned entry this visitor can resume. A bare | |
| 422 | + // true (older Pro) carries no id, so nothing is restored rather than entry #1. | |
| 423 | + $abandonedEntryId = apply_filters('bitform_is_abandoned_entry', false, $formID, $entryId, $atts); | |
| 424 | + $abandonedEntryId = (!is_bool($abandonedEntryId) && is_numeric($abandonedEntryId)) ? (int) $abandonedEntryId : 0; | |
| 425 | + $isAbandoned = $abandonedEntryId > 0; | |
| 416 | 426 | |
| 417 | 427 | FrontendHelpers::setBfFrontendFormIds($formID); |
| 418 | 428 | $bfFrontendFormIds = FrontendHelpers::$bfFrontendFormIds; |
| 419 | 429 | $shortCodeCounter = count($bfFrontendFormIds); |
| @@ -433,9 +443,13 @@ | ||
| 433 | 443 | } |
| 434 | 444 | |
| 435 | 445 | // Read-only: password reset token from URL for display-time validation. No state written until form is submitted. |
| 436 | 446 | if (!empty($_GET['token']) && !empty($_GET['id'])) { |
| 437 | - $this->validPassowordResetToken(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID); | |
| 447 | + $resetNotice = $this->invalidPasswordResetNotice(sanitize_text_field(wp_unslash($_GET['token'])), sanitize_text_field(wp_unslash($_GET['id'])), $formID); | |
| 448 | + if ('' !== $resetNotice) { | |
| 449 | + ob_end_clean(); | |
| 450 | + return $resetNotice; | |
| 451 | + } | |
| 438 | 452 | } |
| 439 | 453 | |
| 440 | 454 | $previousValue = $this->getValuesFromQueryParams(); |
| 441 | 455 | $errorMessages = []; // delete |
| @@ -616,14 +630,19 @@ | ||
| 616 | 630 | if (isset($additional->enabled->validateFocusLost)) { |
| 617 | 631 | $bitFormFrontArr['validateFocusLost'] = true; |
| 618 | 632 | } |
| 619 | 633 | |
| 620 | - if (!empty($isAbandoned)) { | |
| 634 | + if ($isAbandoned) { | |
| 621 | 635 | // One visitor's typed values, so this response must not be page-cached. |
| 622 | - $bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $isAbandoned); | |
| 636 | + $bitFormFrontArr['oldValues'] = $this->getFieldsValue($formID, $abandonedEntryId); | |
| 623 | 637 | self::markResponseUncacheable(); |
| 624 | 638 | if (empty($entryId)) { |
| 625 | - $bitFormFrontArr['entryId'] = $entryId; | |
| 639 | + // Later draft saves must update this entry, not start a new one each time. | |
| 640 | + $bitFormFrontArr['entryId'] = $abandonedEntryId; | |
| 641 | + $entryToken = Cryptography::encrypt($abandonedEntryId, Helpers::getBitformSalt()); | |
| 642 | + if (is_string($entryToken)) { | |
| 643 | + $bitFormFrontArr['entryToken'] = $entryToken; | |
| 644 | + } | |
| 626 | 645 | } |
| 627 | 646 | } |
| 628 | 647 | |
| 629 | 648 | $formInfo = $FrontendFormManager->getFormInfo(); |
| @@ -890,8 +909,12 @@ | ||
| 890 | 909 | } else { |
| 891 | 910 | $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal); |
| 892 | 911 | } |
| 893 | 912 | } |
| 913 | + // A restored draft never gets the password back; the visitor types it again. | |
| 914 | + if (isset($formFields[$metaKey]['type']) && 'password' === $formFields[$metaKey]['type']) { | |
| 915 | + continue; | |
| 916 | + } | |
| 894 | 917 | if (!isset($fldsData->{$metaKey})) { |
| 895 | 918 | $fldsData->{$metaKey} = ''; |
| 896 | 919 | } |
| 897 | 920 | $fldsData->{$metaKey} = $metaVal; |
| @@ -944,8 +967,16 @@ | ||
| 944 | 967 | } else { |
| 945 | 968 | $metaVal = implode(BITFORMS_BF_SEPARATOR, $metaVal); |
| 946 | 969 | } |
| 947 | 970 | } |
| 971 | + if (property_exists($fields, $metaKey) && 'password' === $fields->{$metaKey}->typ) { | |
| 972 | + // Never hand a stored password back to the browser; blank keeps it on update. | |
| 973 | + $fields->{$metaKey}->val = ''; | |
| 974 | + if ('' !== (string) $metaValue->meta_value && isset($fields->{$metaKey}->valid) && is_object($fields->{$metaKey}->valid)) { | |
| 975 | + $fields->{$metaKey}->valid->req = false; | |
| 976 | + } | |
| 977 | + continue; | |
| 978 | + } | |
| 948 | 979 | if (property_exists($fields, $metaKey)) { |
| 949 | 980 | $fields->{$metaKey}->val = $metaVal; |
| 950 | 981 | if ('file-up' === $fields->{$metaKey}->typ || 'advanced-file-up' === $fields->{$metaKey}->typ) { |
| 951 | 982 | $fields->{$metaKey}->val = $metaValue->meta_value; |
| @@ -1027,9 +1058,9 @@ | ||
| 1027 | 1058 | $formUpdateVersion = get_option('bitform_form_update_version'); |
| 1028 | 1059 | if (!wp_style_is('bitform-style-' . $newFormId) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-' . $newFormId . '.css')) { |
| 1029 | 1060 | wp_enqueue_style( |
| 1030 | 1061 | 'bitform-style-' . $newFormId, |
| 1031 | - BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-{$newFormId}.css", | |
| 1062 | + FrontendHelpers::styleSrc("form-styles/bitform-{$newFormId}.css"), | |
| 1032 | 1063 | [], |
| 1033 | 1064 | $formUpdateVersion |
| 1034 | 1065 | ); |
| 1035 | 1066 | if ($isPageBuilder) { |
| @@ -1039,9 +1070,9 @@ | ||
| 1039 | 1070 | } |
| 1040 | 1071 | if (!wp_style_is('bitform-style-custom-' . $formID) && is_readable(BITFORMS_CONTENT_DIR . '/form-styles/bitform-custom-' . $formID . '.css')) { |
| 1041 | 1072 | wp_enqueue_style( |
| 1042 | 1073 | 'bitform-style-custom-' . $formID, |
| 1043 | - BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-custom-{$formID}.css", | |
| 1074 | + FrontendHelpers::styleSrc("form-styles/bitform-custom-{$formID}.css"), | |
| 1044 | 1075 | [], |
| 1045 | 1076 | $formUpdateVersion |
| 1046 | 1077 | ); |
| 1047 | 1078 | if ($isPageBuilder) { |
| @@ -1054,9 +1085,9 @@ | ||
| 1054 | 1085 | if (!wp_style_is('bitform-conversational-style-' . $formID) && |
| 1055 | 1086 | is_readable(BITFORMS_CONTENT_DIR . "/form-styles/bitform-conversational-{$formID}.css")) { |
| 1056 | 1087 | wp_enqueue_style( |
| 1057 | 1088 | 'bitform-conversational-style', |
| 1058 | - BITFORMS_UPLOAD_BASE_URL . "/form-styles/bitform-conversational-{$formID}.css", | |
| 1089 | + FrontendHelpers::styleSrc("form-styles/bitform-conversational-{$formID}.css"), | |
| 1059 | 1090 | [], |
| 1060 | 1091 | $formUpdateVersion |
| 1061 | 1092 | ); |
| 1062 | 1093 | } |