: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
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;
protected static $instance;
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new self();
* Recaptcha error message.
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 );
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.
public function maybe_remove_recaptcha_query( $referrer_url ) {
if ( ! $this->using_single_recaptcha() ) {
if ( $this->has_recaptcha_parameter( $referrer_url ) ) {
$referrer_url = add_query_arg( self::TYPE_PARAM, false, $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.
public function maybe_add_blocked_message( $redirect_url, $params ) {
if ( ! $this->using_single_recaptcha() ) {
if ( $params['is_valid'] ) {
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 );
$redirect_url = add_query_arg( self::TYPE_PARAM, self::TYPE_VALUE, $redirect_url );
* Has recaptcha parameter on URL.
* @param string $url $url URL.
* @param string $query_value Query value.
private function has_recaptcha_parameter( $url = '', $query_value = self::TYPE_VALUE ) {
$query_params = ppw_core_get_query_param();
$query_params = ppw_core_get_param_in_url( $url );
if ( ! isset( $query_params[ self::TYPE_PARAM ] ) ) {
return $query_value === $query_params[ self::TYPE_PARAM ];
* Customize error message.
* @param array $params Parameters.
public function maybe_customize_error_message( $params ) {
if ( ! $this->using_single_recaptcha() ) {
if ( $this->has_recaptcha_parameter() ) {
$message = $this->get_error_message();
$params['error_msg'] = apply_filters( 'ppw_recaptcha_error_message', $message, $params );
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;
$result = $this->verify_recaptcha( $_post['g-recaptcha-response'] );
if ( ! $result['success'] ) {
$this->show_message = true;
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 ) ) {
* Get setting api key of recaptcha with current type.
* @param string $type Recaptcha type.
* @param string $default Default value.
public function get_setting_api_key( $type = '', $default = '' ) {
$type = $this->get_recaptcha_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();
* Get setting api secret of recaptcha with current type.
* @param string $type Recaptcha type.
* @param string $default Default value.
public function get_setting_api_secret( $type = '', $default = '' ) {
$type = $this->get_recaptcha_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();
* Get recaptcha v3 API key.
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.
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.
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.
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 );
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.
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' );
public function using_single_recaptcha() {
return $this->using_recaptcha() && in_array( self::SINGLE_PASSWORD, $this->get_password_types() );
* Using sitewide recaptcha
public function using_sitewide_recaptcha() {
return $this->using_recaptcha() && in_array( self::SITEWIDE_PASSWORD, $this->get_password_types() );
* Using sitewide recaptcha
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() {
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
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();
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo esc_attr( $recaptcha_key ); ?>"></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;
echo ob_get_clean(); // phpcs:ignore -- we already escape the $recaptcha_key above
* Verify google recaptcha V3.
* @param string $recaptcha_response Recaptcha response.
public function verify_recaptcha( $recaptcha_response ) {
if ( ! $recaptcha_response ) {
$secret = $this->get_setting_api_secret();
$response = wp_remote_post(
'https://www.google.com/recaptcha/api/siteverify',
'response' => $recaptcha_response,
if ( is_wp_error( $response ) ) {
$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;
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;
* @param string $key Recaptcha API key.
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.
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() ) {
$this->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();
case self::RECAPTCHA_V2_CHECKBOX_TYPE:
case self::RECAPTCHA_V2_INVISIBLE_TYPE:
$this->load_recaptcha_v2_js();
* 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 ) ) {
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>';
return '<input type="hidden" name="g-recaptcha-response" id="ppwRecaptchaResponse" />';
* Customize sitewide css.
public function customize_sitewide_css() {
if ( ! $this->using_sitewide_recaptcha() ) {