File: //proc/self/cwd/wp-content/plugins/password-protect-page/includes/services/class-ppw-recaptcha.php
<?php
/**
*
* Class PPW_Recaptcha
*/
class PPW_Recaptcha {
const TYPE_PARAM = 'ppwp_type';
const TYPE_VALUE = 'recaptcha';
const RECAPTCHA_V3_TYPE = 'recaptcha_v3';
const RECAPTCHA_V2_CHECKBOX_TYPE = 'recaptcha_v2_checkbox';
const RECAPTCHA_V2_INVISIBLE_TYPE = 'recaptcha_v2_invisible';
const SINGLE_PASSWORD = 'single';
const PCP_PASSWORD = 'pcp';
const SITEWIDE_PASSWORD = 'sitewide';
private $show_message = false;
/**
* @var PPW_Recaptcha
*/
protected static $instance;
/**
* @return PPW_Recaptcha
*/
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Recaptcha error message.
*
* @return string
*/
public function get_error_message() {
$message = get_theme_mod( 'ppwp_form_error_recaptcha_message_text', PPW_Constants::DEFAULT_ERROR_RECAPTCHA_MESSAGE );
$message = wp_kses_post( $message );
return _x( $message, PPW_Constants::CONTEXT_PASSWORD_FORM, PPW_Constants::DOMAIN );
}
/**
* Register hooks.
* @since 1.0.0
*/
public function register() {
add_filter( 'ppwp_customize_ppf', array( $this, 'maybe_customize_error_message' ), 25 );
add_filter( 'ppwp_ppf_redirect_url', array( $this, 'maybe_add_blocked_message' ), 20, 2 );
add_filter( 'ppwp_ppf_referrer_url', array( $this, 'maybe_remove_recaptcha_query' ), 10, 2 );
add_filter( 'ppwpea_recaptcha_v2_site_key', array( $this, 'get_ppwpea_recaptcha_v2_api_key' ), 10 );
add_filter( 'ppwpea_recaptcha_v2_secret', array( $this, 'get_ppwpea_recaptcha_v2_api_secret' ), 10 );
add_action( 'wp_footer', array( $this, 'load_js_in_footer' ), 10 );
add_action( 'ppw_custom_footer_form_entire_site', array( $this, 'maybe_load_sitewide_recaptcha_js' ), 10 );
add_action( 'ppw_sitewide_above_submit_button', array( $this, 'maybe_add_recaptcha_input_below_sitewide_form' ), 10 );
add_action( 'ppw_sitewide_custom_internal_css', array( $this, 'customize_sitewide_css' ), 10 );
add_filter( 'ppw_sitewide_valid_password', array( $this, 'validate_sitewide_password' ), 10 );
}
/**
* Remove blocked query if user enter right password.
*
* @param string $referrer_url Referrer URL.
*
* @return string
*/
public function maybe_remove_recaptcha_query( $referrer_url ) {
if ( ! $this->using_single_recaptcha() ) {
return $referrer_url;
}
if ( $this->has_recaptcha_parameter( $referrer_url ) ) {
$referrer_url = add_query_arg( self::TYPE_PARAM, false, $referrer_url );
}
return $referrer_url;
}
public function using_recaptcha() {
return ppw_core_get_setting_type_bool_by_option_name( PPW_Constants::USING_RECAPTCHA, PPW_Constants::EXTERNAL_OPTIONS );
}
/**
* Add blocked message if user turn on option.
*
* @param string $redirect_url Redirect URL.
* @param array $params Parameters.
*
* @return string
*/
public function maybe_add_blocked_message( $redirect_url, $params ) {
if ( ! $this->using_single_recaptcha() ) {
return $redirect_url;
}
if ( $params['is_valid'] ) {
return $redirect_url;
}
if ( ! $this->show_message ) {
// Remove blocked parameter if URL has it.
if ( $this->has_recaptcha_parameter( $redirect_url ) ) {
$redirect_url = add_query_arg( self::TYPE_PARAM, false, $redirect_url );
}
return $redirect_url;
}
$redirect_url = add_query_arg( self::TYPE_PARAM, self::TYPE_VALUE, $redirect_url );
return $redirect_url;
}
/**
* Has recaptcha parameter on URL.
*
* @param string $url $url URL.
* @param string $query_value Query value.
*
* @return bool
*/
private function has_recaptcha_parameter( $url = '', $query_value = self::TYPE_VALUE ) {
if ( empty( $url ) ) {
$query_params = ppw_core_get_query_param();
} else {
$query_params = ppw_core_get_param_in_url( $url );
}
if ( ! isset( $query_params[ self::TYPE_PARAM ] ) ) {
return false;
}
return $query_value === $query_params[ self::TYPE_PARAM ];
}
/**
* Customize error message.
*
* @param array $params Parameters.
*
* @return array
*/
public function maybe_customize_error_message( $params ) {
if ( ! $this->using_single_recaptcha() ) {
return $params;
}
if ( $this->has_recaptcha_parameter() ) {
$message = $this->get_error_message();
$params['error_msg'] = apply_filters( 'ppw_recaptcha_error_message', $message, $params );
}
return $params;
}
/**
* Validate recaptcha.
*
* @return bool
*/
public function is_valid_recaptcha() {
$_post = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- We no need to handle nonce verification here because already handle on parent function.
if ( ! isset( $_post['g-recaptcha-response'] ) ) {
$this->show_message = true;
return false;
}
$result = $this->verify_recaptcha( $_post['g-recaptcha-response'] );
if ( ! $result['success'] ) {
$this->show_message = true;
return false;
}
return true;
}
/**
* Get limit score.
*
* @return double
*/
public function get_limit_score() {
$score = ppw_core_get_settings_by_option_name( PPW_Constants::RECAPTCHA_SCORE, PPW_Constants::EXTERNAL_OPTIONS );
if ( is_null( $score ) ) {
return (double) 0.5;
}
return (double) $score;
}
/**
* Get setting api key of recaptcha with current type.
*
* @param string $type Recaptcha type.
* @param string $default Default value.
*
* @return string
*/
public function get_setting_api_key( $type = '', $default = '' ) {
if ( empty( $type ) ) {
$type = $this->get_recaptcha_type();
}
switch ( $type ) {
case self::RECAPTCHA_V3_TYPE:
return $this->get_recaptcha_v3_api_key();
case self::RECAPTCHA_V2_CHECKBOX_TYPE:
return $this->get_recaptcha_v2_api_key();
default:
return $default;
}
}
/**
* Get setting api secret of recaptcha with current type.
*
* @param string $type Recaptcha type.
* @param string $default Default value.
*
* @return string
*/
public function get_setting_api_secret( $type = '', $default = '' ) {
if ( empty( $type ) ) {
$type = $this->get_recaptcha_type();
}
switch ( $type ) {
case self::RECAPTCHA_V3_TYPE:
return $this->get_recaptcha_v3_api_secret();
case self::RECAPTCHA_V2_CHECKBOX_TYPE:
return $this->get_recaptcha_v2_api_secret();
default:
return $default;
}
}
/**
* Get recaptcha v3 API key.
*
* @return string
*/
public function get_recaptcha_v3_api_key() {
return ppw_core_get_setting_type_string_by_option_name( PPW_Constants::RECAPTCHA_API_KEY, PPW_Constants::EXTERNAL_OPTIONS );
}
/**
* Get recaptcha v3 API secret.
*
* @return string
*/
public function get_recaptcha_v3_api_secret() {
return ppw_core_get_setting_type_string_by_option_name( PPW_Constants::RECAPTCHA_API_SECRET, PPW_Constants::EXTERNAL_OPTIONS );
}
/**
* Get recaptcha v2 API key.
*
* @return string
*/
public function get_recaptcha_v2_api_key() {
return ppw_core_get_setting_type_string_by_option_name( PPW_Constants::RECAPTCHA_V2_CHECKBOX_API_KEY, PPW_Constants::EXTERNAL_OPTIONS );
}
/**
* Get recaptcha v2 API secret.
*
* @return string
*/
public function get_recaptcha_v2_api_secret() {
return ppw_core_get_setting_type_string_by_option_name( PPW_Constants::RECAPTCHA_V2_CHECKBOX_API_SECRET, PPW_Constants::EXTERNAL_OPTIONS );
}
/**
* Get recaptcha type.
*
* @return string
*/
public function get_recaptcha_type() {
$recaptcha_type = ppw_core_get_setting_type_string_by_option_name( PPW_Constants::RECAPTCHA_TYPE, PPW_Constants::EXTERNAL_OPTIONS );
return $recaptcha_type ? $recaptcha_type : self::RECAPTCHA_V3_TYPE;
}
/**
* Get password types selected.
*
* @return string[]
*/
public function get_password_types() {
$password_types = ppw_core_get_setting_type_array_by_option_name( PPW_Constants::RECAPTCHA_PASSWORD_TYPES, PPW_Constants::EXTERNAL_OPTIONS );
return $password_types ? $password_types : array( 'single' );
}
/**
* Using single recaptcha
*
* @return bool
*/
public function using_single_recaptcha() {
return $this->using_recaptcha() && in_array( self::SINGLE_PASSWORD, $this->get_password_types() );
}
/**
* Using sitewide recaptcha
*
* @return bool
*/
public function using_sitewide_recaptcha() {
return $this->using_recaptcha() && in_array( self::SITEWIDE_PASSWORD, $this->get_password_types() );
}
/**
* Using sitewide recaptcha
*
* @return bool
*/
public function using_pcp_recaptcha() {
return $this->using_recaptcha() && in_array( self::PCP_PASSWORD, $this->get_password_types() );
}
/**
* Load recaptcha v2 javascript.
*/
public function load_recaptcha_v2_js() {
ob_start();
?>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<?php
echo ob_get_clean(); // phpcs:ignore -- we cannot escape ob_start ob_get_clean(), there are no variable to escape in statement above
}
/**
* Load recaptcha v3 javascript.
*/
public function load_recaptcha_v3_js() {
$recaptcha_key = $this->get_recaptcha_v3_api_key();
ob_start();
?>
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo esc_attr( $recaptcha_key ); ?>"></script>
<script>
grecaptcha.ready(function () {
grecaptcha.execute('<?php echo esc_attr( $recaptcha_key ); ?>', {action: 'enter_password'}).then(function (token) {
var recaptchaResponse = document.getElementById('ppwRecaptchaResponse');
recaptchaResponse.value = token;
});
});
</script>
<?php
echo ob_get_clean(); // phpcs:ignore -- we already escape the $recaptcha_key above
}
/**
* Verify google recaptcha V3.
*
* @param string $recaptcha_response Recaptcha response.
*
* @return array
*/
public function verify_recaptcha( $recaptcha_response ) {
$default = array(
'success' => false,
'message' => '',
);
if ( ! $recaptcha_response ) {
return $default;
}
$secret = $this->get_setting_api_secret();
if ( ! $secret ) {
return $default;
}
$response = wp_remote_post(
'https://www.google.com/recaptcha/api/siteverify',
array(
'method' => 'POST',
'timeout' => 45,
'redirection' => 5,
'httpversion' => '1.0',
'blocking' => true,
'headers' => array(),
'body' => array(
'secret' => $secret,
'response' => $recaptcha_response,
),
'cookies' => array(),
)
);
if ( is_wp_error( $response ) ) {
return $default;
}
$body = wp_remote_retrieve_body( $response );
$body = json_decode( $body );
// Whether this request was a valid reCAPTCHA token for your site.
$success = isset( $body->success ) && $body->success;
$external = true;
if ( $this->get_recaptcha_type() === self::RECAPTCHA_V3_TYPE ) {
$limit_score = $this->get_limit_score();
// The score for this request (0.0 - 1.0) 1.0 is very likely a good interaction, 0.0 is very likely a bot.
$score = isset( $body->score ) ? (double) $body->score : 0;
$external = $score > $limit_score;
}
$default['success'] = $success && $external;
return $default;
}
/**
* Load PPWPEA api key.
*
* @param string $key Recaptcha API key.
*
* @return string
*/
public function get_ppwpea_recaptcha_v2_api_key( $key ) {
return $this->get_recaptcha_v2_api_key();
}
/**
* Load PPWPEA api secret.
*
* @param string $secret Recaptcha API secret.
*
* @return string
*/
public function get_ppwpea_recaptcha_v2_api_secret( $secret ) {
return $this->get_recaptcha_v2_api_secret();
}
public function maybe_load_sitewide_recaptcha_js() {
if ( ! $this->using_sitewide_recaptcha() ) {
return;
}
$this->add_recaptcha_to_head();
}
/**
* Add recaptcha to head.
*/
public function add_recaptcha_to_head() {
$recaptcha_type = $this->get_recaptcha_type();
switch ( $recaptcha_type ) {
case self::RECAPTCHA_V3_TYPE:
$this->load_recaptcha_v3_js();
break;
case self::RECAPTCHA_V2_CHECKBOX_TYPE:
case self::RECAPTCHA_V2_INVISIBLE_TYPE:
$this->load_recaptcha_v2_js();
break;
}
}
/**
* Add recaptcha input below sitewide form.
*/
public function maybe_add_recaptcha_input_below_sitewide_form() {
$recaptcha_input = $this->get_recaptcha_input();
if ( ! empty( $recaptcha_input ) ) {
echo $recaptcha_input;
}
}
/**
* Get recaptcha input.
*
* @return string
*/
public function get_recaptcha_input() {
switch ( $this->get_recaptcha_type() ) {
case PPW_Recaptcha::RECAPTCHA_V2_CHECKBOX_TYPE:
$site_key = $this->get_recaptcha_v2_api_key();
return '<div class="ppw-recaptcha g-recaptcha" data-sitekey="' . $site_key . '"></div>';
default:
return '<input type="hidden" name="g-recaptcha-response" id="ppwRecaptchaResponse" />';
}
}
/**
* Customize sitewide css.
*/
public function customize_sitewide_css() {
if ( ! $this->using_sitewide_recaptcha() ) {
return;
}
?>
.g-recaptcha {
transform:scale(0.9);
transform-origin:0 0;
}
<?php
}
/**
* Validate sitewide password form.
*
* @param $validated
*
* @return bool
*/
public function validate_sitewide_password( $validated ) {
if ( ! $validated ) {
return $validated;
}
if ( ! $this->using_sitewide_recaptcha() ) {
return $validated;
}
if ( ! $this->is_valid_recaptcha() ) {
add_filter( 'ppw_sitewide_error_message', array( $this, 'get_sitewide_error_message' ) );
return false;
}
return $validated;
}
/**
* Get sitewide error message.
*
* @return string
*/
public function get_sitewide_error_message() {
return __( 'Google reCAPTCHA verification failed, please try again later.', PPW_Constants::DOMAIN );
}
/**
* Load JS in footer.
*/
public function load_js_in_footer() {
if ( ! $this->using_single_recaptcha() ) {
return;
}
$allowed = is_singular();
$allowed = apply_filters( 'ppw_recaptcha_allowed_to_load_script', $allowed );
if ( ! $allowed ) {
return;
}
$post_id = get_the_ID();
if ( ! $post_id || ! post_password_required( $post_id ) ) {
return;
}
$this->add_recaptcha_to_head();
}
}