$value) {
$req .= $key . '=' . urlencode(stripslashes($value)) . '&';
}
// Cut the last '&'
$req=substr($req, 0, strlen($req)-1);
return $req;
}
/**
* Submits an HTTP GET to a reCAPTCHA server
* @param string $path
* @param array $data
*/
function _wppb_submitHTTPGet($path, $data)
{
$req = _wppb_encodeQS($data);
$response = wp_remote_get($path . $req);
if ( is_wp_error( $response ) ) {
return '';
}
return isset( $response['body'] ) ? $response['body'] : '';
}
/**
* Gets the challenge HTML (javascript and non-javascript version).
* This is called from the browser, and the resulting reCAPTCHA HTML widget
* is embedded within the HTML form it was called from.
* @param string $pubkey A public key for reCAPTCHA
* @param string $error The error given by reCAPTCHA (optional, default is null)
* @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
* @return string - The HTML to be embedded in the user's form.
*/
function wppb_recaptcha_get_html ( $pubkey, $form_name='' ){
global $wppb_recaptcha_forms; // is the counter for the number of forms that have recaptcha so we always have unique ids on the element
if( is_null( $wppb_recaptcha_forms ) )
$wppb_recaptcha_forms = 0;
$wppb_recaptcha_forms++;
$field = wppb_get_recaptcha_field();
if ( empty($pubkey) )
echo ''. esc_html__("To use reCAPTCHA you must get an API key from", "profile-builder"). " https://www.google.com/recaptcha/admin/create
";
// extra class needed for Invisible reCAPTCHA html
$invisible_class = '';
$v3_field_html = '';
if ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'invisible') ) {
$invisible_class = 'wppb-invisible-recaptcha';
} elseif ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'v3') ) {
$invisible_class = 'wppb-v3-recaptcha';
$v3_field_html = '';
}
$output = '
'.$v3_field_html.'
';
if ( isset($field['recaptcha-type']) && ($field['recaptcha-type'] == 'v3') ) {
$output .= '';
if( $form_name == 'pb_login' ) {
add_filter( 'wppb_login_submit_button_extra_attributes', 'wppb_recaptcha_login_submit_button_extra_attributes' );
}
}
// reCAPTCHA html for all forms and we make sure we have a unique id for v2
return $output;
}
/**
* Add disabled attribute to login form submit button when reCaptcha v3 is used
* This is used to prevent form submission before the reCaptcha script is loaded and a token is received
*
* @param string $attributes
* @return string
*/
function wppb_recaptcha_login_submit_button_extra_attributes( $attributes ) {
return $attributes . ' disabled="disabled"';
}
/**
* Add reCAPTCHA scripts to both front-end PB forms (with support for multiple forms) as well as Default WP forms
*/
function wppb_recaptcha_script_footer(){
$field = wppb_get_recaptcha_field();
/* if we do not have a recaptcha field do nothing */
if( empty( $field ) )
return;
global $wppb_recaptcha_present;
global $wppb_shortcode_on_front;
//do not add script on regular frontend pages unless a PB shortcode or reCAPTCHA HTML is present
if( current_filter() == 'wp_footer' && ( !isset( $wppb_shortcode_on_front ) || $wppb_shortcode_on_front === false ) && ( !isset( $wppb_recaptcha_present ) || $wppb_recaptcha_present === false ) )
return;
//do not add script if the html for the field has not been added
if( !isset( $wppb_recaptcha_present ) || $wppb_recaptcha_present === false )
return;
//we don't have jquery on the backend
if( current_filter() != 'wp_footer' ) {
wp_print_scripts('jquery');
}else if(!wp_script_is('jquery')){
wp_print_scripts('jquery');
}
//get site key
$pubkey = '';
if( isset( $field['public-key'] ) ) {
$pubkey = sanitize_text_field( $field['public-key'] );
}
// Check if we have a reCAPTCHA type
if ( !isset($field['recaptcha-type']) )
$field['recaptcha-type'] = 'v2' ;
/*for invisible recaptcha we have extra parameters and the selector is different. v2 is initialized on the id of the div
that must be unique and invisible is on the submit button of the forms that have the div */
if ( $field['recaptcha-type'] === 'invisible' ) {
$callback_conditions = 'jQuery("input[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest("form") )';
$invisible_parameters = '"callback" : wppbInvisibleRecaptchaOnSubmit,"size": "invisible"';
} elseif ( $field['recaptcha-type'] === 'v3' ) {
$callback_conditions = 'jQuery( jQuery( ".wppb-recaptcha-element" ).closest("form") )';
$invisible_parameters = '';
} else {
$callback_conditions = 'jQuery(".wppb-recaptcha-element")';
$invisible_parameters = '';
}
/* For Invisible reCAPTCHA the token is only produced once the async grecaptcha script has loaded and bound the
submit button. Until then the submit button behaves like a plain button, so an early click would submit the form
with an empty g-recaptcha-response. Since validation now fails closed on a missing token, disable the submit
button(s) until the widget is ready and re-enable them afterwards (same approach used for reCAPTCHA v3 login). */
$invisible_submit_selector = 'jQuery( "input[type=\'submit\'], button[type=\'submit\']", jQuery( ".wppb-recaptcha-element" ).closest( "form" ) )';
$invisible_disable_submit_js = '';
$invisible_enable_submit_js = '';
if ( $field['recaptcha-type'] === 'invisible' ) {
$invisible_disable_submit_js = $invisible_submit_selector . '.prop( "disabled", true ).addClass( "wppb-recaptcha-not-ready" );';
$invisible_enable_submit_js = $invisible_submit_selector . '.prop( "disabled", false ).removeClass( "wppb-recaptcha-not-ready" );';
}
if( $field['recaptcha-type'] === 'v3' ) {
//the section below is properly escaped or the variables contain static strings
// phpcs:disable
echo '
';
// phpcs:enable
if ( $field['recaptcha-type'] === 'invisible' ) {
echo '';
}
$lang = '&hl=en';
$locale = get_locale();
if(!empty($locale)) {
$locale_parts = explode('_',$locale);
$lang = '&hl='.urlencode($locale_parts[0]);
}
$source = apply_filters( 'wppb_recaptcha_custom_field_source', 'www.google.com' );
if( $field['recaptcha-type'] === 'v3' ) {
echo '';
} else {
echo '';
}
}
add_action('wp_footer', 'wppb_recaptcha_script_footer', 9999);
add_action('login_footer', 'wppb_recaptcha_script_footer');
add_action('register_form', 'wppb_recaptcha_script_footer');
add_action('lost_password', 'wppb_recaptcha_script_footer');
/**
* Print style
*
*/
function wppb_recaptcha_print_style() {
echo '';
}
add_action( 'wp_footer', 'wppb_recaptcha_print_style' );
add_action( 'login_footer', 'wppb_recaptcha_print_style' );
/**
* A wppb_ReCaptchaResponse is returned from wppb_recaptcha_check_answer()
*/
class wppb_ReCaptchaResponse {
var $is_valid;
}
/**
* Calls an HTTP POST function to verify if the user's answer was correct
* @param string $privkey
* @param string $remoteip
* @param string $response
* @return wppb_ReCaptchaResponse
*/
function wppb_recaptcha_check_answer ( $privkey, $remoteip, $response, $score_threshold = 0.5 ) {
if ( $remoteip == null || $remoteip == '' )
echo ''. esc_html__("For security reasons, you must pass the remote ip to reCAPTCHA!", "profile-builder") .'
';
// Discard empty solution submissions. Fail closed: a missing token is never valid.
// The previous wppb_recaptcha_load_error nonce "escape hatch" was removed - that nonce is printed in the
// page HTML, so a bot could replay it to skip verification. A genuinely unconfigured reCAPTCHA (empty keys)
// is handled upstream in wppb_validate_captcha_response(), so this does not lock users out on misconfig.
if ($response == null || strlen($response) == 0) {
$recaptchaResponse = new wppb_ReCaptchaResponse();
$recaptchaResponse->is_valid = false;
return $recaptchaResponse;
}
$source = apply_filters( 'wppb_recaptcha_custom_field_source', 'www.google.com' );
$getResponse = _wppb_submitHTTPGet(
"https://".$source."/recaptcha/api/siteverify?",
array (
'secret' => $privkey,
'remoteip' => $remoteip,
'response' => $response
)
);
$answers = json_decode( $getResponse, true );
$recaptchaResponse = new wppb_ReCaptchaResponse();
// Fail closed when the HTTP call fails or the body is not valid JSON.
if ( ! is_array( $answers ) || empty( $answers['success'] ) ) {
$recaptchaResponse->is_valid = false;
return $recaptchaResponse;
}
if ( array_key_exists( 'score', $answers ) ) {
$recaptchaResponse->is_valid = ( $answers['score'] >= $score_threshold );
} else {
$recaptchaResponse->is_valid = true;
}
return $recaptchaResponse;
}
/* the function to display error message on the registration page */
function wppb_validate_captcha_response( $publickey, $privatekey, $score_threshold = 0.5 ){
/* If the reCAPTCHA keys are not configured the widget cannot work for anyone, so do not enforce -
otherwise an incomplete setup would lock every visitor out of the form. These keys are admin-side
configuration, not attacker controlled, so this cannot be used to bypass a properly configured reCAPTCHA. */
if ( empty( $publickey ) || empty( $privatekey ) ) {
return true;
}
if (isset($_POST['g-recaptcha-response'])){
$recaptcha_response_field = sanitize_textarea_field( $_POST['g-recaptcha-response'] );
} else {
$recaptcha_response_field = '';
}
$already_validated = false;
$saved = get_option( 'wppb_recaptcha_validations', array() );
if( isset( $saved[ $recaptcha_response_field ] ) && $saved[ $recaptcha_response_field ] == true ){
$already_validated = true;
if( !wp_doing_ajax() ){
unset( $saved[ $recaptcha_response_field ] );
update_option( 'wppb_recaptcha_validations', $saved, false );
}
}
if( !$already_validated ){
if( isset( $_SERVER["REMOTE_ADDR"] ) ){
$resp = wppb_recaptcha_check_answer($privatekey, sanitize_text_field( $_SERVER["REMOTE_ADDR"] ), $recaptcha_response_field, $score_threshold );
if( isset( $resp ) ){
$already_validated = ( ( !$resp->is_valid ) ? false : true );
}
}
}
// Save valid results when they are being triggered from an ajax request
if( wp_doing_ajax() && isset( $_POST['action'] ) && $_POST['action'] == 'pms_validate_checkout' ){
$saved = get_option( 'wppb_recaptcha_validations', array() );
if( $already_validated === true )
$saved[ $recaptcha_response_field ] = true;
update_option( 'wppb_recaptcha_validations', $saved, false );
}
return $already_validated;
}
/* the function to add reCAPTCHA to the registration form of PB */
function wppb_recaptcha_handler ( $output, $form_location, $field, $user_id, $field_check_errors, $request_data ){
if ( $field['field'] == 'reCAPTCHA' ){
$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 ) );
$item_description = wppb_icl_t( 'plugin profile-builder-pro', 'custom_field_'.$field['id'].'_description_translation', $field['description'], true );
wppb_recaptcha_set_default_values();
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 ) ) ) ) {
$error_mark = ( ( $field['required'] == 'Yes' ) ? '*' : '' );
global $wppb_recaptcha_present;
$wppb_recaptcha_present = true;
if ( array_key_exists( $field['id'], $field_check_errors ) )
$error_mark = '';
$publickey = trim( $field['public-key'] );
$privatekey = trim( $field['private-key'] );
if ( empty( $publickey ) || empty( $privatekey ) )
return ''.apply_filters( 'wppb_'.$form_location.'_recaptcha_custom_field_'.$field['id'].'_error_message', __("To use reCAPTCHA you must get an API public key from:", "profile-builder"). 'https://www.google.com/recaptcha/admin/create' ).'';
if ( empty($field['recaptcha-type']) || ($field['recaptcha-type'] == 'v2') ) {
$output = '' . wppb_recaptcha_get_html($publickey, 'pb_register');
if (!empty($item_description))
$output .= '' . $item_description . '';
}
else {
// html for Invisible reCAPTCHA
$output = wppb_recaptcha_get_html($publickey, 'pb_register');
}
return $output;
}
}
}
add_filter( 'wppb_output_form_field_recaptcha', 'wppb_recaptcha_handler', 10, 6 );
/* handle reCAPTCHA field validation on PB Register form */
function wppb_check_recaptcha_value( $message, $field, $request_data, $form_location ){
if( $field['field'] == 'reCAPTCHA' ){
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 ) ) ) ) {
/* 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
so do not verify it again or it will fail */
global $wppb_recaptcha_response;
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 );
}
/* reCAPTCHA must fail closed: whenever it is configured to display on this form it has to be
verified, regardless of the "required" toggle. A missing/empty token makes
wppb_validate_captcha_response() return false, so bots that omit g-recaptcha-response are blocked. */
if ( $wppb_recaptcha_response == false ){
return wppb_required_field_error($field["field-title"]);
}
}
}
return $message;
}
add_filter( 'wppb_check_form_field_recaptcha', 'wppb_check_recaptcha_value', 10, 4 );
// Get the reCAPTCHA field information
function wppb_get_recaptcha_field(){
$wppb_manage_fields = get_option( 'wppb_manage_fields', 'not_found' );
$field = array();
if ( $wppb_manage_fields != 'not_found' ) {
foreach ($wppb_manage_fields as $value) {
if ($value['field'] == 'reCAPTCHA'){
$field = $value;
break;
}
}
}
return $field;
}
/* Display reCAPTCHA on PB Recover Password form */
function wppb_display_recaptcha_recover_password( $output ){
$field = wppb_get_recaptcha_field();
if ( !empty($field) ) {
$publickey = trim($field['public-key']);
$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));
$item_description = wppb_icl_t('plugin profile-builder-pro', 'custom_field_' . $field['id'] . '_description_translation', $field['description'], true);
// check where reCAPTCHA should display and add reCAPTCHA html
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 ) ) ) ) {
global $wppb_recaptcha_present;
$wppb_recaptcha_present = true;
if ( empty($field['recaptcha-type']) || ($field['recaptcha-type'] == 'v2') ) {
$recaptcha_output = '' . wppb_recaptcha_get_html($publickey, 'pb_recover_password');
if (!empty($item_description))
$recaptcha_output .= '' . $item_description . '';
$output = str_replace('', '
';
}
add_action( 'comment_form_top', 'wppb_display_recaptcha_default_wp_comments_error' );
// Verify reCAPTCHA for default WP Comments form
function wppb_verify_recaptcha_default_wp_comments( $approved, $commentdata ){
if ( !isset( $_POST['comment_post_ID'] ) )
return $approved;
$field = wppb_get_recaptcha_field();
if ( !empty( $field ) ) {
if ( isset( $field['captcha-wp-forms'] ) && ( strpos( $field['captcha-wp-forms'], 'default_wp_comments' ) !== false ) ) {
global $wppb_recaptcha_response;
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 );
if ( $wppb_recaptcha_response == false ) {
$redirect_to = wp_get_referer();
if ( empty( $redirect_to ) && isset( $commentdata['comment_post_ID'] ) )
$redirect_to = get_permalink( absint( $commentdata['comment_post_ID'] ) );
if ( !empty( $redirect_to ) && !wp_doing_ajax() ) {
$redirect_to = preg_replace( '/#.*$/', '', remove_query_arg( array( 'wppb_comment_recaptcha_error', 'wppb_comment_turnstile_error' ), $redirect_to ) );
wp_safe_redirect( add_query_arg( 'wppb_comment_recaptcha_error', '1', $redirect_to ) . '#respond' );
exit;
}
return new WP_Error( 'wppb_recaptcha_error', wppb_recaptcha_field_error( $field['field-title'] ), 200 );
}
}
}
return $approved;
}
add_filter( 'pre_comment_approved', 'wppb_verify_recaptcha_default_wp_comments', 10, 2 );
// set default values in case there's already an existing reCAPTCHA field in Manage fields (when upgrading)
function wppb_recaptcha_set_default_values() {
$manage_fields = get_option('wppb_manage_fields', 'not_set');
if ($manage_fields != 'not_set') {
foreach ($manage_fields as $key => $value) {
if ($value['field'] == 'reCAPTCHA') {
if ( !isset($value['captcha-pb-forms']) ) $manage_fields[$key]['captcha-pb-forms'] = 'pb_register';
if ( !isset($value['captcha-wp-forms']) ) $manage_fields[$key]['captcha-wp-forms'] = 'default_wp_register';
if ( !isset($value['recaptcha-type']) ) $manage_fields[$key]['recaptcha-type'] = 'v2';
}
}
update_option('wppb_manage_fields', $manage_fields);
}
}
if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'paid-member-subscriptions/index.php' ) && defined( 'PMS_VERSION' ) && version_compare( PMS_VERSION, '2.12.9', '<' ) ) {
$notifications = WPPB_Plugin_Notifications::get_instance();
// this must be unique
$notification_id = 'wppb_pms_recaptcha_compatibility';
$notification_message = '
' . __( 'reCAPTCHA v3 is not compatible with Paid Member Subscriptions versions that are older than 2.12.7. Please update Paid Member Subscriptions to a newer version to avoid any issues.', 'profile-builder' ) . '
';
$notification_message .= '' . __( 'Dismiss this notice.', 'profile-builder' ) . '';
// add the notification (we need to add the "notice is-dismissible" classes for the dismiss button to be correctly positioned)
$notifications->add_notification( $notification_id, $notification_message, 'wppb-notice notice notice-warning is-dismissible', false );
}
// Make sure the reCAPTCHA field score threshold is set correctly
function wppb_check_recaptcha_fields_settings( $values ) {
if( isset( $values['field'] ) && $values['field'] == 'reCAPTCHA' ) {
if ( empty( $values['score-threshold'] ) || $values['score-threshold'] < 0 || $values['score-threshold'] > 1 ) {
$values['score-threshold'] = 0.5;
}
}
return $values;
}
add_action( 'wck_update_meta_filter_values_wppb_manage_fields', 'wppb_check_recaptcha_fields_settings' );
function wppb_maybe_enable_recaptcha_v3_on_form( $recaptcha_field ){
// Static cache to avoid repeated calculations
static $cache = array();
// Early validation checks
if( empty( $recaptcha_field ) || empty( $recaptcha_field['captcha-pb-forms'] ) )
return false;
$post_id = get_the_ID();
$post = get_post( $post_id );
// Check if post is set, if not return false
if( empty( $post ) || empty( $post->post_content ) )
return false;
// Create cache key based on post ID and captcha forms configuration
$cache_key = md5( $post_id . serialize( $recaptcha_field['captcha-pb-forms'] ) );
// Return cached result if available
if( isset( $cache[ $cache_key ] ) )
return $cache[ $cache_key ];
$wppb_recaptcha_v3 = false;
// Define form configurations for loop processing
$form_configs = array(
'pb_register' => array(
'shortcode_pattern' => '[wppb-register',
'block_name' => 'wppb/register',
'other_forms' => array(
array( 'shortcode' => '[wppb-login', 'block' => 'wppb/login', 'form_type' => 'pb_login' ),
array( 'shortcode' => '[wppb-recover-password', 'block' => 'wppb/recover-password', 'form_type' => 'pb_recover_password' )
)
),
'pb_login' => array(
'shortcode_pattern' => '[wppb-login',
'block_name' => 'wppb/login',
'other_forms' => array(
array( 'shortcode' => '[wppb-register', 'block' => 'wppb/register', 'form_type' => 'pb_register' ),
array( 'shortcode' => '[wppb-recover-password', 'block' => 'wppb/recover-password', 'form_type' => 'pb_recover_password' )
)
),
'pb_recover_password' => array(
'shortcode_pattern' => '[wppb-recover-password',
'block_name' => 'wppb/recover-password',
'other_forms' => array(
array( 'shortcode' => '[wppb-register', 'block' => 'wppb/register', 'form_type' => 'pb_register' ),
array( 'shortcode' => '[wppb-login', 'block' => 'wppb/login', 'form_type' => 'pb_login' )
)
)
);
// Process each form type using loop
foreach( $form_configs as $form_type => $config ) {
// Skip if this form type is already enabled in captcha-pb-forms
if( strpos( $recaptcha_field['captcha-pb-forms'], $form_type ) !== false )
continue;
// Check if current form type exists on the page
$current_form_exists = ( strpos( $post->post_content, $config['shortcode_pattern'] ) !== false || has_block( $config['block_name'] ) );
if( $current_form_exists ) {
// Check if any other enabled form types also exist on the page
foreach( $config['other_forms'] as $other_form ) {
$other_form_exists = ( strpos( $post->post_content, $other_form['shortcode'] ) !== false || has_block( $other_form['block'] ) );
$other_form_enabled = ( strpos( $recaptcha_field['captcha-pb-forms'], $other_form['form_type'] ) !== false );
if( $other_form_exists && $other_form_enabled ) {
$wppb_recaptcha_v3 = true;
break 2; // Break out of both loops since we found a match
}
}
}
}
// Cache the result
$cache[ $cache_key ] = $wppb_recaptcha_v3;
return $wppb_recaptcha_v3;
}