PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.3
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.3
4.0.3 4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 341 releases
← 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 }