PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 3.0.1
Defender Security – Malware Scanner, Login Security & Firewall v3.0.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / controller / recaptcha.php

recaptcha.php in Defender Security – Malware Scanner, Login Security & Firewall 3.0.1, at src/controller/recaptcha.php

997 lines 29.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WP_Defender\Controller;
4
5 use Calotes\Component\Request;
6 use Calotes\Component\Response;
7 use WP_Defender\Component\Config\Config_Hub_Helper;
8 use WP_Defender\Controller;
9 use WP_Defender\Traits\Hummingbird;
10 use WP_Error;
11 use WP_User;
12
13 /**
14 * Class Recaptcha
15 *
16 * @package WP_Defender\Controller
17 * @since 2.5.4
18 */
19 class Recaptcha extends Controller {
20 public const DEFAULT_LOGIN_FORM = 'login',
21 DEFAULT_REGISTER_FORM = 'register',
22 DEFAULT_LOST_PASSWORD_FORM = 'lost_password',
23 DEFAULT_COMMENT_FORM = 'comments';
24
25 public const WOO_LOGIN_FORM = 'woo_login',
26 WOO_REGISTER_FORM = 'woo_register',
27 WOO_LOST_PASSWORD_FORM = 'woo_lost_password';
28
29 use Hummingbird;
30
31 /**
32 * Accepted values: v2_checkbox, v2_invisible, v3_recaptcha.
33 * @var string
34 */
35 private $recaptcha_type;
36
37 /**
38 * Accepted values: light and dark.
39 * @var string
40 */
41 private $recaptcha_theme;
42
43 /**
44 * Accepted values: normal and compact.
45 * @var string
46 */
47 private $recaptcha_size;
48
49 /**
50 * @var string
51 */
52 private $public_key;
53
54 /**
55 * @var string
56 */
57 private $private_key;
58
59 /**
60 * @var string
61 */
62 private $language;
63
64 /**
65 * @var string
66 */
67 private $default_msg;
68
69 /**
70 * Use for cache.
71 *
72 * @var \WP_Defender\Model\Setting\Recaptcha
73 */
74 public $model;
75
76 /**
77 * @var bool
78 */
79 private $is_woo_activated;
80
81 public function __construct() {
82 $this->model = $this->get_model();
83 // Use default msg to avoid empty message error.
84 $default_values = $this->model->get_default_values();
85 $this->default_msg = $default_values['message'];
86 $this->register_routes();
87 $this->is_woo_activated = wd_di()->get( \WP_Defender\Integrations\Woocommerce::class )->is_activated();
88 add_filter( 'wp_defender_advanced_tools_data', array( $this, 'script_data' ) );
89
90 if (
91 $this->model->is_active()
92 // No need the check by is_woo_activated because we use this below.
93 && ( $this->model->enable_default_locations() || $this->model->enable_woo_locations() )
94 && ! $this->exclude_recaptcha_for_requests()
95 ) {
96 $this->declare_variables();
97 $this->add_actions();
98
99 add_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ), 10, 2 );
100 }
101 }
102
103 protected function declare_variables() {
104 $this->recaptcha_type = $this->model->active_type;
105 $this->recaptcha_theme = 'light';
106 $this->recaptcha_size = 'invisible';
107 $this->language = ! empty( $this->model->language ) && 'automatic' !== $this->model->language
108 ? $this->model->language
109 : get_locale();
110
111 // Add the reCAPTCHA keys depending on the reCAPTCHA type.
112 if ( 'v2_checkbox' === $this->recaptcha_type ) {
113 $this->public_key = $this->model->data_v2_checkbox['key'];
114 $this->private_key = $this->model->data_v2_checkbox['secret'];
115 $this->recaptcha_theme = $this->model->data_v2_checkbox['style'];
116 $this->recaptcha_size = $this->model->data_v2_checkbox['size'];
117 } elseif ( 'v2_invisible' === $this->recaptcha_type ) {
118 $this->public_key = $this->model->data_v2_invisible['key'];
119 $this->private_key = $this->model->data_v2_invisible['secret'];
120 } elseif ( 'v3_recaptcha' === $this->recaptcha_type ) {
121 $this->public_key = $this->model->data_v3_recaptcha['key'];
122 $this->private_key = $this->model->data_v3_recaptcha['secret'];
123 }
124 }
125
126 protected function add_actions() {
127 $extra_conditions = is_admin() && ! defined( 'DOING_AJAX' );
128 // @since 2.5.6
129 do_action( 'wd_recaptcha_before_actions', $extra_conditions );
130 if ( $extra_conditions ) {
131 return;
132 }
133 $is_user_logged_in = is_user_logged_in();
134 $locations = $this->model->locations;
135 // Default login form.
136 if ( in_array( self::DEFAULT_LOGIN_FORM, $locations, true ) ) {
137 add_action( 'login_form', array( $this, 'display_login_recaptcha' ) );
138 add_filter( 'wp_authenticate_user', array( $this, 'validate_captcha_field_on_login' ), 8 );
139 }
140 // Default register form.
141 if ( in_array( self::DEFAULT_REGISTER_FORM, $locations, true ) ) {
142 if ( ! is_multisite() ) {
143 add_action( 'register_form', array( $this, 'display_login_recaptcha' ) );
144 add_filter( 'registration_errors', array( $this, 'validate_captcha_field_on_registration' ), 10 );
145 } else {
146 add_action( 'signup_extra_fields', array( $this, 'display_signup_recaptcha' ) );
147 add_action( 'signup_blogform', array( $this, 'display_signup_recaptcha' ) );
148 add_filter( 'wpmu_validate_user_signup', array( $this, 'validate_captcha_field_on_wpmu_registration' ), 10 );
149 }
150 }
151 // Default lost password form.
152 if ( in_array( self::DEFAULT_LOST_PASSWORD_FORM, $locations, true ) ) {
153 add_action( 'lostpassword_form', array( $this, 'display_login_recaptcha' ) );
154 if ( ! $this->is_woocommerce_page() && ! isset( $_POST['wc_reset_password'], $_POST['user_login'] ) ) {
155 add_action( 'lostpassword_post', array( $this, 'validate_captcha_field_on_lostpassword' ) );
156 }
157 }
158 // For Woo forms. Mandatory check for the activated Woo before.
159 if ( $this->is_woo_activated && $this->model->enable_woo_locations() ) {
160 $woo_locations = $this->model->woo_checked_locations;
161 // Woo login form.
162 if ( in_array( self::WOO_LOGIN_FORM, $woo_locations, true ) ) {
163 add_action( 'woocommerce_login_form', array( $this, 'display_login_recaptcha' ) );
164 add_filter( 'woocommerce_process_login_errors', array( $this, 'validate_captcha_field_on_woo_login' ), 10 );
165 }
166 // Woo register form.
167 if ( in_array( self::WOO_REGISTER_FORM, $woo_locations, true ) ) {
168 add_action( 'woocommerce_register_form', array( $this, 'display_login_recaptcha' ) );
169 add_filter( 'woocommerce_registration_errors', array( $this, 'validate_captcha_field_on_woo_registration' ), 10 );
170 }
171 // Woo lost password form.
172 if ( in_array( self::WOO_LOST_PASSWORD_FORM, $woo_locations, true ) ) {
173 add_action( 'woocommerce_lostpassword_form', array( $this, 'display_login_recaptcha' ) );
174 // Use default WP hook because Woo doesn't have own hook, so there's the extra check for Woo form.
175 if ( isset( $_POST['wc_reset_password'], $_POST['user_login'] ) ) {
176 add_action( 'lostpassword_post', array( $this, 'validate_captcha_field_on_lostpassword' ) );
177 }
178 }
179 }
180 // Default comment form.
181 if ( ! $is_user_logged_in && in_array( self::DEFAULT_COMMENT_FORM, $locations, true ) ) {
182 add_action( 'comment_form_after_fields', array( $this, 'display_comment_recaptcha' ) );
183 add_action( 'pre_comment_on_post', array( $this, 'validate_captcha_field_on_comment' ) );
184 // When comments are loaded via Hummingbird's lazy load feature.
185 if ( $this->is_lazy_load_comments_enabled() ) {
186 add_action( 'wp_footer', array( $this, 'add_scripts_for_lazy_load' ) );
187 }
188 }
189 // @since 2.5.6
190 do_action( 'wd_recaptcha_after_actions', $is_user_logged_in );
191 }
192
193 /**
194 * Add the async and defer tag.
195 * @param string $tag
196 * @param string $handle
197 *
198 * @return string
199 */
200 public function script_loader_tag( $tag, $handle ) {
201 if ( 'wpdef_recaptcha_api' === $handle ) {
202 $tag = str_replace( ' src', ' data-cfasync="false" async="async" defer="defer" src', $tag );
203 }
204 return $tag;
205 }
206
207 /**
208 * @return string
209 */
210 protected function get_api_url() {
211 $api_url = null;
212 if ( isset( $this->recaptcha_type ) &&
213 in_array( $this->recaptcha_type, array( 'v2_checkbox', 'v2_invisible' ), true )
214 ) {
215 $api_url = sprintf( 'https://www.google.com/recaptcha/api.js?hl=%s&render=explicit', $this->language );
216 }
217 if ( isset( $this->recaptcha_type ) && 'v3_recaptcha' === $this->recaptcha_type ) {
218 $api_url = sprintf( 'https://www.google.com/recaptcha/api.js?hl=%s&render=%s', $this->language, $this->public_key );
219 }
220 return $api_url;
221 }
222
223 protected function remove_dublicate_scripts() {
224 global $wp_scripts;
225
226 if ( ! is_object( $wp_scripts ) || empty( $wp_scripts ) ) {
227 return false;
228 }
229
230 foreach ( $wp_scripts->registered as $script_name => $args ) {
231 if ( preg_match( '|google\.com/recaptcha/api\.js|', $args->src ) && 'wpdef_recaptcha_api' !== $script_name ) {
232 // Remove a previously enqueued script.
233 wp_dequeue_script( $script_name );
234 }
235 }
236 }
237
238 public function add_scripts() {
239 if ( isset( $this->recaptcha_type ) ) {
240 $this->remove_dublicate_scripts();
241 }
242
243 wp_enqueue_script( 'wpdef_recaptcha_script', plugins_url( 'assets/js/recaptcha_frontend.js', WP_DEFENDER_FILE ), array( 'jquery', 'wpdef_recaptcha_api' ), DEFENDER_VERSION, true );
244 // @since 2.5.6
245 do_action( 'wd_recaptcha_extra_assets' );
246
247 $error_text = __( 'More than one reCAPTCHA has been found in the current form. Please remove all unnecessary reCAPTCHA fields to make it work properly.', 'wpdef' );
248 $options = array(
249 'hl' => $this->language,
250 'size' => $this->recaptcha_size,
251 'version' => $this->recaptcha_type,
252 'sitekey' => $this->public_key,
253 'error' => sprintf( '<strong>%s</strong>:&nbsp;%s', __( 'Warning', 'wpdef' ), $error_text ),
254 // For default comment form.
255 'disable' => '',
256 );
257
258 if ( 'v2_checkbox' === $this->recaptcha_type ) {
259 $options['theme'] = $this->recaptcha_theme;
260 }
261
262 wp_localize_script(
263 'wpdef_recaptcha_script',
264 'WPDEF',
265 array(
266 'options' => $options,
267 'vars' => array(
268 'visibility' => ( 'login_footer' === current_filter() ),
269 ),
270 )
271 );
272 }
273
274 /**
275 * Add scripts when comments are lazy loaded.
276 *
277 * @since 2.6.1
278 */
279 public function add_scripts_for_lazy_load() {
280 if (
281 in_array( $this->recaptcha_type, array( 'v2_checkbox', 'v2_invisible' ), true )
282 && ( is_single() || is_page() )
283 && comments_open()
284 ) {
285 if ( ! wp_script_is( 'wpdef_recaptcha_api', 'registered' ) ) {
286 $api_url = $this->get_api_url();
287 $deps = array( 'jquery' );
288 wp_register_script( 'wpdef_recaptcha_api', $api_url, $deps, DEFENDER_VERSION, true );
289 }
290
291 $this->add_scripts();
292 }
293 }
294
295 /**
296 * Display the reCAPTCHA field.
297 */
298 public function display_login_recaptcha() {
299 if ( 'v2_checkbox' === $this->recaptcha_type ) {
300 $from_width = 302; ?>
301 <style media="screen">
302 .login-action-login #loginform,
303 .login-action-lostpassword #lostpasswordform,
304 .login-action-register #registerform {
305 width: <?php echo $from_width; ?>px !important;
306 }
307 #login_error,
308 .message {
309 width: <?php echo $from_width + 20; ?>px !important;
310 }
311 .login-action-login #loginform .recaptcha_wrap,
312 .login-action-lostpassword #lostpasswordform .recaptcha_wrap,
313 .login-action-register #registerform .recaptcha_wrap {
314 margin-bottom: 10px;
315 }
316 </style>
317 <?php
318 } elseif ( 'v2_invisible' === $this->recaptcha_type ) {
319 ?>
320 <style>
321 .login-action-lostpassword #lostpasswordform .recaptcha_wrap,
322 .login-action-login #loginform .recaptcha_wrap,
323 .login-action-register #registerform .recaptcha_wrap {
324 margin-bottom: 10px;
325 }
326 #signup-content .recaptcha_wrap {
327 margin-top: 10px;
328 }
329 </style>
330 <?php
331 }
332 echo $this->display_recaptcha();
333 }
334
335 /**
336 * @since 2.5.6
337 * @return bool
338 */
339 protected function exclude_recaptcha_for_requests() {
340 $current_request = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/';
341 $excluded_requests = (array) apply_filters( 'wd_recaptcha_excluded_requests', array() );
342
343 return in_array( $current_request, $excluded_requests, true );
344 }
345
346 /**
347 * Display the output of the recaptcha.
348 *
349 * @return string
350 */
351 protected function display_recaptcha() {
352 $deps = null;
353 $content = '<div class="recaptcha_wrap wpdef_recaptcha_' . $this->recaptcha_type . '">';
354 if ( ! $this->private_key || ! $this->public_key || empty( $this->recaptcha_type ) ) {
355 // Display nothing.
356 $content .= '</div>';
357
358 return $content;
359 }
360
361 $api_url = $this->get_api_url();
362
363 // Generate random id value if there's content with pagination plugin for not getting duplicate id values.
364 $id = random_int(0, mt_getrandmax());
365 if ( in_array( $this->recaptcha_type, array( 'v2_checkbox', 'v2_invisible' ), true ) ) {
366 $content .= '<div id="wpdef_recaptcha_' . $id . '" class="wpdef_recaptcha"></div>
367 <noscript>
368 <div style="width: 302px;">
369 <div style="width: 302px; height: 422px; position: relative;">
370 <div style="width: 302px; height: 422px; position: absolute;">
371 <iframe src="https://www.google.com/recaptcha/api/fallback?k=' . $this->public_key . '" frameborder="0" scrolling="no" style="width: 302px; height:422px; border-style: none;"></iframe>
372 </div>
373 </div>
374 <div style="border-style: none; bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px; background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px; height: 60px; width: 300px;">
375 <textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px !important; height: 40px !important; border: 1px solid #c1c1c1 !important; margin: 10px 25px !important; padding: 0px !important; resize: none !important;"></textarea>
376 </div>
377 </div>
378 </noscript>';
379 $deps = array( 'jquery' );
380 } elseif ( 'v3_recaptcha' === $this->recaptcha_type ) {
381 $content .= '<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" />';
382 }
383 $content .= '</div>';
384
385 // Register reCAPTCHA script.
386 $locations = $this->model->locations;
387 if ( ! wp_script_is( 'wpdef_recaptcha_api', 'registered' ) ) {
388
389 if ( 'v3_recaptcha' === $this->recaptcha_type ) {
390 wp_register_script( 'wpdef_recaptcha_api', $api_url, false, null, false );
391 } else {
392 wp_register_script( 'wpdef_recaptcha_api', $api_url, $deps, DEFENDER_VERSION, true );
393 }
394 add_action( 'wp_footer', array( $this, 'add_scripts' ) );
395 if (
396 in_array( self::DEFAULT_LOGIN_FORM, $locations, true )
397 || in_array( self::DEFAULT_REGISTER_FORM, $locations, true )
398 || in_array( self::DEFAULT_LOST_PASSWORD_FORM, $locations, true )
399 ) {
400 add_action( 'login_footer', array( $this, 'add_scripts' ) );
401 }
402 }
403
404 return $content;
405 }
406
407 /**
408 * Check the current page from is from the Woo plugin.
409 *
410 * @retun bool
411 */
412 protected function is_woocommerce_page() {
413 if ( ! $this->is_woo_activated ) {
414 return false;
415 }
416
417 $traces = debug_backtrace();
418 foreach ( $traces as $trace ) {
419 if ( isset( $trace['file'] ) && false !== strpos( $trace['file'], 'woocommerce' ) ) {
420 return true;
421 }
422 }
423
424 return false;
425 }
426
427 /**
428 * Verify the recaptcha code on the Login page.
429 *
430 * @param WP_User|WP_Error $user
431 *
432 * @return WP_Error|WP_User
433 */
434 public function validate_captcha_field_on_login( $user ) {
435 if ( $this->is_woocommerce_page() ) {
436 return $user;
437 }
438 // Skip check if connecting to XMLRPC.
439 if ( defined( 'XMLRPC_REQUEST' ) ) {
440 return $user;
441 }
442
443 if ( ! isset( $_POST['g-recaptcha-response'] ) ) {
444 return $user;
445 }
446
447 if ( ! $this->recaptcha_response( 'default_login' ) ) {
448 if ( is_wp_error( $user ) ) {
449 $user->add( 'invalid_captcha', $this->error_message() );
450 return $user;
451 } else {
452 return new WP_Error( 'invalid_captcha', $this->error_message() );
453 }
454 }
455
456 return $user;
457 }
458
459 /**
460 * Verify the recaptcha code on the Registration page.
461 *
462 * @param WP_Error $errors
463 *
464 * @return WP_Error
465 */
466 public function validate_captcha_field_on_registration( $errors ) {
467 // Skip check if connecting to XMLRPC.
468 if ( defined( 'XMLRPC_REQUEST' ) ) {
469 return $errors;
470 }
471
472 if ( ! $this->recaptcha_response( 'default_registration' ) ) {
473 $errors->add( 'invalid_captcha', $this->error_message() );
474 }
475 $_POST['g-recaptcha-response-check'] = true;
476
477 return $errors;
478 }
479
480 /**
481 * Add google recaptcha to the multisite signup form.
482 *
483 * @param WP_Error $errors
484 */
485 public function display_signup_recaptcha( $errors ) {
486 $error_message = $errors->get_error_message( 'invalid_captcha' );
487 if ( ! empty( $error_message ) ) {
488 printf( '<p class="error">%s</p>', $error_message );
489 }
490 echo $this->display_recaptcha();
491 }
492
493 /**
494 * Verify the recaptcha code on the multisite signup page.
495 *
496 * @param array $result
497 *
498 * @return array|WP_user
499 */
500 public function validate_captcha_field_on_wpmu_registration( $result ) {
501 global $current_user;
502 if ( is_admin() && ! defined( 'DOING_AJAX' ) && ! empty( $current_user->data->ID ) ) {
503 return $result;
504 }
505
506 if ( ! $this->recaptcha_response( 'wpmu_registration' ) ) {
507 if ( isset( $result['errors'] ) && ! empty( $result['errors'] ) ) {
508 $errors = $result['errors'];
509 } else {
510 $errors = new WP_Error();
511 }
512
513 $errors->add( 'invalid_captcha', $this->error_message() );
514 $result['errors'] = $errors;
515 return $result;
516 }
517
518 return $result;
519 }
520
521 /**
522 * Verify the recaptcha code on Woo login page.
523 *
524 * @param WP_Error $errors
525 *
526 * @return WP_Error
527 */
528 public function validate_captcha_field_on_woo_login( $errors ) {
529 // Skip check if connecting to XMLRPC.
530 if ( defined( 'XMLRPC_REQUEST' ) ) {
531 return $errors;
532 }
533
534 if ( ! $this->recaptcha_response( 'woo_login' ) ) {
535 // Remove 'Error: ' because Woo has it by default.
536 $message = str_replace( __( '<strong>Error:</strong> ', 'wpdef' ), '', $this->error_message() );
537 $errors->add( 'invalid_captcha', $message );
538 }
539
540 return $errors;
541 }
542
543 /**
544 * Check recaptcha on Woo registration form.
545 *
546 * @param WP_Error $errors
547 *
548 * @return WP_Error
549 */
550 public function validate_captcha_field_on_woo_registration( $errors ) {
551 if ( defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
552 return $errors;
553 }
554 if ( ! $this->recaptcha_response( 'woo_registration' ) ) {
555 // Remove 'Error: ' because Woo has it by default.
556 $message = str_replace( __( '<strong>Error:</strong> ', 'wpdef' ), '', $this->error_message() );
557 $errors->add( 'invalid_captcha', $message );
558 }
559
560 return $errors;
561 }
562
563 /**
564 * Fires before errors are returned from a password reset request.
565 * Without 2nd `$user_data` parameter because it's since WP 5.4.0.
566 *
567 * @param WP_Error $errors
568 */
569 public function validate_captcha_field_on_lostpassword( $errors ) {
570 if ( ! $this->recaptcha_response( 'default_lost_password' ) ) {
571 $errors->add( 'invalid_captcha', $this->error_message() );
572 }
573 }
574
575 /**
576 * Add google recaptcha to the comment form.
577 */
578 public function display_comment_recaptcha() {
579 echo '<style>#commentform .recaptcha_wrap {margin: 0 0 10px;}</style>';
580 echo $this->display_recaptcha();
581 return true;
582 }
583
584 /**
585 * Check JS enabled for comment form.
586 *
587 * @return void
588 */
589 public function validate_captcha_field_on_comment() {
590 if ( $this->exclude_recaptcha_for_requests() ) {
591 return;
592 }
593
594 if ( ! $this->recaptcha_response( 'default_comments' ) ) {
595 // @since v2.5.6
596 wp_die( (string) apply_filters( 'wd_recaptcha_require_valid_comment', $this->error_message() ) );
597 }
598 }
599
600 /**
601 * @return \WP_Defender\Model\Setting\Recaptcha
602 */
603 private function get_model() {
604 if ( is_object( $this->model ) ) {
605 return $this->model;
606 }
607
608 return new \WP_Defender\Model\Setting\Recaptcha();
609 }
610
611 /**
612 * Get the reCAPTCHA API response.
613 *
614 * @param string $form
615 *
616 * @return bool
617 */
618 protected function recaptcha_response( $form ) {
619 if ( empty( $this->private_key ) || empty( $_POST['g-recaptcha-response'] ) ) {
620 return false;
621 }
622 // reCAPTCHA response post data.
623 $response = stripslashes( sanitize_text_field( $_POST['g-recaptcha-response'] ) );
624 $remote_ip = filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP );
625 // @since v2.5.6
626 $remote_ip = (string) apply_filters( 'wd_recaptcha_remote_ip', $remote_ip );
627
628 $post_body = array(
629 'secret' => $this->private_key,
630 'response' => $response,
631 'remoteip' => $remote_ip,
632 );
633
634 $result = $this->recaptcha_post_request( $post_body );
635 // @since 2.5.6
636 return apply_filters( 'wd_recaptcha_check_result', $result, $form );
637 }
638
639 /**
640 * Display custom error message.
641 *
642 * @return string
643 */
644 private function error_message() {
645
646 return sprintf(
647 /* translators: ... */
648 __( '<strong>Error:</strong> %s', 'wpdef' ),
649 empty( $this->model->message ) ? $this->default_msg : $this->model->message
650 );
651 }
652
653 /**
654 * Send HTTP POST request and return the response.
655 * Also initialize the error text if an error response is received.
656 *
657 * @param array $post_body - HTTP POST body
658 *
659 * @return bool
660 */
661 protected function recaptcha_post_request( $post_body ) {
662 $args = array(
663 'body' => $post_body,
664 'sslverify' => false,
665 );
666 $url = 'https://www.google.com/recaptcha/api/siteverify';
667 $request = wp_remote_post( $url, $args );
668 // Get the request response body
669 if ( is_wp_error( $request ) ) {
670 return false;
671 }
672
673 $response_body = wp_remote_retrieve_body( $request );
674 $response_keys = json_decode( $response_body, true );
675 if ( 'v3_recaptcha' === $this->recaptcha_type ) {
676 if (
677 $response_keys['success']
678 && isset( $this->model->data_v3_recaptcha['threshold'], $response_keys['score'] )
679 && is_numeric( $this->model->data_v3_recaptcha['threshold'] )
680 && is_numeric( $response_keys['score'] )
681 ) {
682 $is_success = $response_keys['score'] >= (float) $this->model->data_v3_recaptcha['threshold'];
683 } else {
684 $is_success = false;
685 }
686 } else {
687 $is_success = (bool) $response_keys['success'];
688 }
689
690 return $is_success;
691 }
692
693 /**
694 * @param array $data
695 *
696 * @return mixed
697 * @throws \ReflectionException
698 */
699 public function script_data( $data ) {
700 $data['recaptcha'] = $this->data_frontend();
701
702 return $data;
703 }
704
705 /**
706 * Save settings.
707 * @param Request $request
708 *
709 * @return Response
710 * @defender_route
711 */
712 public function save_settings( Request $request ) {
713 $data = $request->get_data_by_model( $this->model );
714 $this->model->import( $data );
715 if ( $this->model->validate() ) {
716 $this->model->save();
717 Config_Hub_Helper::set_clear_active_flag();
718
719 return new Response(
720 true,
721 array_merge(
722 array(
723 'message' => __( 'Google reCAPTCHA settings saved successfully.', 'wpdef' ),
724 ),
725 $this->data_frontend()
726 )
727 );
728 }
729
730 return new Response(
731 false,
732 // Merge stored data to avoid errors.
733 array_merge( array( 'message' => $this->model->get_formatted_errors() ), $this->data_frontend() )
734 );
735 }
736
737 /**
738 * @param Request $request
739 *
740 * @return Response
741 * @defender_route
742 */
743 public function load_recaptcha_preview( Request $request ) {
744 $onload = null;
745 $js = null;
746 $data = $request->get_data(
747 array(
748 'captcha_type' => array(
749 'type' => 'string',
750 ),
751 )
752 );
753 $this->recaptcha_type = $data['captcha_type'];
754
755 $model = $this->get_model();
756 $language = ! empty( $model->language ) && 'automatic' !== $model->language ? $model->language : get_locale();
757
758 $notice = '<div class="sui-notice sui-notice-default">';
759 $notice .= '<div class="sui-notice-content">';
760 $notice .= '<div class="sui-notice-message">';
761 $notice .= '<i class="sui-notice-icon sui-icon-info sui-md" aria-hidden="true"></i>';
762 $notice .= '<p>' . esc_html__( 'Save your API keys to load the reCAPTCHA preview.', 'wpdef' ) . '</p>';
763 $notice .= '</div>';
764 $notice .= '</div>';
765 $notice .= '</div>';
766
767 $theme = 'light';
768 $captcha_size = 'invisible';
769 $data_args = '';
770 if ( 'v2_checkbox' === $this->recaptcha_type ) {
771 $this->public_key = $model->data_v2_checkbox['key'];
772 $theme = $model->data_v2_checkbox['style'];
773 $captcha_size = $model->data_v2_checkbox['size'];
774 $onload = 'defender_render_admin_captcha_v2';
775 // Onload method for Recaptcha works only for js 'var'.
776 $js = "<script>var defender_render_admin_captcha_v2 = function () {
777 setTimeout( function () {
778 var captcha_v2 = jQuery( '.defender-g-recaptcha-v2_checkbox' ),
779 sitekey_v2 = captcha_v2.data('sitekey'),
780 theme_v2 = captcha_v2.data('theme'),
781 size_v2 = captcha_v2.data('size')
782 ;
783 window.grecaptcha.render( captcha_v2[0], {
784 sitekey: sitekey_v2,
785 theme: theme_v2,
786 size: size_v2,
787 'error-callback': function() {
788 jQuery('#v2_checkbox_notice_1').hide();
789 jQuery('#v2_checkbox_notice_2').show();
790 }
791 } );
792 }, 100 );
793 };</script>";
794 } elseif ( 'v2_invisible' === $this->recaptcha_type ) {
795 $this->public_key = $model->data_v2_invisible['key'];
796 $onload = 'defender_render_admin_captcha_v2_invisible';
797 $data_args = 'data-badge="inline" data-callback="setResponse"';
798 $js = "<script>var defender_render_admin_captcha_v2_invisible = function () {
799 setTimeout( function () {
800 var captcha = jQuery( '.defender-g-recaptcha-v2_invisible' ),
801 sitekey = captcha.data('sitekey'),
802 theme = captcha.data('theme'),
803 size = captcha.data('size')
804 ;
805 window.grecaptcha.render( captcha[0], {
806 sitekey: sitekey,
807 theme: theme,
808 size: size,
809 badge: 'inline',
810 'error-callback': function() {
811 jQuery('#v2_invisible_notice_1').hide();
812 jQuery('#v2_invisible_notice_2').show();
813 }
814 } );
815 }, 100 );
816 };</script>";
817 } elseif ( 'v3_recaptcha' === $this->recaptcha_type ) {
818 $this->public_key = $model->data_v3_recaptcha['key'];
819 $onload = 'defender_render_admin_captcha_v3';
820 $js = "<script>var defender_render_admin_captcha_v3 = function () {
821 setTimeout( function () {
822 var captcha = jQuery( '.defender-g-recaptcha-v3_recaptcha' ),
823 sitekey = captcha.data('sitekey'),
824 theme = captcha.data('theme'),
825 size = captcha.data('size')
826 ;
827 window.grecaptcha.render( captcha[0], {
828 sitekey: sitekey,
829 theme: theme,
830 size: size,
831 badge: 'inline',
832 'error-callback': function() {
833 jQuery('#v3_recaptcha_notice_1').hide();
834 jQuery('#v3_recaptcha_notice_2').show();
835 }
836 } );
837 }, 100 );
838 };</script>";
839 }
840
841 $html = '';
842 if ( isset( $this->recaptcha_type ) && ! empty( $this->public_key ) ) {
843 $html .= '<script src="https://www.google.com/recaptcha/api.js?hl=' . $language . '&render=explicit&onload=' . $onload . '" async defer></script>' . $js;
844
845 $html .= sprintf(
846 '<div class="%s" data-sitekey="%s" data-theme="%s" data-size="%s" %s></div>',
847 'defender-g-recaptcha-' . $this->recaptcha_type,
848 $this->public_key,
849 $theme,
850 $captcha_size,
851 $data_args
852 );
853 } else {
854 $html .= $notice;
855 }
856
857 return new Response(
858 true,
859 array(
860 'preview' => true,
861 'html' => $html,
862 )
863 );
864 }
865
866 public function remove_settings() {}
867
868 public function remove_data() {
869 $this->get_model()->delete();
870 }
871
872 public function to_array() {}
873
874 /**
875 * @return array
876 */
877 public function data_frontend() {
878 $model = $this->get_model();
879 $is_active = $model->is_active();
880 /**
881 * Different cases for entered keys and locations:
882 * success - default or Woo location is checked,
883 * warning - default and Woo locations are unchecked,
884 * warning - default location is unchecked and Woo is deactivated,
885 * warning - non-entered keys.
886 */
887 if ( $is_active ) {
888 if ( $model->enable_default_locations() || $model->check_woo_locations( $this->is_woo_activated ) ) {
889 switch ( $model->active_type ) {
890 case 'v2_invisible':
891 $type = 'V2 Invisible';
892 break;
893 case 'v3_recaptcha':
894 $type = 'V3';
895 break;
896 case 'v2_checkbox':
897 default:
898 $type = 'V2 Checkbox';
899 break;
900 }
901 $notice_type = 'success';
902 $notice_text = sprintf(
903 /* translators: */
904 __( 'Google reCAPTCHA is currently active. %s type has been set successfully.', 'wpdef' ),
905 $type
906 );
907 } elseif ( ! $this->is_woo_activated && ! $model->enable_default_locations() ) {
908 $notice_type = 'warning';
909 $notice_text = __( 'Google reCAPTCHA is currently inactive for all forms. You can deploy reCAPTCHA for specific forms in the <b>reCAPTCHA Locations</b> below.', 'wpdef' );
910 } elseif ( $this->is_woo_activated && ! $model->enable_woo_locations() && ! $model->enable_default_locations() ) {
911 $notice_type = 'warning';
912 $notice_text = __( 'Google reCAPTCHA is currently inactive for all forms. You can deploy reCAPTCHA for specific forms in the <b>reCAPTCHA Locations</b> or <b>WooCommerce</b> settings below.', 'wpdef' );
913 }
914 } else {
915 // Inactive case.
916 $notice_type = 'warning';
917 $notice_text = __( 'Google reCAPTCHA is currently inactive. Enter your Site and Secret keys and save your settings to finish setup.', 'wpdef' );
918 }
919
920 /**
921 * Cases:
922 * Invalid domain for Site Key,
923 * Google ReCAPTCHA is in localhost,
924 * Cannot contact reCAPTCHA. Check your connection.
925 */
926 $ticket_text = __( 'If you see any errors in the preview, make sure the keys you’ve entered are valid, and you\'ve listed your domain name while generating the keys.', 'wpdef' );
927
928 if ( ( new \WP_Defender\Behavior\WPMUDEV() )->show_support_links() ) {
929 $ticket_text .= sprintf(
930 /* translators: ... */
931 __( 'Still having trouble? <a target="_blank" href="%s">Open a support ticket</a>.', 'wpdef' ),
932 'https://wpmudev.com/forums/forum/support#question'
933 );
934 }
935
936 return array_merge(
937 array(
938 'model' => $model->export(),
939 'is_active' => $is_active,
940 'default_message' => $this->default_msg,
941 'default_locations' => array(
942 self::DEFAULT_LOGIN_FORM => __( 'Login', 'wpdef' ),
943 self::DEFAULT_REGISTER_FORM => __( 'Register', 'wpdef' ),
944 self::DEFAULT_LOST_PASSWORD_FORM => __( 'Lost Password', 'wpdef' ),
945 self::DEFAULT_COMMENT_FORM => __( 'Comments', 'wpdef' ),
946 ),
947 'notice_type' => $notice_type,
948 'notice_text' => $notice_text,
949 'ticket_text' => $ticket_text,
950 'is_woo_active' => $this->is_woo_activated,
951 'woo_locations' => array(
952 self::WOO_LOGIN_FORM => __( 'Login', 'wpdef' ),
953 self::WOO_REGISTER_FORM => __( 'Registration', 'wpdef' ),
954 self::WOO_LOST_PASSWORD_FORM => __( 'Lost Password', 'wpdef' ),
955 ),
956 ),
957 $this->dump_routes_and_nonces()
958 );
959 }
960
961 /**
962 * @return array
963 */
964 public function dashboard_widget() {
965 $model = $this->get_model();
966 $notice_type = 'warning';
967 if ( $model->is_active()
968 && ( $model->enable_default_locations() || $model->check_woo_locations( $this->is_woo_activated ) )
969 ) {
970 $notice_type = 'success';
971 }
972
973 return array(
974 'model' => $model->export(),
975 'notice_type' => $notice_type,
976 );
977 }
978
979 public function import_data( $data ) {
980 $model = $this->get_model();
981
982 $model->import( $data );
983 if ( $model->validate() ) {
984 $model->save();
985 }
986 }
987
988 /**
989 * @return array
990 */
991 public function export_strings() {
992 return array(
993 $this->get_model()->is_active() ? __( 'Active', 'wpdef' ) : __( 'Inactive', 'wpdef' ),
994 );
995 }
996 }
997