| 1 |
<?php |
| 2 |
/** |
| 3 |
* The WP_Members_Captcha Class. |
| 4 |
* |
| 5 |
* This is the WP_Members Captcha object class. This class contains functions |
| 6 |
* for handling the various captchas that the plugin natively supports. This |
| 7 |
* includes reCAPTCHA v2/v3, Really Simple CAPTCHA, and hCaptcha. |
| 8 |
* |
| 9 |
* @package WP-Members |
| 10 |
* @subpackage WP_Members_Captcha Object Class |
| 11 |
* @since 3.0.0 |
| 12 |
*/ |
| 13 |
|
| 14 |
// Exit if accessed directly. |
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit(); |
| 17 |
} |
| 18 |
|
| 19 |
class WP_Members_Captcha { |
| 20 |
|
| 21 |
/** |
| 22 |
* Gets which CAPTCHA is set. |
| 23 |
* |
| 24 |
* @since 3.3.5 |
| 25 |
*/ |
| 26 |
static function type( $decode = false ) { |
| 27 |
global $wpmem; |
| 28 |
$value = ( false !== $decode ) ? $decode : $wpmem->captcha; |
| 29 |
switch ( $value ) { |
| 30 |
case 0: |
| 31 |
return "Disabled"; |
| 32 |
break; |
| 33 |
case 1: |
| 34 |
case 3: |
| 35 |
return "recaptcha_v2"; |
| 36 |
break; |
| 37 |
case 4: |
| 38 |
return "recaptcha_v3"; |
| 39 |
break; |
| 40 |
case 5: |
| 41 |
return "hcaptcha"; |
| 42 |
break; |
| 43 |
case 2: |
| 44 |
default: |
| 45 |
return "rs_captcha"; |
| 46 |
break; |
| 47 |
} |
| 48 |
} |
| 49 |
|
| 50 |
/** |
| 51 |
* Display a CAPTCHA. |
| 52 |
* |
| 53 |
* @since 3.3.4 |
| 54 |
* @since 3.3.6 $type defaults to false, so captcha defaults to $wpmem setting. |
| 55 |
* |
| 56 |
* @param string $type Type of captcha to display. |
| 57 |
* @param array $keys Google reCAPTCHA keys (if used). |
| 58 |
*/ |
| 59 |
static function show( $type = false, $key = false ) { |
| 60 |
if ( false === $type ) { |
| 61 |
$type = self::type(); |
| 62 |
} |
| 63 |
if ( 'rs_captcha' == $type ) { |
| 64 |
return self::rs_captcha(); |
| 65 |
} elseif ( 'hcaptcha' == $type ) { |
| 66 |
return self::hcaptcha( $key ); |
| 67 |
} else { |
| 68 |
return self::recaptcha( $key ); |
| 69 |
} |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Create a hCaptcha form. |
| 74 |
* |
| 75 |
* @since 3.3.5 |
| 76 |
* |
| 77 |
* @param string $key Your hCaptcha API key. |
| 78 |
* @return string $html The form HTML. |
| 79 |
*/ |
| 80 |
static function hcaptcha( $key = false ) { |
| 81 |
|
| 82 |
if ( false === $key ) { |
| 83 |
$opts = get_option( 'wpmembers_captcha' ); |
| 84 |
$key = $opts['hcaptcha']['api_key']; |
| 85 |
} |
| 86 |
$html = '<div class="h-captcha" data-sitekey="' . $key . '"></div>'; |
| 87 |
$html .= '<script src="https://hcaptcha.com/1/api.js" async defer></script>'; |
| 88 |
/** This filter is defined in /includes/class-wp-members-captcha.php */ |
| 89 |
return apply_filters( 'wpmem_captcha', $html ); |
| 90 |
} |
| 91 |
|
| 92 |
/** |
| 93 |
* Create reCAPTCHA form. |
| 94 |
* |
| 95 |
* @since 3.3.0 Replaces wpmem_inc_recaptcha(). |
| 96 |
* @since 3.3.5 Accepts API public key for static use. |
| 97 |
* |
| 98 |
* @global stdCalss $wpmem |
| 99 |
* @param string $key Your reCAPTCHA public key. |
| 100 |
* @return string $html HTML for reCAPTCHA display. |
| 101 |
*/ |
| 102 |
static function recaptcha( $key = false ) { |
| 103 |
|
| 104 |
global $wpmem; |
| 105 |
|
| 106 |
if ( false == $key ) { |
| 107 |
$opts = get_option( 'wpmembers_captcha' ); |
| 108 |
$key = $opts['recaptcha']['public']; |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* Filters the URL used for google recaptcha API. |
| 113 |
* |
| 114 |
* @since 3.4.2 |
| 115 |
* |
| 116 |
* @param string $url |
| 117 |
*/ |
| 118 |
$wpmem_recaptcha_url = apply_filters( 'wpmem_recaptcha_url', 'https://www.google.com/recaptcha/api.js' ); |
| 119 |
|
| 120 |
/* |
| 121 |
* NOTE: DO NOT EDIT THIS. Use the filter hook found below. |
| 122 |
* |
| 123 |
* Don't know how to use filter or action hooks to customize WP? |
| 124 |
* See https://wpbitz.com/how-to-use-hooks-in-wordpress/ |
| 125 |
*/ |
| 126 |
if ( 3 == $wpmem->captcha ) { |
| 127 |
$html = '<script src="' . $wpmem_recaptcha_url . '" async defer></script> |
| 128 |
<div class="g-recaptcha" data-sitekey="' . $key . '"></div>'; |
| 129 |
} else { |
| 130 |
$html = '<script src="' . $wpmem_recaptcha_url . '?render=' . $key . '"></script>'; |
| 131 |
$html.= "<script> |
| 132 |
grecaptcha.ready(function () { |
| 133 |
grecaptcha.execute('" . $key . "', { action: 'contact' }).then(function (token) { |
| 134 |
var recaptchaResponse = document.getElementById('recaptchaResponse'); |
| 135 |
recaptchaResponse.value = token; |
| 136 |
}); |
| 137 |
}); |
| 138 |
</script>"; |
| 139 |
$html.= '<input type="hidden" name="recaptcha_response" id="recaptchaResponse">'; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Filter the reCAPTCHA HTML. |
| 144 |
* |
| 145 |
* @since 2.7.4 |
| 146 |
* @deprecated 3.3.5 Use wpmem_captcha instead. |
| 147 |
* |
| 148 |
* @param string $html A string of HTML for the reCAPTCHA. |
| 149 |
*/ |
| 150 |
$html = apply_filters_deprecated( 'wpmem_recaptcha', array( $html ), '3.3.5', 'wpmem_captcha' ); |
| 151 |
|
| 152 |
|
| 153 |
/** |
| 154 |
* Filter the captcha HTML. |
| 155 |
* |
| 156 |
* @since 3.3.5 |
| 157 |
* |
| 158 |
* @param string $html A string of HTML for the registration captcha. |
| 159 |
*/ |
| 160 |
return apply_filters( 'wpmem_captcha', $html ); |
| 161 |
} |
| 162 |
|
| 163 |
/** |
| 164 |
* Create Really Simple CAPTCHA. |
| 165 |
* |
| 166 |
* @since 3.3.0 Replaces wpmem_build_rs_captcha(). |
| 167 |
* |
| 168 |
* @return string|array { |
| 169 |
* HTML string, OR array of form elements for Really Simple CAPTCHA. |
| 170 |
* |
| 171 |
* @type string label_text The raw text used for the label. |
| 172 |
* @type string label The HTML for the label. |
| 173 |
* @type string field The input tag and the CAPTCHA image. |
| 174 |
* } |
| 175 |
*/ |
| 176 |
static function rs_captcha( $return = 'string' ) { |
| 177 |
|
| 178 |
if ( defined( 'REALLYSIMPLECAPTCHA_VERSION' ) ) { |
| 179 |
// setup defaults |
| 180 |
$defaults = array( |
| 181 |
'characters' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789', |
| 182 |
'num_char' => '4', |
| 183 |
'dim_w' => '72', |
| 184 |
'dim_h' => '30', |
| 185 |
'font_color' => '0,0,0', |
| 186 |
'bg_color' => '255,255,255', |
| 187 |
'font_size' => '12', |
| 188 |
'kerning' => '14', |
| 189 |
'img_type' => 'png', |
| 190 |
); |
| 191 |
$opts = get_option( 'wpmembers_captcha' ); |
| 192 |
|
| 193 |
$args = ( isset( $opts['really_simple'] ) && is_array( $opts['really_simple'] ) ) ? $opts['really_simple'] : array(); |
| 194 |
$args = wp_parse_args( $args, $defaults ); |
| 195 |
|
| 196 |
$rs_captcha = new ReallySimpleCaptcha(); |
| 197 |
$rs_captcha->chars = $args['characters']; |
| 198 |
$rs_captcha->char_length = $args['num_char']; |
| 199 |
$rs_captcha->img_size = array( $args['dim_w'], $args['dim_h'] ); |
| 200 |
$rs_captcha->fg = explode( ",", $args['font_color'] ); |
| 201 |
$rs_captcha->bg = explode( ",", $args['bg_color'] ); |
| 202 |
$rs_captcha->font_size = $args['font_size']; |
| 203 |
$rs_captcha->font_char_width = $args['kerning']; |
| 204 |
$rs_captcha->img_type = $args['img_type']; |
| 205 |
|
| 206 |
$rs_captcha_word = $rs_captcha->generate_random_word(); |
| 207 |
$rs_captcha_prefix = mt_rand(); |
| 208 |
$rs_captcha_image_name = $rs_captcha->generate_image( $rs_captcha_prefix, $rs_captcha_word ); |
| 209 |
|
| 210 |
/** |
| 211 |
* Filters the default Really Simple Captcha folder location. |
| 212 |
* |
| 213 |
* @since 3.0 |
| 214 |
* |
| 215 |
* @param string The default location of RS Captcha. |
| 216 |
*/ |
| 217 |
$rs_captcha_image_url = apply_filters( 'wpmem_rs_captcha_folder', get_bloginfo( 'wpurl' ) . '/wp-content/plugins/really-simple-captcha/tmp/' ); |
| 218 |
|
| 219 |
$img_w = $rs_captcha->img_size[0]; |
| 220 |
$img_h = $rs_captcha->img_size[1]; |
| 221 |
$src = $rs_captcha_image_url . $rs_captcha_image_name; |
| 222 |
$size = $rs_captcha->char_length; |
| 223 |
$pre = $rs_captcha_prefix; |
| 224 |
|
| 225 |
/* |
| 226 |
* NOTE: DO NOT EDIT THIS. Use the filter hook found below. |
| 227 |
* |
| 228 |
* Don't know how to use filter or action hooks to customize WP? |
| 229 |
* See https://wpbitz.com/how-to-use-hooks-in-wordpress/ |
| 230 |
*/ |
| 231 |
$captcha_rows_args = array( |
| 232 |
'label_text' => wpmem_get_text( 'register_rscaptcha' ), |
| 233 |
'code_size' => esc_attr( $size ), |
| 234 |
'prefix' => $pre, |
| 235 |
'img_src' => esc_url( $src ), |
| 236 |
'img_w' => esc_attr( $img_w ), |
| 237 |
'img_h' => esc_attr( $img_h ), |
| 238 |
'label' => '<label class="text" for="captcha">' . wpmem_get_text( 'register_rscaptcha' ) . '</label>', |
| 239 |
'field' => '<input id="captcha_code" name="captcha_code" size="' . esc_attr( $size ) . '" type="text" class="textbox" required />', |
| 240 |
'hidden' => '<input id="captcha_prefix" name="captcha_prefix" type="hidden" value="' . esc_attr( $pre ) . '" />', |
| 241 |
'img' => '<img src="' . esc_url( $src ) . '" alt="captcha" width="' . esc_attr( $img_w ) . '" height="' . esc_attr( $img_h ) . '" />', |
| 242 |
); |
| 243 |
|
| 244 |
/** |
| 245 |
* Filter the RS CAPTCHA HTML. |
| 246 |
* |
| 247 |
* @since 3.3.5 |
| 248 |
* |
| 249 |
* @param array |
| 250 |
*/ |
| 251 |
$rows = apply_filters( 'wpmem_rs_captcha_rows', $captcha_rows_args ); |
| 252 |
|
| 253 |
if ( 'array' == $return ) { |
| 254 |
return $rows; |
| 255 |
} else { |
| 256 |
$html = $rows['label'] . $rows['img'] . $rows['hidden'] . $rows['field']; |
| 257 |
/** This filter is defined in /includes/class-wp-members-captcha.php */ |
| 258 |
return apply_filters( 'wpmem_captcha', $html ); |
| 259 |
} |
| 260 |
} else { |
| 261 |
return ( 'array' == $return ) ? array( 'field' => "Really Simple CAPTCHA is not enabled", 'label' => '', 'label_text' => '', 'img' => '', 'hidden' => '' ) : "Really Simple CAPTCHA is not enabled"; |
| 262 |
} |
| 263 |
} |
| 264 |
|
| 265 |
/** |
| 266 |
* Process a captcha. |
| 267 |
* |
| 268 |
* @since 3.1.6 |
| 269 |
* @since 3.3.0 Ported from wpmem_register_handle_captcha() in register.php. |
| 270 |
* @since 3.3.4 Added argument to specify which captcha type to validate. |
| 271 |
* @since 3.4.8 Additional error checking on RS Captchas |
| 272 |
* |
| 273 |
* @global $wpmem_themsg |
| 274 |
* @param $which_captcha |
| 275 |
* @return $string |
| 276 |
*/ |
| 277 |
static function validate( $which_captcha = false, $secret = false ) { |
| 278 |
|
| 279 |
global $wpmem_themsg; |
| 280 |
|
| 281 |
$captcha = ( ! $which_captcha ) ? self::type() : $which_captcha; |
| 282 |
|
| 283 |
if ( 'rs_captcha' == $captcha ) { |
| 284 |
if ( defined( 'REALLYSIMPLECAPTCHA_VERSION' ) ) { |
| 285 |
// Validate Really Simple Captcha. |
| 286 |
$rs_captcha = new ReallySimpleCaptcha(); |
| 287 |
// This variable holds the CAPTCHA image prefix, which corresponds to the correct answer. |
| 288 |
$rs_captcha_prefix = ( wpmem_get( 'captcha_prefix', false ) ); |
| 289 |
if ( ! $rs_captcha_prefix ) { |
| 290 |
$wpmem_themsg = wpmem_get_text( 'rs_captcha_error' ); |
| 291 |
return false; |
| 292 |
} |
| 293 |
// This variable holds the CAPTCHA response, entered by the user. |
| 294 |
$rs_captcha_code = ( wpmem_get('captcha_code', false ) ); |
| 295 |
if ( ! $rs_captcha_code ) { |
| 296 |
$wpmem_themsg = wpmem_get_text( 'rs_captcha_empty' ); |
| 297 |
return false; |
| 298 |
} |
| 299 |
// Check CAPTCHA validity. |
| 300 |
$rs_captcha_correct = ( $rs_captcha->check( $rs_captcha_prefix, $rs_captcha_code ) ) ? true : false; |
| 301 |
// Clean up the tmp directory. |
| 302 |
$rs_captcha->remove( $rs_captcha_prefix ); |
| 303 |
$rs_captcha->cleanup(); |
| 304 |
// If CAPTCHA validation fails (incorrect value entered in CAPTCHA field), return an error. |
| 305 |
if ( ! $rs_captcha_correct ) { |
| 306 |
$wpmem_themsg = wpmem_get_text( 'rs_captcha_wrong' ); |
| 307 |
return false; |
| 308 |
} |
| 309 |
} |
| 310 |
|
| 311 |
} elseif ( 'hcaptcha' == $captcha ) { |
| 312 |
|
| 313 |
// Get the captcha settings (api keys). |
| 314 |
if ( ! $secret ) { |
| 315 |
$opts = get_option( 'wpmembers_captcha' ); |
| 316 |
$secret = $opts['hcaptcha']['secret']; |
| 317 |
} |
| 318 |
|
| 319 |
$captcha = wpmem_get( 'h-captcha-response', false ); |
| 320 |
|
| 321 |
// If there is no captcha value, return error. |
| 322 |
if ( false === $captcha ) { |
| 323 |
$wpmem_themsg = wpmem_get_text( 'reg_empty_captcha' ); |
| 324 |
return false; |
| 325 |
} |
| 326 |
|
| 327 |
// Validate the captcha. |
| 328 |
$response = wp_remote_post( 'https://hcaptcha.com/siteverify', array( |
| 329 |
'body' => array( |
| 330 |
'secret' => $secret, |
| 331 |
'response' => $captcha, |
| 332 |
) ) ); |
| 333 |
|
| 334 |
// Decode the json response. |
| 335 |
$response = json_decode( wp_remote_retrieve_body( $response, true ) ); |
| 336 |
|
| 337 |
if ( $response->success ) { |
| 338 |
// your success code goes here |
| 339 |
} else { |
| 340 |
$wpmem_themsg = wpmem_get_text( 'reg_invalid_captcha' ); |
| 341 |
return false; |
| 342 |
} |
| 343 |
|
| 344 |
} else { |
| 345 |
|
| 346 |
// It is reCAPTCHA. |
| 347 |
$recaptcha_verify_url = 'https://www.google.com/recaptcha/api/siteverify?'; |
| 348 |
|
| 349 |
// Get the captcha settings (api keys). |
| 350 |
if ( ! $secret ) { |
| 351 |
$opts = get_option( 'wpmembers_captcha' ); |
| 352 |
$secret = $opts['recaptcha']['private']; |
| 353 |
} |
| 354 |
|
| 355 |
if ( 'recaptcha_v2' == $captcha ) { |
| 356 |
|
| 357 |
$captcha = wpmem_get( 'g-recaptcha-response', false ); |
| 358 |
|
| 359 |
// If there is no captcha value, return error. |
| 360 |
if ( false === $captcha ) { |
| 361 |
$wpmem_themsg = wpmem_get_text( 'reg_empty_captcha' ); |
| 362 |
return false; |
| 363 |
} |
| 364 |
|
| 365 |
// Build URL for captcha evaluation. |
| 366 |
$url = $recaptcha_verify_url . http_build_query([ |
| 367 |
'secret' => $secret, |
| 368 |
'response' => $captcha, |
| 369 |
'remoteip' => wpmem_get_user_ip(), |
| 370 |
]); |
| 371 |
|
| 372 |
// Validate the captcha. |
| 373 |
$response = wp_remote_fopen( $url ); |
| 374 |
|
| 375 |
// Decode the json response. |
| 376 |
$response = json_decode( $response, true ); |
| 377 |
|
| 378 |
// If captcha validation was unsuccessful. |
| 379 |
if ( false == $response['success'] ) { |
| 380 |
$wpmem_themsg = wpmem_get_text( 'reg_invalid_captcha' ); |
| 381 |
if ( WP_DEBUG && isset( $response['error-codes'] ) ) { |
| 382 |
$wpmem_themsg.= '<br /><br />'; |
| 383 |
foreach( $response['error-codes'] as $code ) { |
| 384 |
$wpmem_themsg.= "Error code: " . $code . "<br />"; |
| 385 |
} |
| 386 |
} |
| 387 |
return false; |
| 388 |
} |
| 389 |
} elseif ( 'recaptcha_v3' == $captcha ) { |
| 390 |
$captcha = wpmem_get( 'recaptcha_response', false ); |
| 391 |
|
| 392 |
if ( false === $captcha ) { |
| 393 |
$wpmem_themsg = wpmem_get_text( 'reg_empty_captcha' ); |
| 394 |
return false; |
| 395 |
} |
| 396 |
|
| 397 |
if ( $_SERVER['REQUEST_METHOD'] === 'POST' && false !== $captcha ) { |
| 398 |
|
| 399 |
// Make and decode POST request: |
| 400 |
$url = $recaptcha_verify_url . http_build_query([ |
| 401 |
'secret' => $secret, |
| 402 |
'response' => $captcha, |
| 403 |
]); |
| 404 |
$recaptcha = file_get_contents( $url ); |
| 405 |
$recaptcha = json_decode( $recaptcha ); |
| 406 |
|
| 407 |
/** |
| 408 |
* Filters the reCAPTCHA v3 score. |
| 409 |
* @link https://developers.google.com/recaptcha/docs/v3 |
| 410 |
* |
| 411 |
* @since 3.3.9 |
| 412 |
* |
| 413 |
* @param int $score |
| 414 |
*/ |
| 415 |
$score = apply_filters( 'wpmem_recaptcha_score', 0.5 ); |
| 416 |
// Take action based on the score returned: |
| 417 |
if ( $recaptcha->score >= $score ) { |
| 418 |
return true; |
| 419 |
} else { |
| 420 |
$wpmem_themsg = wpmem_get_text( 'reg_invalid_captcha' ); |
| 421 |
return false; |
| 422 |
} |
| 423 |
} else { |
| 424 |
return false; |
| 425 |
} |
| 426 |
} |
| 427 |
} |
| 428 |
|
| 429 |
return true; |
| 430 |
} |
| 431 |
} |