← All changes
|
front-end/default-fields/recaptcha/recaptcha.php
+607
-100
3.9.8
→
4.0.3
View file →
| @@ -28,10 +28,13 @@ | ||
| 28 | 28 | { |
| 29 | 29 | $req = _wppb_encodeQS($data); |
| 30 | 30 | $response = wp_remote_get($path . $req); |
| 31 | 31 | |
| 32 | - if ( ! is_wp_error( $response )) | |
| 33 | - return $response["body"]; | |
| 32 | + if ( is_wp_error( $response ) ) { | |
| 33 | + return ''; | |
| 34 | + } | |
| 35 | + | |
| 36 | + return isset( $response['body'] ) ? $response['body'] : ''; | |
| 34 | 37 | } |
| 35 | 38 | |
| 36 | 39 | /** |
| 37 | 40 | * Gets the challenge HTML (javascript and non-javascript version). |
| @@ -55,34 +58,59 @@ | ||
| 55 | 58 | echo '<span class="error">'. esc_html__("To use reCAPTCHA you must get an API key from", "profile-builder"). " <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a></span><br/><br/>"; |
| 56 | 59 | |
| 57 | 60 | // extra class needed for Invisible reCAPTCHA html |
| 58 | 61 | $invisible_class = ''; |
| 62 | + $v3_field_html = ''; | |
| 59 | 63 | if ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'invisible') ) { |
| 60 | 64 | $invisible_class = 'wppb-invisible-recaptcha'; |
| 65 | + } elseif ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'v3') ) { | |
| 66 | + $invisible_class = 'wppb-v3-recaptcha'; | |
| 67 | + $v3_field_html = '<input type="hidden" name="g-recaptcha-response" class="g-recaptcha-response wppb-v3-recaptcha">'; | |
| 61 | 68 | } |
| 62 | 69 | |
| 70 | + $output = '<div id="wppb-recaptcha-element-'.$form_name.$wppb_recaptcha_forms.'" class="wppb-recaptcha-element '.$invisible_class.'">'.$v3_field_html.'</div>'; | |
| 71 | + | |
| 72 | + if ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'v3') ) { | |
| 73 | + $output .= '<input type="hidden" name="wppb-recaptcha-v3" value="1">'; | |
| 74 | + | |
| 75 | + if( $form_name == 'pb_login' ) { | |
| 76 | + add_filter( 'wppb_login_submit_button_extra_attributes', 'wppb_recaptcha_login_submit_button_extra_attributes' ); | |
| 77 | + } | |
| 78 | + | |
| 79 | + } | |
| 80 | + | |
| 63 | 81 | // reCAPTCHA html for all forms and we make sure we have a unique id for v2 |
| 64 | - return '<div id="wppb-recaptcha-element-'.$form_name.$wppb_recaptcha_forms.'" class="wppb-recaptcha-element '.$invisible_class.'"></div>'; | |
| 82 | + return $output; | |
| 65 | 83 | } |
| 66 | 84 | |
| 85 | +/** | |
| 86 | + * Add disabled attribute to login form submit button when reCaptcha v3 is used | |
| 87 | + * This is used to prevent form submission before the reCaptcha script is loaded and a token is received | |
| 88 | + * | |
| 89 | + * @param string $attributes | |
| 90 | + * @return string | |
| 91 | + */ | |
| 92 | +function wppb_recaptcha_login_submit_button_extra_attributes( $attributes ) { | |
| 93 | + return $attributes . ' disabled="disabled"'; | |
| 94 | +} | |
| 67 | 95 | |
| 68 | - | |
| 69 | 96 | /** |
| 70 | 97 | * Add reCAPTCHA scripts to both front-end PB forms (with support for multiple forms) as well as Default WP forms |
| 71 | 98 | */ |
| 72 | 99 | function wppb_recaptcha_script_footer(){ |
| 73 | 100 | $field = wppb_get_recaptcha_field(); |
| 74 | - /* if we do not have a recaptcha field don't do nothing */ | |
| 101 | + /* if we do not have a recaptcha field do nothing */ | |
| 75 | 102 | if( empty( $field ) ) |
| 76 | 103 | return; |
| 77 | 104 | |
| 78 | - //do not add script if there is no shortcode | |
| 105 | + global $wppb_recaptcha_present; | |
| 79 | 106 | global $wppb_shortcode_on_front; |
| 80 | - if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) ) | |
| 107 | + | |
| 108 | + //do not add script on regular frontend pages unless a PB shortcode or reCAPTCHA HTML is present | |
| 109 | + if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) && ( !isset( $wppb_recaptcha_present ) || $wppb_recaptcha_present === false ) ) | |
| 81 | 110 | return; |
| 82 | 111 | |
| 83 | 112 | //do not add script if the html for the field has not been added |
| 84 | - global $wppb_recaptcha_present; | |
| 85 | 113 | if( !isset( $wppb_recaptcha_present ) || $wppb_recaptcha_present === false ) |
| 86 | 114 | return; |
| 87 | 115 | |
| 88 | 116 | //we don't have jquery on the backend |
| @@ -103,41 +131,196 @@ | ||
| 103 | 131 | $field['recaptcha-type'] = 'v2' ; |
| 104 | 132 | |
| 105 | 133 | /*for invisible recaptcha we have extra parameters and the selector is different. v2 is initialized on the id of the div |
| 106 | 134 | that must be unique and invisible is on the submit button of the forms that have the div */ |
| 107 | - if( $field['recaptcha-type'] === 'invisible' ) { | |
| 108 | - $callback_conditions = 'jQuery("input[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest("form") )'; | |
| 135 | + if ( $field['recaptcha-type'] === 'invisible' ) { | |
| 136 | + $callback_conditions = 'jQuery("input[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest("form") )'; | |
| 109 | 137 | $invisible_parameters = '"callback" : wppbInvisibleRecaptchaOnSubmit,"size": "invisible"'; |
| 110 | - }else { | |
| 111 | - $callback_conditions = 'jQuery(".wppb-recaptcha-element")'; | |
| 138 | + } elseif ( $field['recaptcha-type'] === 'v3' ) { | |
| 139 | + $callback_conditions = 'jQuery( jQuery( ".wppb-recaptcha-element" ).closest("form") )'; | |
| 112 | 140 | $invisible_parameters = ''; |
| 141 | + } else { | |
| 142 | + $callback_conditions = 'jQuery(".wppb-recaptcha-element")'; | |
| 143 | + $invisible_parameters = ''; | |
| 113 | 144 | } |
| 114 | - //the section below is properly escaped or the variables contain static strings | |
| 115 | - // phpcs:disable | |
| 116 | - echo ' | |
| 117 | - <script> | |
| 118 | - var wppbRecaptchaCallback = function() { | |
| 119 | - if( typeof window.wppbRecaptchaCallbackExecuted == "undefined" ){//see if we executed this before | |
| 120 | - '.$callback_conditions.'.each(function(){ | |
| 121 | - recID = grecaptcha.render( jQuery(this).attr("id"), { | |
| 122 | - "sitekey" : "' . $pubkey . '", | |
| 123 | - "error-callback": wppbRecaptchaInitializationError, | |
| 124 | - '.$invisible_parameters.' | |
| 125 | - }); | |
| 145 | + | |
| 146 | + /* For Invisible reCAPTCHA the token is only produced once the async grecaptcha script has loaded and bound the | |
| 147 | + submit button. Until then the submit button behaves like a plain button, so an early click would submit the form | |
| 148 | + with an empty g-recaptcha-response. Since validation now fails closed on a missing token, disable the submit | |
| 149 | + button(s) until the widget is ready and re-enable them afterwards (same approach used for reCAPTCHA v3 login). */ | |
| 150 | + $invisible_submit_selector = 'jQuery( "input[type=\'submit\'], button[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest( "form" ) )'; | |
| 151 | + $invisible_disable_submit_js = ''; | |
| 152 | + $invisible_enable_submit_js = ''; | |
| 153 | + if ( $field['recaptcha-type'] === 'invisible' ) { | |
| 154 | + $invisible_disable_submit_js = $invisible_submit_selector . '.prop( "disabled", true ).addClass( "wppb-recaptcha-not-ready" );'; | |
| 155 | + $invisible_enable_submit_js = $invisible_submit_selector . '.prop( "disabled", false ).removeClass( "wppb-recaptcha-not-ready" );'; | |
| 156 | + } | |
| 157 | + | |
| 158 | + if( $field['recaptcha-type'] === 'v3' ) { | |
| 159 | + | |
| 160 | + //the section below is properly escaped or the variables contain static strings | |
| 161 | + // phpcs:disable | |
| 162 | + echo ' | |
| 163 | + <script> | |
| 164 | + window.wppbRecaptchaCallbackExecuted = false; | |
| 165 | + window.wppbRecaptchaV3 = true; | |
| 166 | + var wppbRecaptchaCallback = function() { | |
| 167 | + if( !window.wppbRecaptchaCallbackExecuted ){ | |
| 168 | + '.$callback_conditions.'.each(function() { | |
| 169 | + let wppbElement = jQuery(this), | |
| 170 | + form = wppbElement.is("form") ? wppbElement : wppbElement.find("form"), | |
| 171 | + currentForm = form[0]; | |
| 172 | + | |
| 173 | + // Ensure we have a PB Form | |
| 174 | + if (form.length === 0) { | |
| 175 | + return; | |
| 176 | + } | |
| 177 | + | |
| 178 | + // Listen for PB-Form submission | |
| 179 | + jQuery(currentForm).on("submit.wppbRecaptchaV3", wppbInitializeRecaptchaV3); | |
| 180 | + }); | |
| 181 | + window.wppbRecaptchaCallbackExecuted = true;//we use this to make sure we only run the callback once | |
| 182 | + | |
| 183 | + // Enable login form submit button | |
| 184 | + if( jQuery("#wppb-loginform input[type=submit]").length > 0 ) { | |
| 185 | + jQuery("#wppb-loginform input[type=submit]").attr("disabled", false); | |
| 186 | + } | |
| 187 | + } | |
| 188 | + }; | |
| 189 | + | |
| 190 | + function wppbInitializeRecaptchaV3( event = null, current_form = null ){ | |
| 191 | + | |
| 192 | + if( event ){ | |
| 193 | + event.preventDefault(); | |
| 194 | + event.stopPropagation(); | |
| 195 | + } | |
| 196 | + | |
| 197 | + let currentForm = this | |
| 198 | + | |
| 199 | + if( current_form != null && current_form && current_form[0] ){ | |
| 200 | + currentForm = current_form[0] | |
| 201 | + } | |
| 202 | + | |
| 203 | + return new Promise((resolve) => { | |
| 204 | + | |
| 205 | + grecaptcha.ready(function() { | |
| 206 | + grecaptcha.execute("' . $pubkey . '", {action: "submit"}).then(function(token) { | |
| 207 | + | |
| 208 | + let recaptchaResponse = jQuery(currentForm).find(".wppb-v3-recaptcha.g-recaptcha-response"); | |
| 209 | + jQuery(recaptchaResponse).val(token); // Set the recaptcha response | |
| 210 | + | |
| 211 | + if( token === false ){ | |
| 212 | + return wppbRecaptchaInitializationError(); | |
| 213 | + } | |
| 214 | + | |
| 215 | + var submitForm = true | |
| 216 | + | |
| 217 | + /* dont submit form if PMS gateway is Stripe */ | |
| 218 | + if( jQuery(".pms_pay_gate[type=radio]").length > 0 ){ | |
| 219 | + jQuery(".pms_pay_gate").each( function(){ | |
| 220 | + if( jQuery(this).is(":checked") && !jQuery(this).is(":disabled") && ( jQuery(this).val() == "stripe_connect" || jQuery(this).val() == "stripe_intents" || jQuery(this).val() == "stripe" || jQuery(this).val() == "paypal_connect" ) ) | |
| 221 | + submitForm = false | |
| 222 | + }) | |
| 223 | + } else if( jQuery(".pms_pay_gate[type=hidden]").length > 0 ) { | |
| 224 | + | |
| 225 | + if( !jQuery(".pms_pay_gate[type=hidden]").is(":disabled") && ( jQuery(".pms_pay_gate[type=hidden]").val() == "stripe_connect" || jQuery(".pms_pay_gate[type=hidden]").val() == "stripe_intents" || jQuery(".pms_pay_gate[type=hidden]").val() == "stripe" || jQuery(".pms_pay_gate[type=hidden]").val() == "paypal_connect" ) ) | |
| 226 | + submitForm = false | |
| 227 | + } else if( currentForm.classList.contains("wppb-ajax-form") ) { | |
| 228 | + submitForm = false; | |
| 229 | + } else if( currentForm.classList.contains("wppb-2fa-form") ) { | |
| 230 | + submitForm = false; | |
| 231 | + } | |
| 232 | + | |
| 233 | + if( currentForm.classList.contains("wppb-2fa-authentication-requested" ) ){ | |
| 234 | + submitForm = true; | |
| 235 | + } | |
| 236 | + | |
| 237 | + if( submitForm ){ | |
| 238 | + jQuery(currentForm).off("submit.wppbRecaptchaV3"); | |
| 239 | + if( currentForm.id === "commentform" ){ | |
| 240 | + HTMLFormElement.prototype.submit.call(currentForm); | |
| 241 | + } else { | |
| 242 | + currentForm.submit(); | |
| 243 | + } | |
| 244 | + } else { | |
| 245 | + jQuery(document).trigger( "wppb_v3_recaptcha_success", jQuery( "input[type=\'submit\']", jQuery( currentForm ) ) ) | |
| 246 | + } | |
| 247 | + | |
| 248 | + resolve( token ); | |
| 249 | + | |
| 250 | + }); | |
| 251 | + }); | |
| 252 | + | |
| 126 | 253 | }); |
| 127 | - window.wppbRecaptchaCallbackExecuted = true;//we use this to make sure we only run the callback once | |
| 128 | 254 | } |
| 129 | - }; | |
| 255 | + | |
| 256 | + /* the callback function for when the captcha does not load propperly, maybe network problem or wrong keys */ | |
| 257 | + function wppbRecaptchaInitializationError(){ | |
| 258 | + window.wppbRecaptchaInitError = true; | |
| 259 | + '; | |
| 130 | 260 | |
| 131 | - /* the callback function for when the captcha does not load propperly, maybe network problem or wrong keys */ | |
| 132 | - function wppbRecaptchaInitializationError(){ | |
| 133 | - window.wppbRecaptchaInitError = true; | |
| 134 | - '; | |
| 261 | + } else { | |
| 262 | + //the section below is properly escaped or the variables contain static strings | |
| 263 | + // phpcs:disable | |
| 264 | + echo ' | |
| 265 | + <script> | |
| 266 | + window.wppbRecaptchaCallbackExecuted = false; | |
| 267 | + window.wppbRecaptcha = true; | |
| 135 | 268 | |
| 136 | - if( $field['recaptcha-type'] === 'invisible' ) { | |
| 269 | + /* keep the form from being submitted with an empty token before the invisible reCAPTCHA is ready */ | |
| 270 | + ' . $invisible_disable_submit_js . ' | |
| 271 | + | |
| 272 | + var wppbRecaptchaCallback = function() { | |
| 273 | + if( !window.wppbRecaptchaCallbackExecuted ){//see if we executed this before | |
| 274 | + ' . $callback_conditions . '.each(function(){ | |
| 275 | + var $recaptchaElement = jQuery(this); | |
| 276 | + var existingRecaptchaId = $recaptchaElement.data("wppb-recaptcha-id"); | |
| 277 | + | |
| 278 | + if ( typeof existingRecaptchaId !== "undefined" ) { | |
| 279 | + grecaptcha.reset( existingRecaptchaId ); | |
| 280 | + return; | |
| 281 | + } | |
| 282 | + | |
| 283 | + try { | |
| 284 | + var recID = grecaptcha.render( | |
| 285 | + $recaptchaElement.attr("id"), | |
| 286 | + { | |
| 287 | + "sitekey" : "' . $pubkey . '", | |
| 288 | + "error-callback": wppbRecaptchaInitializationError, | |
| 289 | + ' . $invisible_parameters . ' | |
| 290 | + } | |
| 291 | + ) | |
| 292 | + | |
| 293 | + $recaptchaElement.data("wppb-recaptcha-id", recID); | |
| 294 | + } catch( error ) { | |
| 295 | + if( error && error.message && error.message.indexOf("already been rendered") !== -1 ) { | |
| 296 | + return; | |
| 297 | + } | |
| 298 | + | |
| 299 | + throw error; | |
| 300 | + } | |
| 301 | + }); | |
| 302 | + | |
| 303 | + /* the invisible reCAPTCHA is now bound to the submit button, so it is safe to re-enable it */ | |
| 304 | + ' . $invisible_enable_submit_js . ' | |
| 305 | + | |
| 306 | + window.wppbRecaptchaCallbackExecuted = true;//we use this to make sure we only run the callback once | |
| 307 | + } | |
| 308 | + }; | |
| 309 | + | |
| 310 | + /* the callback function for when the captcha does not load propperly, maybe network problem or wrong keys */ | |
| 311 | + function wppbRecaptchaInitializationError(){ | |
| 312 | + window.wppbRecaptchaInitError = true; | |
| 313 | + | |
| 314 | + /* the widget could not load, so re-enable the submit button and let the (fallback) submit below run */ | |
| 315 | + ' . $invisible_enable_submit_js . ' | |
| 316 | + '; | |
| 317 | + } | |
| 318 | + | |
| 319 | + if ( $field['recaptcha-type'] === 'invisible' ) { | |
| 137 | 320 | echo ' |
| 138 | 321 | /* make sure that if the invisible recaptcha did not load properly ( network error or wrong keys ) we can still submit the form */ |
| 139 | - jQuery("input[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest("form") ).on("click", function(e){ | |
| 322 | + jQuery("input[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest("form") ).not("#commentform input[type=\'submit\']").on("click", function(e){ | |
| 140 | 323 | jQuery(this).closest("form").submit(); |
| 141 | 324 | }); |
| 142 | 325 | '; |
| 143 | 326 | } |
| @@ -143,9 +326,9 @@ | ||
| 143 | 326 | } |
| 144 | 327 | |
| 145 | 328 | echo ' |
| 146 | 329 | //add a captcha field so we do not just let the form submit if we do not have a captcha response |
| 147 | - jQuery( ".wppb-recaptcha-element" ).after(\''. wp_nonce_field( 'wppb_recaptcha_init_error', 'wppb_recaptcha_load_error', false, false ) .'\'); | |
| 330 | + jQuery( ".wppb-recaptcha-element" ).after(\'' . wp_nonce_field( 'wppb_recaptcha_init_error', 'wppb_recaptcha_load_error', false, false ) . '\'); | |
| 148 | 331 | } |
| 149 | 332 | |
| 150 | 333 | /* compatibility with other plugins that may include recaptcha with an onload callback. if their script loads first then our callback will not execute so call it explicitly */ |
| 151 | 334 | jQuery( window ).on( "load", function() { |
| @@ -152,9 +335,9 @@ | ||
| 152 | 335 | wppbRecaptchaCallback(); |
| 153 | 336 | }); |
| 154 | 337 | </script>'; |
| 155 | 338 | // phpcs:enable |
| 156 | - if( $field['recaptcha-type'] === 'invisible' ) { | |
| 339 | + if ( $field['recaptcha-type'] === 'invisible' ) { | |
| 157 | 340 | echo '<script> |
| 158 | 341 | /* success callback for invisible recaptcha. it submits the form that contains the right token response */ |
| 159 | 342 | function wppbInvisibleRecaptchaOnSubmit(token){ |
| 160 | 343 | |
| @@ -161,28 +344,42 @@ | ||
| 161 | 344 | var elem = jQuery(".g-recaptcha-response").filter(function(){ |
| 162 | 345 | return jQuery(this).val() === token; |
| 163 | 346 | }); |
| 164 | 347 | |
| 348 | + var form = elem.closest("form"); | |
| 349 | + | |
| 165 | 350 | var submitForm = true |
| 166 | 351 | |
| 167 | 352 | /* dont submit form if PMS gateway is Stripe */ |
| 168 | 353 | if( jQuery(".pms_pay_gate[type=radio]").length > 0 ){ |
| 169 | 354 | jQuery(".pms_pay_gate").each( function(){ |
| 170 | - if( jQuery(this).is(":checked") && !jQuery(this).is(":disabled") && ( jQuery(this).val() == "stripe_intents" || jQuery(this).val() == "stripe" ) ) | |
| 355 | + if( jQuery(this).is(":checked") && !jQuery(this).is(":disabled") && ( jQuery(this).val() == "stripe_connect" || jQuery(this).val() == "stripe_intents" || jQuery(this).val() == "stripe" || jQuery(this).val() == "paypal_connect" ) ) | |
| 171 | 356 | submitForm = false |
| 172 | 357 | }) |
| 173 | 358 | } else if( jQuery(".pms_pay_gate[type=hidden]").length > 0 ) { |
| 174 | 359 | |
| 175 | - if( !jQuery(".pms_pay_gate[type=hidden]").is(":disabled") && ( jQuery(".pms_pay_gate[type=hidden]").val() == "stripe_intents" || jQuery(".pms_pay_gate[type=hidden]").val() == "stripe" ) ) | |
| 360 | + if( !jQuery(".pms_pay_gate[type=hidden]").is(":disabled") && ( jQuery(".pms_pay_gate[type=hidden]").val() == "stripe_connect" || jQuery(".pms_pay_gate[type=hidden]").val() == "stripe_intents" || jQuery(".pms_pay_gate[type=hidden]").val() == "stripe" || jQuery(".pms_pay_gate[type=hidden]").val() == "paypal_connect" ) ) | |
| 176 | 361 | submitForm = false |
| 362 | + | |
| 363 | + } else if( form.hasClass("wppb-ajax-form") ) { | |
| 364 | + submitForm = false; | |
| 365 | + } else if( form.hasClass("wppb-2fa-form") ) { | |
| 366 | + submitForm = false; | |
| 367 | + } | |
| 177 | 368 | |
| 369 | + if( form.hasClass("wppb-2fa-authentication-requested" ) ){ | |
| 370 | + submitForm = true; | |
| 178 | 371 | } |
| 179 | 372 | |
| 180 | 373 | if( submitForm ){ |
| 181 | - var form = elem.closest("form"); | |
| 182 | - form.submit(); | |
| 374 | + if( form.attr("id") === "commentform" && form[0] ){ | |
| 375 | + HTMLFormElement.prototype.submit.call(form[0]); | |
| 376 | + } else { | |
| 377 | + form.submit(); | |
| 378 | + } | |
| 183 | 379 | } else { |
| 184 | 380 | jQuery(document).trigger( "wppb_invisible_recaptcha_success", jQuery( ".form-submit input[type=\'submit\']", elem.closest("form") ) ) |
| 381 | + return true; | |
| 185 | 382 | } |
| 186 | 383 | } |
| 187 | 384 | </script>'; |
| 188 | 385 | } |
| @@ -195,9 +392,14 @@ | ||
| 195 | 392 | } |
| 196 | 393 | |
| 197 | 394 | $source = apply_filters( 'wppb_recaptcha_custom_field_source', 'www.google.com' ); |
| 198 | 395 | |
| 199 | - echo '<script src="https://'. esc_attr( $source ) .'/recaptcha/api.js?onload=wppbRecaptchaCallback&render=explicit'.esc_attr( $lang ).'" async defer></script>'; | |
| 396 | + if( $field['recaptcha-type'] === 'v3' ) { | |
| 397 | + echo '<script src="https://'. esc_attr( $source ) .'/recaptcha/api.js?render='.esc_attr( $pubkey ).'" async defer></script>'; | |
| 398 | + } else { | |
| 399 | + echo '<script src="https://'. esc_attr( $source ) .'/recaptcha/api.js?onload=wppbRecaptchaCallback&render=explicit'.esc_attr( $lang ).'" async defer></script>'; | |
| 400 | + } | |
| 401 | + | |
| 200 | 402 | } |
| 201 | 403 | add_action('wp_footer', 'wppb_recaptcha_script_footer', 9999); |
| 202 | 404 | add_action('login_footer', 'wppb_recaptcha_script_footer'); |
| 203 | 405 | add_action('register_form', 'wppb_recaptcha_script_footer'); |
| @@ -204,8 +406,27 @@ | ||
| 204 | 406 | add_action('lost_password', 'wppb_recaptcha_script_footer'); |
| 205 | 407 | |
| 206 | 408 | |
| 207 | 409 | /** |
| 410 | + * Print style | |
| 411 | + * | |
| 412 | + */ | |
| 413 | +function wppb_recaptcha_print_style() { | |
| 414 | + echo '<style type="text/css"> | |
| 415 | + /* Hide reCAPTCHA V3 badge */ | |
| 416 | + .grecaptcha-badge { | |
| 417 | + | |
| 418 | + visibility: hidden !important; | |
| 419 | + | |
| 420 | + } | |
| 421 | + </style>'; | |
| 422 | +} | |
| 423 | + | |
| 424 | +add_action( 'wp_footer', 'wppb_recaptcha_print_style' ); | |
| 425 | +add_action( 'login_footer', 'wppb_recaptcha_print_style' ); | |
| 426 | + | |
| 427 | + | |
| 428 | +/** | |
| 208 | 429 | * A wppb_ReCaptchaResponse is returned from wppb_recaptcha_check_answer() |
| 209 | 430 | */ |
| 210 | 431 | class wppb_ReCaptchaResponse { |
| 211 | 432 | var $is_valid; |
| @@ -218,22 +439,21 @@ | ||
| 218 | 439 | * @param string $remoteip |
| 219 | 440 | * @param string $response |
| 220 | 441 | * @return wppb_ReCaptchaResponse |
| 221 | 442 | */ |
| 222 | -function wppb_recaptcha_check_answer ( $privkey, $remoteip, $response ){ | |
| 443 | +function wppb_recaptcha_check_answer ( $privkey, $remoteip, $response, $score_threshold = 0.5 ) { | |
| 223 | 444 | |
| 224 | 445 | if ( $remoteip == null || $remoteip == '' ) |
| 225 | 446 | echo '<span class="error">'. esc_html__("For security reasons, you must pass the remote ip to reCAPTCHA!", "profile-builder") .'</span><br/><br/>'; |
| 226 | 447 | |
| 227 | - // Discard empty solution submissions | |
| 448 | + // Discard empty solution submissions. Fail closed: a missing token is never valid. | |
| 449 | + // The previous wppb_recaptcha_load_error nonce "escape hatch" was removed - that nonce is printed in the | |
| 450 | + // page HTML, so a bot could replay it to skip verification. A genuinely unconfigured reCAPTCHA (empty keys) | |
| 451 | + // is handled upstream in wppb_validate_captcha_response(), so this does not lock users out on misconfig. | |
| 228 | 452 | if ($response == null || strlen($response) == 0) { |
| 229 | 453 | $recaptchaResponse = new wppb_ReCaptchaResponse(); |
| 454 | + $recaptchaResponse->is_valid = false; | |
| 230 | 455 | |
| 231 | - if( isset( $_POST['wppb_recaptcha_load_error'] ) && wp_verify_nonce( sanitize_text_field( $_POST['wppb_recaptcha_load_error'] ), 'wppb_recaptcha_init_error' ) ) | |
| 232 | - $recaptchaResponse->is_valid = true; | |
| 233 | - else | |
| 234 | - $recaptchaResponse->is_valid = false; | |
| 235 | - | |
| 236 | 456 | return $recaptchaResponse; |
| 237 | 457 | } |
| 238 | 458 | |
| 239 | 459 | $source = apply_filters( 'wppb_recaptcha_custom_field_source', 'www.google.com' ); |
| @@ -246,32 +466,82 @@ | ||
| 246 | 466 | 'response' => $response |
| 247 | 467 | ) |
| 248 | 468 | ); |
| 249 | 469 | |
| 250 | - $answers = json_decode($getResponse, true); | |
| 470 | + $answers = json_decode( $getResponse, true ); | |
| 251 | 471 | $recaptchaResponse = new wppb_ReCaptchaResponse(); |
| 252 | - if (trim($answers ['success']) == true) { | |
| 472 | + | |
| 473 | + // Fail closed when the HTTP call fails or the body is not valid JSON. | |
| 474 | + if ( ! is_array( $answers ) || empty( $answers['success'] ) ) { | |
| 475 | + $recaptchaResponse->is_valid = false; | |
| 476 | + return $recaptchaResponse; | |
| 477 | + } | |
| 478 | + | |
| 479 | + if ( array_key_exists( 'score', $answers ) ) { | |
| 480 | + $recaptchaResponse->is_valid = ( $answers['score'] >= $score_threshold ); | |
| 481 | + } else { | |
| 253 | 482 | $recaptchaResponse->is_valid = true; |
| 254 | - } else { | |
| 255 | - $recaptchaResponse->is_valid = false; | |
| 256 | 483 | } |
| 484 | + | |
| 257 | 485 | return $recaptchaResponse; |
| 258 | 486 | |
| 259 | 487 | } |
| 260 | 488 | |
| 261 | 489 | /* the function to display error message on the registration page */ |
| 262 | -function wppb_validate_captcha_response( $publickey, $privatekey ){ | |
| 490 | +function wppb_validate_captcha_response( $publickey, $privatekey, $score_threshold = 0.5 ){ | |
| 491 | + /* If the reCAPTCHA keys are not configured the widget cannot work for anyone, so do not enforce - | |
| 492 | + otherwise an incomplete setup would lock every visitor out of the form. These keys are admin-side | |
| 493 | + configuration, not attacker controlled, so this cannot be used to bypass a properly configured reCAPTCHA. */ | |
| 494 | + if ( empty( $publickey ) || empty( $privatekey ) ) { | |
| 495 | + return true; | |
| 496 | + } | |
| 497 | + | |
| 263 | 498 | if (isset($_POST['g-recaptcha-response'])){ |
| 264 | 499 | $recaptcha_response_field = sanitize_textarea_field( $_POST['g-recaptcha-response'] ); |
| 500 | + } else { | |
| 501 | + $recaptcha_response_field = ''; | |
| 265 | 502 | } |
| 266 | - else { | |
| 267 | - $recaptcha_response_field = ''; | |
| 503 | + | |
| 504 | + $already_validated = false; | |
| 505 | + $saved = get_option( 'wppb_recaptcha_validations', array() ); | |
| 506 | + | |
| 507 | + if( isset( $saved[ $recaptcha_response_field ] ) && $saved[ $recaptcha_response_field ] == true ){ | |
| 508 | + $already_validated = true; | |
| 509 | + | |
| 510 | + if( !wp_doing_ajax() ){ | |
| 511 | + unset( $saved[ $recaptcha_response_field ] ); | |
| 512 | + | |
| 513 | + update_option( 'wppb_recaptcha_validations', $saved, false ); | |
| 514 | + } | |
| 268 | 515 | } |
| 269 | - if( isset( $_SERVER["REMOTE_ADDR"] ) ) | |
| 270 | - $resp = wppb_recaptcha_check_answer($privatekey, sanitize_text_field( $_SERVER["REMOTE_ADDR"] ), $recaptcha_response_field ); | |
| 271 | 516 | |
| 272 | - if ( !empty( $_POST ) && isset( $resp ) ) | |
| 273 | - return ( ( !$resp->is_valid ) ? false : true ); | |
| 517 | + if( !$already_validated ){ | |
| 518 | + | |
| 519 | + if( isset( $_SERVER["REMOTE_ADDR"] ) ){ | |
| 520 | + $resp = wppb_recaptcha_check_answer($privatekey, sanitize_text_field( $_SERVER["REMOTE_ADDR"] ), $recaptcha_response_field, $score_threshold ); | |
| 521 | + | |
| 522 | + if( isset( $resp ) ){ | |
| 523 | + $already_validated = ( ( !$resp->is_valid ) ? false : true ); | |
| 524 | + } | |
| 525 | + } | |
| 526 | + | |
| 527 | + } | |
| 528 | + | |
| 529 | + // Save valid results when they are being triggered from an ajax request that only pre-validates the | |
| 530 | + // credentials, so the same single use token is still accepted on the form submission that follows it | |
| 531 | + if( wppb_is_captcha_prevalidation_request() ){ | |
| 532 | + | |
| 533 | + $saved = wppb_prune_captcha_prevalidations( get_option( 'wppb_recaptcha_validations', array() ) ); | |
| 534 | + | |
| 535 | + if( $already_validated === true ) | |
| 536 | + $saved[ $recaptcha_response_field ] = time(); | |
| 537 | + | |
| 538 | + update_option( 'wppb_recaptcha_validations', $saved, false ); | |
| 539 | + | |
| 540 | + } | |
| 541 | + | |
| 542 | + return $already_validated; | |
| 543 | + | |
| 274 | 544 | } |
| 275 | 545 | |
| 276 | 546 | /* the function to add reCAPTCHA to the registration form of PB */ |
| 277 | 547 | function wppb_recaptcha_handler ( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){ |
| @@ -279,9 +549,10 @@ | ||
| 279 | 549 | $item_title = apply_filters( 'wppb_'.$form_location.'_recaptcha_custom_field_'.$field['id'].'_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_title_translation', $field['field-title'], true ) ); |
| 280 | 550 | $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true ); |
| 281 | 551 | |
| 282 | 552 | wppb_recaptcha_set_default_values(); |
| 283 | - if ( ($form_location == 'register') && ( isset($field['captcha-pb-forms']) ) && (strpos($field['captcha-pb-forms'],'pb_register') !== false) ) { | |
| 553 | + | |
| 554 | + if ( ($form_location == 'register') && ( isset($field['captcha-pb-forms']) ) && ( strpos($field['captcha-pb-forms'],'pb_register') !== false || ( $field['recaptcha-type'] == 'v3' && wppb_maybe_enable_recaptcha_v3_on_form( $field ) ) ) ) { | |
| 284 | 555 | $error_mark = ( ( $field['required'] == 'Yes' ) ? '<span class="wppb-required" title="'.wppb_required_field_error($field["field-title"]).'">*</span>' : '' ); |
| 285 | 556 | |
| 286 | 557 | global $wppb_recaptcha_present; |
| 287 | 558 | $wppb_recaptcha_present = true; |
| @@ -316,17 +587,19 @@ | ||
| 316 | 587 | |
| 317 | 588 | /* handle reCAPTCHA field validation on PB Register form */ |
| 318 | 589 | function wppb_check_recaptcha_value( $message, $field, $request_data, $form_location ){ |
| 319 | 590 | if( $field['field'] == 'reCAPTCHA' ){ |
| 320 | - if ( ( $form_location == 'register' ) && ( isset($field['captcha-pb-forms']) ) && (strpos($field['captcha-pb-forms'],'pb_register') !== false) ) { | |
| 321 | - | |
| 591 | + if ( ( $form_location == 'register' ) && ( isset($field['captcha-pb-forms']) ) && ( strpos($field['captcha-pb-forms'],'pb_register') !== false || ( $field['recaptcha-type'] == 'v3' && wppb_maybe_enable_recaptcha_v3_on_form( $field ) ) ) ) { | |
| 322 | 592 | /* theme my login plugin executes the register_errors hook on the frontend on all pages so on our register forms we might have already a recaptcha response |
| 323 | 593 | so do not verify it again or it will fail */ |
| 324 | 594 | global $wppb_recaptcha_response; |
| 325 | 595 | if (!isset($wppb_recaptcha_response)){ |
| 326 | - $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 596 | + $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 327 | 597 | } |
| 328 | - if ( ( $wppb_recaptcha_response == false ) && ( $field['required'] == 'Yes' ) ){ | |
| 598 | + /* reCAPTCHA must fail closed: whenever it is configured to display on this form it has to be | |
| 599 | + verified, regardless of the "required" toggle. A missing/empty token makes | |
| 600 | + wppb_validate_captcha_response() return false, so bots that omit g-recaptcha-response are blocked. */ | |
| 601 | + if ( $wppb_recaptcha_response == false ){ | |
| 329 | 602 | return wppb_required_field_error($field["field-title"]); |
| 330 | 603 | } |
| 331 | 604 | } |
| 332 | 605 | } |
| @@ -336,13 +609,15 @@ | ||
| 336 | 609 | |
| 337 | 610 | // Get the reCAPTCHA field information |
| 338 | 611 | function wppb_get_recaptcha_field(){ |
| 339 | 612 | $wppb_manage_fields = get_option( 'wppb_manage_fields', 'not_found' ); |
| 340 | - $field = ''; | |
| 613 | + $field = array(); | |
| 341 | 614 | if ( $wppb_manage_fields != 'not_found' ) { |
| 342 | 615 | foreach ($wppb_manage_fields as $value) { |
| 343 | - if ($value['field'] == 'reCAPTCHA') | |
| 616 | + if ($value['field'] == 'reCAPTCHA'){ | |
| 344 | 617 | $field = $value; |
| 618 | + break; | |
| 619 | + } | |
| 345 | 620 | } |
| 346 | 621 | } |
| 347 | 622 | return $field; |
| 348 | 623 | } |
| @@ -356,9 +631,9 @@ | ||
| 356 | 631 | $item_title = apply_filters('wppb_recover_password_recaptcha_custom_field_' . $field['id'] . '_item_title', wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true)); |
| 357 | 632 | $item_description = wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true); |
| 358 | 633 | |
| 359 | 634 | // check where reCAPTCHA should display and add reCAPTCHA html |
| 360 | - if ( isset($field['captcha-pb-forms']) && ( strpos( $field['captcha-pb-forms'],'pb_recover_password' ) !== false ) ) { | |
| 635 | + if ( isset($field['captcha-pb-forms']) && ( strpos( $field['captcha-pb-forms'],'pb_recover_password' ) !== false || ( $field['recaptcha-type'] == 'v3' && wppb_maybe_enable_recaptcha_v3_on_form( $field ) ) ) ) { | |
| 361 | 636 | |
| 362 | 637 | global $wppb_recaptcha_present; |
| 363 | 638 | $wppb_recaptcha_present = true; |
| 364 | 639 | |
| @@ -366,13 +641,13 @@ | ||
| 366 | 641 | $recaptcha_output = '<label for="recaptcha_response_field">' . $item_title . '</label>' . wppb_recaptcha_get_html($publickey, 'pb_recover_password'); |
| 367 | 642 | if (!empty($item_description)) |
| 368 | 643 | $recaptcha_output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>'; |
| 369 | 644 | |
| 370 | - $output = str_replace('</ul>', '<li class="wppb-form-field wppb-recaptcha">' . $recaptcha_output . '</li>' . '</ul>', $output); | |
| 645 | + $output = str_replace('</ul>', '<li class="wppb-form-field wppb-recaptcha wppb-recaptcha-'. $field['recaptcha-type'] .'">' . $recaptcha_output . '</li>' . '</ul>', $output); | |
| 371 | 646 | } |
| 372 | 647 | else { |
| 373 | 648 | // output Invisible reCAPTCHA html |
| 374 | - $output = str_replace('</ul>', '<li class="wppb-form-field wppb-recaptcha">' . wppb_recaptcha_get_html($publickey, 'pb_recover_password') . '</li>' . '</ul>', $output); | |
| 649 | + $output = str_replace('</ul>', '<li class="wppb-form-field wppb-recaptcha wppb-recaptcha-'. $field['recaptcha-type'] .'">' . wppb_recaptcha_get_html($publickey, 'pb_recover_password') . '</li>' . '</ul>', $output); | |
| 375 | 650 | } |
| 376 | 651 | } |
| 377 | 652 | } |
| 378 | 653 | return $output; |
| @@ -386,13 +661,13 @@ | ||
| 386 | 661 | $field = wppb_get_recaptcha_field(); |
| 387 | 662 | if (!empty($field)) { |
| 388 | 663 | |
| 389 | 664 | global $wppb_recaptcha_response; |
| 390 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 665 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 391 | 666 | |
| 392 | 667 | if ( isset($field['captcha-pb-forms']) && (strpos($field['captcha-pb-forms'], 'pb_recover_password') !== false) ) { |
| 393 | 668 | |
| 394 | - if ( ($wppb_recaptcha_response == false ) && ( $field['required'] == 'Yes' ) ) | |
| 669 | + if ( $wppb_recaptcha_response == false ) | |
| 395 | 670 | $messageNo = ''; |
| 396 | 671 | } |
| 397 | 672 | } |
| 398 | 673 | } |
| @@ -406,9 +681,9 @@ | ||
| 406 | 681 | $field = wppb_get_recaptcha_field(); |
| 407 | 682 | |
| 408 | 683 | if ( !empty($field) ){ |
| 409 | 684 | global $wppb_recaptcha_response; |
| 410 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 685 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 411 | 686 | |
| 412 | 687 | if ( isset($field['captcha-pb-forms']) && ( strpos( $field['captcha-pb-forms'],'pb_recover_password' ) !== false ) && ( $wppb_recaptcha_response == false )) { |
| 413 | 688 | |
| 414 | 689 | // This message is also altered by the plugin-compatibilities.php file, in regards to Captcha plugin ( function wppb_captcha_recover_password_displayed_message1 ) |
| @@ -432,9 +707,9 @@ | ||
| 432 | 707 | $field = wppb_get_recaptcha_field(); |
| 433 | 708 | |
| 434 | 709 | if (!empty($field)) { |
| 435 | 710 | global $wppb_recaptcha_response; |
| 436 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 711 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 437 | 712 | |
| 438 | 713 | if ( isset($field['captcha-pb-forms']) && ( strpos($field['captcha-pb-forms'], 'pb_recover_password') !== false ) && ( $wppb_recaptcha_response == false ) ){ |
| 439 | 714 | $message = 'wppb_recaptcha_error'; |
| 440 | 715 | } |
| @@ -457,9 +732,9 @@ | ||
| 457 | 732 | if ( !empty($field) ) { |
| 458 | 733 | $item_title = apply_filters('wppb_login_recaptcha_custom_field_' . $field['id'] . '_item_title', wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true)); |
| 459 | 734 | $item_description = wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true); |
| 460 | 735 | |
| 461 | - if ( isset($field['captcha-pb-forms']) && ( strpos( $field['captcha-pb-forms'],'pb_login' ) !== false ) ) { // check where reCAPTCHA should display and add reCAPTCHA html | |
| 736 | + if ( isset($field['captcha-pb-forms']) && ( strpos( $field['captcha-pb-forms'],'pb_login' ) !== false || ( $field['recaptcha-type'] == 'v3' && wppb_maybe_enable_recaptcha_v3_on_form( $field ) ) ) ) { // check where reCAPTCHA should display and add reCAPTCHA html | |
| 462 | 737 | |
| 463 | 738 | global $wppb_recaptcha_present; |
| 464 | 739 | $wppb_recaptcha_present = true; |
| 465 | 740 | |
| @@ -467,13 +742,14 @@ | ||
| 467 | 742 | $recaptcha_output = '<label for="recaptcha_response_field">' . $item_title . '</label>' . wppb_recaptcha_get_html(trim($field['public-key']), 'pb_login'); |
| 468 | 743 | if (!empty($item_description)) |
| 469 | 744 | $recaptcha_output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>'; |
| 470 | 745 | |
| 471 | - $form_part .= '<div class="wppb-form-field wppb-recaptcha">' . $recaptcha_output . '</div>'; | |
| 746 | + $form_part .= '<div class="wppb-form-field wppb-recaptcha wppb-recaptcha-'. $field['recaptcha-type'] .'">' . $recaptcha_output . '</div>'; | |
| 472 | 747 | } |
| 473 | 748 | else { |
| 474 | 749 | //output Invisible reCAPTCHA html |
| 475 | - $form_part .= wppb_recaptcha_get_html(trim($field['public-key']), 'pb_login'); | |
| 750 | +// $form_part .= wppb_recaptcha_get_html(trim($field['public-key']), 'pb_login'); | |
| 751 | + $form_part .= '<div class="wppb-form-field wppb-recaptcha wppb-recaptcha-'. $field['recaptcha-type'] .'">' . wppb_recaptcha_get_html(trim($field['public-key']), 'pb_login') . '</div>'; | |
| 476 | 752 | } |
| 477 | 753 | } |
| 478 | 754 | } |
| 479 | 755 | |
| @@ -512,35 +788,41 @@ | ||
| 512 | 788 | |
| 513 | 789 | //Show reCAPTCHA error on Login form (both default and PB one) |
| 514 | 790 | function wppb_recaptcha_login_wp_error_message($user){ |
| 515 | 791 | //make sure you're on a Login form (WP or PB) |
| 516 | - if ( isset( $_POST['wp-submit'] ) && !is_wp_error($user) && !isset( $_POST['pms_login'] ) ) { | |
| 792 | + if ( isset( $_POST['log'] ) && !is_wp_error($user) && !isset( $_POST['pms_login'] ) ) { | |
| 517 | 793 | |
| 518 | 794 | $field = wppb_get_recaptcha_field(); |
| 519 | 795 | if ( !empty($field) ){ |
| 520 | - global $wppb_recaptcha_response; | |
| 796 | + /* Work out whether reCAPTCHA is enabled for the form that was actually submitted before verifying | |
| 797 | + anything. The token is single use, so verifying it on a form where our widget was never displayed | |
| 798 | + spends a token that belongs to whatever else protects that form, and that plugin's own check | |
| 799 | + then fails as a duplicate. */ | |
| 800 | + if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) { | |
| 801 | + // it's a PB login form, check if we have a reCAPTCHA on it | |
| 802 | + $recaptcha_enabled = ( isset($field['captcha-pb-forms']) && ( strpos($field['captcha-pb-forms'], 'pb_login') !== false || ( $field['recaptcha-type'] == 'v3' && wppb_maybe_enable_recaptcha_v3_on_form( $field ) ) ) ); | |
| 803 | + } | |
| 804 | + else { | |
| 805 | + // default WP login form | |
| 806 | + $recaptcha_enabled = ( isset($field['captcha-wp-forms']) && (strpos($field['captcha-wp-forms'], 'default_wp_login') !== false) ); | |
| 807 | + } | |
| 521 | 808 | |
| 522 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 809 | + if ( $recaptcha_enabled ) { | |
| 810 | + global $wppb_recaptcha_response; | |
| 523 | 811 | |
| 524 | - //reCAPTCHA error for displaying on the PB login form | |
| 525 | - if ( isset($_POST['wppb_login']) && ($_POST['wppb_login'] == true) ) { | |
| 812 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 526 | 813 | |
| 527 | - // it's a PB login form, check if we have a reCAPTCHA on it and display error if not valid | |
| 528 | - if ((isset($field['captcha-pb-forms'])) && (strpos($field['captcha-pb-forms'], 'pb_login') !== false) && ($wppb_recaptcha_response == false)) { | |
| 529 | - $user = new WP_Error('wppb_recaptcha_error', __('Please enter a (valid) reCAPTCHA value', 'profile-builder')); | |
| 530 | - remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); | |
| 531 | - remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); | |
| 814 | + $recaptcha_error_message = __('reCaptcha could not be verified. Please try again.','profile-builder'); | |
| 815 | + | |
| 816 | + if( isset( $field['recaptcha-type'] ) && $field['recaptcha-type'] === 'v2' ) { | |
| 817 | + $recaptcha_error_message = __('Please enter a (valid) reCAPTCHA value','profile-builder'); | |
| 532 | 818 | } |
| 533 | 819 | |
| 534 | - } | |
| 535 | - else { | |
| 536 | - //reCAPTCHA error for displaying on the default WP login form | |
| 537 | - if (isset($field['captcha-wp-forms']) && (strpos($field['captcha-wp-forms'], 'default_wp_login') !== false) && ($wppb_recaptcha_response == false)) { | |
| 538 | - $user = new WP_Error('wppb_recaptcha_error', __('Please enter a (valid) reCAPTCHA value', 'profile-builder')); | |
| 820 | + if ( $wppb_recaptcha_response == false ) { | |
| 821 | + $user = new WP_Error('wppb_recaptcha_error', $recaptcha_error_message); | |
| 539 | 822 | remove_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); |
| 540 | 823 | remove_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); |
| 541 | 824 | } |
| 542 | - | |
| 543 | 825 | } |
| 544 | 826 | } |
| 545 | 827 | } |
| 546 | 828 | return $user; |
| @@ -546,8 +828,24 @@ | ||
| 546 | 828 | return $user; |
| 547 | 829 | } |
| 548 | 830 | add_filter('authenticate','wppb_recaptcha_login_wp_error_message', 9); |
| 549 | 831 | |
| 832 | +/** | |
| 833 | + * Add a reCAPTCHA type–specific CSS class to the Register form field | |
| 834 | + * | |
| 835 | + * @param $classes - existing field classes | |
| 836 | + * @param $field - field data | |
| 837 | + * @return mixed|string | |
| 838 | + */ | |
| 839 | +function wppb_register_form_recaptcha_type_class( $classes, $field ){ | |
| 840 | + | |
| 841 | + if ( isset( $field['field'] ) && $field['field'] == 'reCAPTCHA' && ! empty( $field['recaptcha-type'] ) ) | |
| 842 | + $classes .= ' wppb-recaptcha-' . $field['recaptcha-type']; | |
| 843 | + | |
| 844 | + return $classes; | |
| 845 | +} | |
| 846 | +add_filter( 'wppb_field_css_class', 'wppb_register_form_recaptcha_type_class', 20, 2); | |
| 847 | + | |
| 550 | 848 | // Display reCAPTCHA html on default WP Recover Password form |
| 551 | 849 | function wppb_display_recaptcha_default_wp_recover_password() { |
| 552 | 850 | $field = wppb_get_recaptcha_field(); |
| 553 | 851 | |
| @@ -585,14 +883,25 @@ | ||
| 585 | 883 | return; |
| 586 | 884 | |
| 587 | 885 | $field = wppb_get_recaptcha_field(); |
| 588 | 886 | if ( !empty($field) ){ |
| 589 | - global $wppb_recaptcha_response; | |
| 590 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 887 | + /* Only verify where the captcha is configured for the form being submitted. The token is single use, | |
| 888 | + so verifying it on a form our widget was never displayed on spends a token that another plugin | |
| 889 | + protecting that form still needs, and its own check then fails as a duplicate. */ | |
| 890 | + if ( isset( $field['captcha-wp-forms'] ) && ( strpos( $field['captcha-wp-forms'], 'default_wp_recover_password' ) !== false ) ) { | |
| 891 | + global $wppb_recaptcha_response; | |
| 892 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 591 | 893 | |
| 592 | - // If reCAPTCHA not entered or incorrect reCAPTCHA answer | |
| 593 | - if ( isset( $_REQUEST['g-recaptcha-response'] ) && ( ( "" === $_REQUEST['g-recaptcha-response'] ) || ( $wppb_recaptcha_response == false ) ) ) { | |
| 594 | - wp_die( esc_html__('Please enter a (valid) reCAPTCHA value','profile-builder') . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ; | |
| 894 | + $recaptcha_error_message = esc_html__('reCaptcha could not be verified. Please try again.','profile-builder'); | |
| 895 | + | |
| 896 | + if( isset( $field['recaptcha-type'] ) && $field['recaptcha-type'] === 'v2' ) { | |
| 897 | + $recaptcha_error_message = esc_html__('Please enter a (valid) reCAPTCHA value','profile-builder'); | |
| 898 | + } | |
| 899 | + | |
| 900 | + // Fail closed: a missing token is treated as a failed verification. | |
| 901 | + if ( $wppb_recaptcha_response == false ) { | |
| 902 | + wp_die( esc_html( $recaptcha_error_message ) . '<br />' . esc_html__( "Click the BACK button on your browser, and try again.", 'profile-builder' ) ) ; | |
| 903 | + } | |
| 595 | 904 | } |
| 596 | 905 | } |
| 597 | 906 | } |
| 598 | 907 | add_action('lostpassword_post','wppb_verify_recaptcha_default_wp_recover_password'); |
| @@ -633,14 +942,25 @@ | ||
| 633 | 942 | function wppb_verify_recaptcha_default_wp_register( $errors ){ |
| 634 | 943 | |
| 635 | 944 | $field = wppb_get_recaptcha_field(); |
| 636 | 945 | if ( !empty($field) ){ |
| 637 | - global $wppb_recaptcha_response; | |
| 638 | - if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ) ); | |
| 946 | + /* Only verify where the captcha is configured for the form being submitted. The token is single use, | |
| 947 | + so verifying it on a form our widget was never displayed on spends a token that another plugin | |
| 948 | + protecting that form still needs, and its own check then fails as a duplicate. */ | |
| 949 | + if ( isset( $field['captcha-wp-forms'] ) && ( strpos( $field['captcha-wp-forms'], 'default_wp_register' ) !== false ) ) { | |
| 950 | + global $wppb_recaptcha_response; | |
| 951 | + if (!isset($wppb_recaptcha_response)) $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 639 | 952 | |
| 640 | - // If reCAPTCHA not entered or incorrect reCAPTCHA answer | |
| 641 | - if ( isset( $_REQUEST['g-recaptcha-response'] ) && ( ( "" === $_REQUEST['g-recaptcha-response'] ) || ( $wppb_recaptcha_response == false ) ) ) { | |
| 642 | - $errors->add( 'wppb_recaptcha_error', __('Please enter a (valid) reCAPTCHA value','profile-builder') ); | |
| 953 | + $recaptcha_error_message = esc_html__('reCaptcha could not be verified. Please try again.','profile-builder'); | |
| 954 | + | |
| 955 | + if( isset( $field['recaptcha-type'] ) && $field['recaptcha-type'] === 'v2' ) { | |
| 956 | + $recaptcha_error_message = esc_html__('Please enter a (valid) reCAPTCHA value','profile-builder'); | |
| 957 | + } | |
| 958 | + | |
| 959 | + // Fail closed: a missing token is treated as a failed verification. | |
| 960 | + if ( $wppb_recaptcha_response == false ) { | |
| 961 | + $errors->add( 'wppb_recaptcha_error', $recaptcha_error_message ); | |
| 962 | + } | |
| 643 | 963 | } |
| 644 | 964 | } |
| 645 | 965 | |
| 646 | 966 | return $errors; |
| @@ -646,8 +966,85 @@ | ||
| 646 | 966 | return $errors; |
| 647 | 967 | } |
| 648 | 968 | add_filter('registration_errors','wppb_verify_recaptcha_default_wp_register'); |
| 649 | 969 | |
| 970 | +/* Display reCAPTCHA html on default WP Comments form */ | |
| 971 | +function wppb_display_recaptcha_default_wp_comments(){ | |
| 972 | + $field = wppb_get_recaptcha_field(); | |
| 973 | + | |
| 974 | + if ( !empty( $field ) ) { | |
| 975 | + if ( isset( $field['captcha-wp-forms'] ) && ( strpos( $field['captcha-wp-forms'], 'default_wp_comments' ) !== false ) ) { | |
| 976 | + $publickey = trim( $field['public-key'] ); | |
| 977 | + $item_title = apply_filters( 'wppb_comments_recaptcha_custom_field_' . $field['id'] . '_item_title', wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_title_translation', $field['field-title'], true ) ); | |
| 978 | + $item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true ); | |
| 979 | + $recaptcha_type = empty( $field['recaptcha-type'] ) ? 'v2' : $field['recaptcha-type']; | |
| 980 | + | |
| 981 | + global $wppb_recaptcha_present; | |
| 982 | + $wppb_recaptcha_present = true; | |
| 983 | + | |
| 984 | + if ( $recaptcha_type == 'v2' ) { | |
| 985 | + $recaptcha_output = '<label for="recaptcha_response_field">' . $item_title . '</label>' . wppb_recaptcha_get_html( $publickey, 'default_wp_comments' ); | |
| 986 | + if ( !empty( $item_description ) ) | |
| 987 | + $recaptcha_output .= '<span class="wppb-description-delimiter">' . $item_description . '</span>'; | |
| 988 | + | |
| 989 | + echo '<div class="wppb-form-field wppb-recaptcha wppb-recaptcha-' . esc_attr( $recaptcha_type ) . '">' . $recaptcha_output . '</div>'; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ /* properly escaped when constructing the var */ | |
| 990 | + } | |
| 991 | + else { | |
| 992 | + echo wppb_recaptcha_get_html( $publickey, 'default_wp_comments' ); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ /* properly escaped when constructing the var */ | |
| 993 | + } | |
| 994 | + } | |
| 995 | + } | |
| 996 | +} | |
| 997 | +add_action( 'comment_form_after_fields', 'wppb_display_recaptcha_default_wp_comments' ); | |
| 998 | +add_action( 'comment_form_logged_in_after', 'wppb_display_recaptcha_default_wp_comments' ); | |
| 999 | + | |
| 1000 | +function wppb_display_recaptcha_default_wp_comments_error(){ | |
| 1001 | + if ( !isset( $_GET['wppb_comment_recaptcha_error'] ) ) | |
| 1002 | + return; | |
| 1003 | + | |
| 1004 | + $field = wppb_get_recaptcha_field(); | |
| 1005 | + | |
| 1006 | + if ( empty( $field ) || !isset( $field['captcha-wp-forms'] ) || ( strpos( $field['captcha-wp-forms'], 'default_wp_comments' ) === false ) ) | |
| 1007 | + return; | |
| 1008 | + | |
| 1009 | + echo '<p class="wppb-error wppb-comment-captcha-error" id="wppb_comment_recaptcha_error">' . esc_html( wppb_recaptcha_field_error( $field['field-title'] ) ) . '</p>'; | |
| 1010 | +} | |
| 1011 | +add_action( 'comment_form_top', 'wppb_display_recaptcha_default_wp_comments_error' ); | |
| 1012 | + | |
| 1013 | +// Verify reCAPTCHA for default WP Comments form | |
| 1014 | +function wppb_verify_recaptcha_default_wp_comments( $approved, $commentdata ){ | |
| 1015 | + if ( !isset( $_POST['comment_post_ID'] ) ) | |
| 1016 | + return $approved; | |
| 1017 | + | |
| 1018 | + $field = wppb_get_recaptcha_field(); | |
| 1019 | + | |
| 1020 | + if ( !empty( $field ) ) { | |
| 1021 | + if ( isset( $field['captcha-wp-forms'] ) && ( strpos( $field['captcha-wp-forms'], 'default_wp_comments' ) !== false ) ) { | |
| 1022 | + global $wppb_recaptcha_response; | |
| 1023 | + if ( !isset( $wppb_recaptcha_response ) ) | |
| 1024 | + $wppb_recaptcha_response = wppb_validate_captcha_response( trim( $field['public-key'] ), trim( $field['private-key'] ), isset( $field['score-threshold'] ) ? trim( $field['score-threshold'] ) : 0.5 ); | |
| 1025 | + | |
| 1026 | + if ( $wppb_recaptcha_response == false ) { | |
| 1027 | + $redirect_to = wp_get_referer(); | |
| 1028 | + | |
| 1029 | + if ( empty( $redirect_to ) && isset( $commentdata['comment_post_ID'] ) ) | |
| 1030 | + $redirect_to = get_permalink( absint( $commentdata['comment_post_ID'] ) ); | |
| 1031 | + | |
| 1032 | + if ( !empty( $redirect_to ) && !wp_doing_ajax() ) { | |
| 1033 | + $redirect_to = preg_replace( '/#.*$/', '', remove_query_arg( array( 'wppb_comment_recaptcha_error', 'wppb_comment_turnstile_error' ), $redirect_to ) ); | |
| 1034 | + wp_safe_redirect( add_query_arg( 'wppb_comment_recaptcha_error', '1', $redirect_to ) . '#respond' ); | |
| 1035 | + exit; | |
| 1036 | + } | |
| 1037 | + | |
| 1038 | + return new WP_Error( 'wppb_recaptcha_error', wppb_recaptcha_field_error( $field['field-title'] ), 200 ); | |
| 1039 | + } | |
| 1040 | + } | |
| 1041 | + } | |
| 1042 | + | |
| 1043 | + return $approved; | |
| 1044 | +} | |
| 1045 | +add_filter( 'pre_comment_approved', 'wppb_verify_recaptcha_default_wp_comments', 10, 2 ); | |
| 1046 | + | |
| 650 | 1047 | // set default values in case there's already an existing reCAPTCHA field in Manage fields (when upgrading) |
| 651 | 1048 | function wppb_recaptcha_set_default_values() { |
| 652 | 1049 | $manage_fields = get_option('wppb_manage_fields', 'not_set'); |
| 653 | 1050 | if ($manage_fields != 'not_set') { |
| @@ -659,5 +1056,115 @@ | ||
| 659 | 1056 | } |
| 660 | 1057 | } |
| 661 | 1058 | update_option('wppb_manage_fields', $manage_fields); |
| 662 | 1059 | } |
| 1060 | +} | |
| 1061 | + | |
| 1062 | +if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'paid-member-subscriptions/index.php' ) && defined( 'PMS_VERSION' ) && version_compare( PMS_VERSION, '2.12.9', '<' ) ) { | |
| 1063 | + | |
| 1064 | + $notifications = WPPB_Plugin_Notifications::get_instance(); | |
| 1065 | + | |
| 1066 | + // this must be unique | |
| 1067 | + $notification_id = 'wppb_pms_recaptcha_compatibility'; | |
| 1068 | + | |
| 1069 | + $notification_message = '<p>' . __( 'reCAPTCHA v3 is not compatible with Paid Member Subscriptions versions that are older than <strong>2.12.7</strong>. <br>Please update Paid Member Subscriptions to a newer version to avoid any issues.', 'profile-builder' ) . '</p>'; | |
| 1070 | + $notification_message .= '<a href="' . wp_nonce_url( add_query_arg( array( 'wppb_dismiss_admin_notification' => $notification_id ) ), 'wppb_plugin_notice_dismiss' ) . '" type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss this notice.', 'profile-builder' ) . '</span></a>'; | |
| 1071 | + | |
| 1072 | + // add the notification (we need to add the "notice is-dismissible" classes for the dismiss button to be correctly positioned) | |
| 1073 | + $notifications->add_notification( $notification_id, $notification_message, 'wppb-notice notice notice-warning is-dismissible', false ); | |
| 1074 | + | |
| 1075 | +} | |
| 1076 | + | |
| 1077 | +// Make sure the reCAPTCHA field score threshold is set correctly | |
| 1078 | +function wppb_check_recaptcha_fields_settings( $values ) { | |
| 1079 | + if( isset( $values['field'] ) && $values['field'] == 'reCAPTCHA' ) { | |
| 1080 | + if ( empty( $values['score-threshold'] ) || $values['score-threshold'] < 0 || $values['score-threshold'] > 1 ) { | |
| 1081 | + $values['score-threshold'] = 0.5; | |
| 1082 | + } | |
| 1083 | + } | |
| 1084 | + | |
| 1085 | + return $values; | |
| 1086 | +} | |
| 1087 | +add_action( 'wck_update_meta_filter_values_wppb_manage_fields', 'wppb_check_recaptcha_fields_settings' ); | |
| 1088 | + | |
| 1089 | +function wppb_maybe_enable_recaptcha_v3_on_form( $recaptcha_field ){ | |
| 1090 | + | |
| 1091 | + // Static cache to avoid repeated calculations | |
| 1092 | + static $cache = array(); | |
| 1093 | + | |
| 1094 | + // Early validation checks | |
| 1095 | + if( empty( $recaptcha_field ) || empty( $recaptcha_field['captcha-pb-forms'] ) ) | |
| 1096 | + return false; | |
| 1097 | + | |
| 1098 | + $post_id = get_the_ID(); | |
| 1099 | + $post = get_post( $post_id ); | |
| 1100 | + | |
| 1101 | + // Check if post is set, if not return false | |
| 1102 | + if( empty( $post ) || empty( $post->post_content ) ) | |
| 1103 | + return false; | |
| 1104 | + | |
| 1105 | + // Create cache key based on post ID and captcha forms configuration | |
| 1106 | + $cache_key = md5( $post_id . serialize( $recaptcha_field['captcha-pb-forms'] ) ); | |
| 1107 | + | |
| 1108 | + // Return cached result if available | |
| 1109 | + if( isset( $cache[ $cache_key ] ) ) | |
| 1110 | + return $cache[ $cache_key ]; | |
| 1111 | + | |
| 1112 | + $wppb_recaptcha_v3 = false; | |
| 1113 | + | |
| 1114 | + // Define form configurations for loop processing | |
| 1115 | + $form_configs = array( | |
| 1116 | + 'pb_register' => array( | |
| 1117 | + 'shortcode_pattern' => '[wppb-register', | |
| 1118 | + 'block_name' => 'wppb/register', | |
| 1119 | + 'other_forms' => array( | |
| 1120 | + array( 'shortcode' => '[wppb-login', 'block' => 'wppb/login', 'form_type' => 'pb_login' ), | |
| 1121 | + array( 'shortcode' => '[wppb-recover-password', 'block' => 'wppb/recover-password', 'form_type' => 'pb_recover_password' ) | |
| 1122 | + ) | |
| 1123 | + ), | |
| 1124 | + 'pb_login' => array( | |
| 1125 | + 'shortcode_pattern' => '[wppb-login', | |
| 1126 | + 'block_name' => 'wppb/login', | |
| 1127 | + 'other_forms' => array( | |
| 1128 | + array( 'shortcode' => '[wppb-register', 'block' => 'wppb/register', 'form_type' => 'pb_register' ), | |
| 1129 | + array( 'shortcode' => '[wppb-recover-password', 'block' => 'wppb/recover-password', 'form_type' => 'pb_recover_password' ) | |
| 1130 | + ) | |
| 1131 | + ), | |
| 1132 | + 'pb_recover_password' => array( | |
| 1133 | + 'shortcode_pattern' => '[wppb-recover-password', | |
| 1134 | + 'block_name' => 'wppb/recover-password', | |
| 1135 | + 'other_forms' => array( | |
| 1136 | + array( 'shortcode' => '[wppb-register', 'block' => 'wppb/register', 'form_type' => 'pb_register' ), | |
| 1137 | + array( 'shortcode' => '[wppb-login', 'block' => 'wppb/login', 'form_type' => 'pb_login' ) | |
| 1138 | + ) | |
| 1139 | + ) | |
| 1140 | + ); | |
| 1141 | + | |
| 1142 | + // Process each form type using loop | |
| 1143 | + foreach( $form_configs as $form_type => $config ) { | |
| 1144 | + // Skip if this form type is already enabled in captcha-pb-forms | |
| 1145 | + if( strpos( $recaptcha_field['captcha-pb-forms'], $form_type ) !== false ) | |
| 1146 | + continue; | |
| 1147 | + | |
| 1148 | + // Check if current form type exists on the page | |
| 1149 | + $current_form_exists = ( strpos( $post->post_content, $config['shortcode_pattern'] ) !== false || has_block( $config['block_name'] ) ); | |
| 1150 | + | |
| 1151 | + if( $current_form_exists ) { | |
| 1152 | + // Check if any other enabled form types also exist on the page | |
| 1153 | + foreach( $config['other_forms'] as $other_form ) { | |
| 1154 | + $other_form_exists = ( strpos( $post->post_content, $other_form['shortcode'] ) !== false || has_block( $other_form['block'] ) ); | |
| 1155 | + $other_form_enabled = ( strpos( $recaptcha_field['captcha-pb-forms'], $other_form['form_type'] ) !== false ); | |
| 1156 | + | |
| 1157 | + if( $other_form_exists && $other_form_enabled ) { | |
| 1158 | + $wppb_recaptcha_v3 = true; | |
| 1159 | + break 2; // Break out of both loops since we found a match | |
| 1160 | + } | |
| 1161 | + } | |
| 1162 | + } | |
| 1163 | + } | |
| 1164 | + | |
| 1165 | + // Cache the result | |
| 1166 | + $cache[ $cache_key ] = $wppb_recaptcha_v3; | |
| 1167 | + | |
| 1168 | + return $wppb_recaptcha_v3; | |
| 1169 | + | |
| 663 | 1170 | } |