Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict Content: Add reCAPTCHA for Tags #752

Merged
merged 12 commits into from
Dec 11, 2024
Merged
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ CONVERTKIT_API_KEY=
CONVERTKIT_API_SECRET=
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS=
CONVERTKIT_API_RECAPTCHA_SITE_KEY=
CONVERTKIT_API_RECAPTCHA_SECRET_KEY=
CONVERTKIT_API_FORM_NAME="Page Form [inline]"
CONVERTKIT_API_FORM_ID="2765139"
CONVERTKIT_API_FORM_FORMAT_MODAL_NAME="Modal Form [modal]"
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
KIT_OAUTH_REDIRECT_URI: ${{ secrets.KIT_OAUTH_REDIRECT_URI }}
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID: ${{ secrets.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }} # ConvertKit API Signed Subscriber ID, stored in the repository's Settings > Secrets
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS: ${{ secrets.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }} # ConvertKit API Signed Subscriber ID with no access to Products, stored in the repository's Settings > Secrets
CONVERTKIT_API_RECAPTCHA_SITE_KEY: ${{ secrets.CONVERTKIT_API_RECAPTCHA_SITE_KEY }} # Google reCAPTCHA v3 Site Key, stored in the repository's Settings > Secrets
CONVERTKIT_API_RECAPTCHA_SECRET_KEY: ${{ secrets.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }} # Google reCAPTCHA v3 Secret Key, stored in the repository's Settings > Secrets

# Defines the WordPress and PHP Versions matrix to run tests on
# WooCommerce 5.9.0 requires WordPress 5.6 or greater, so we do not test on earlier versions
Expand Down Expand Up @@ -218,6 +220,9 @@ jobs:
KIT_OAUTH_REDIRECT_URI=${{ env.KIT_OAUTH_REDIRECT_URI }}
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }}
CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }}
CONVERTKIT_API_RECAPTCHA_SITE_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SITE_KEY }}
CONVERTKIT_API_RECAPTCHA_SECRET_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }}

write-mode: append

# Installs wp-browser, Codeception, PHP CodeSniffer and anything else needed to run tests.
Expand Down
70 changes: 70 additions & 0 deletions admin/class-convertkit-admin-settings-restrict-content.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,53 @@ public function register_fields() {
)
);

// reCAPTCHA.
add_settings_field(
'recaptcha_site_key',
__( 'reCAPTCHA: Site Key', 'convertkit' ),
array( $this, 'text_callback' ),
$this->settings_key,
$this->name,
array(
'name' => 'recaptcha_site_key',
'label_for' => 'recaptcha_site_key',
'description' => array(
__( 'Enter your Google reCAPTCHA v3 Site Key. When specified, this will be used in Member Content by Tag functionality to reduce spam signups.', 'convertkit' ),
),
)
);
add_settings_field(
'recaptcha_secret_key',
__( 'reCAPTCHA: Secret Key', 'convertkit' ),
array( $this, 'text_callback' ),
$this->settings_key,
$this->name,
array(
'name' => 'recaptcha_secret_key',
'label_for' => 'recaptcha_secret_key',
'description' => array(
__( 'Enter your Google reCAPTCHA v3 Secret Key. When specified, this will be used in Member Content by Tag functionality to reduce spam signups.', 'convertkit' ),
),
)
);
add_settings_field(
'recaptcha_minimum_score',
__( 'reCAPTCHA: Minimum Score', 'convertkit' ),
array( $this, 'number_callback' ),
$this->settings_key,
$this->name,
array(
'name' => 'recaptcha_minimum_score',
'label_for' => 'recaptcha_minimum_score',
'min' => 0,
'max' => 1,
'step' => 0.01,
'description' => array(
__( 'Enter the minimum threshold for a subscriber to pass Google reCAPTCHA. A higher number will reduce spam signups (1.0 is very likely a good interaction, 0.0 is very likely a bot).', 'convertkit' ),
),
)
);

// Restrict by Product.
add_settings_field(
'subscribe_heading',
Expand Down Expand Up @@ -345,6 +392,29 @@ public function text_callback( $args ) {

}

/**
* Renders the input for the decimal setting.
*
* @since 2.6.8
*
* @param array $args Setting field arguments (name,description).
*/
public function number_callback( $args ) {

echo $this->get_number_field( // phpcs:ignore WordPress.Security.EscapeOutput
$args['name'],
esc_attr( $this->settings->get_by_key( $args['name'] ) ),
$args['min'], // phpcs:ignore WordPress.Security.EscapeOutput
$args['max'], // phpcs:ignore WordPress.Security.EscapeOutput
$args['step'], // phpcs:ignore WordPress.Security.EscapeOutput
$args['description'], // phpcs:ignore WordPress.Security.EscapeOutput
array(
'widefat',
)
);

}

/**
* Renders the input for the textarea setting.
*
Expand Down
6 changes: 3 additions & 3 deletions admin/section/class-convertkit-settings-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,9 @@ public function get_text_field( $name, $value = '', $description = false, $css_c
*
* @param string $name Name.
* @param string $value Value.
* @param int $min `min` attribute value.
* @param int $max `max` attribute value.
* @param int $step `step` attribute value.
* @param int|float $min `min` attribute value.
* @param int|float $max `max` attribute value.
* @param int|float $step `step` attribute value.
* @param bool|string|array $description Description (false|string|array).
* @param bool|array $css_classes CSS Classes (false|array).
* @return string HTML Field
Expand Down
71 changes: 71 additions & 0 deletions includes/class-convertkit-output-restrict-content.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,61 @@ public function maybe_run_subscriber_authentication() {
break;

case 'tag':
// If Google reCAPTCHA is enabled, check if the submission is spam.
if ( $this->restrict_content_settings->has_recaptcha_site_and_secret_keys() ) {
$response = wp_remote_post(
'https://www.google.com/recaptcha/api/siteverify',
array(
'body' => array(
'secret' => $this->restrict_content_settings->get_recaptcha_secret_key(),
'response' => $_POST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR'],
),
)
);

// Bail if an error occured.
if ( is_wp_error( $response ) ) {
$this->error = $response;
return;
}

// Inspect response.
$body = json_decode( wp_remote_retrieve_body( $response ), true );

// If the request wasn't successful, throw an error.
if ( ! $body['success'] ) {
$this->error = new WP_Error(
'convertkit_output_restrict_content_maybe_run_subscriber_authentication_error',
sprintf(
/* translators: Error codes */
__( 'Google reCAPTCHA failure: %s', 'convertkit' ),
implode( ', ', $body['error-codes'] )
)
);
return;
}

// If the action doesn't match the Plugin action, this might not be a reCAPTCHA request
// for this Plugin.
if ( $body['action'] !== 'convertkit_restrict_content_tag' ) {
// Just silently return.
return;
}

// If the score is less than 0.5 (on a scale of 0.0 to 1.0, with 0.0 being a bot, 1.0 being very good),
// it's likely a spam submission.
if ( $body['score'] < $this->restrict_content_settings->get_recaptcha_minimum_score() ) {
$this->error = new WP_Error(
'convertkit_output_restrict_content_maybe_run_subscriber_authentication_error',
__( 'Google reCAPTCHA failed', 'convertkit' )
);
return;
}

// If here, the submission looks genuine. Continue the request.
}

// Tag the subscriber.
$result = $this->api->tag_subscribe( $this->resource_id, $email );

Expand Down Expand Up @@ -1051,6 +1106,22 @@ function () {
return trim( ob_get_clean() );

case 'tag':
// Enqueue Google reCAPTCHA JS if site and secret keys specified.
if ( $this->restrict_content_settings->has_recaptcha_site_and_secret_keys() ) {
add_filter(
'convertkit_output_scripts_footer',
function ( $scripts ) {

$scripts[] = array(
'src' => 'https://www.google.com/recaptcha/api.js?',
);

return $scripts;

}
);
}

// Output.
ob_start();
include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/tag.php';
Expand Down
110 changes: 97 additions & 13 deletions includes/class-convertkit-settings-restrict-content.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,85 @@ public function permit_crawlers() {

}

/**
* Returns the reCAPTCHA Site Key Plugin setting.
*
* @since 2.6.8
*
* @return string
*/
public function get_recaptcha_site_key() {

return $this->settings['recaptcha_site_key'];

}

/**
* Returns whether the reCAPTCHA Site Key has been set in the Plugin settings.
*
* @since 2.6.8
*
* @return bool
*/
public function has_recaptcha_site_key() {

return ! empty( $this->get_recaptcha_site_key() );

}

/**
* Returns the reCAPTCHA Secret Key Plugin setting.
*
* @since 2.6.8
*
* @return string
*/
public function get_recaptcha_secret_key() {

return $this->settings['recaptcha_secret_key'];

}

/**
* Returns whether the reCAPTCHA Secret Key has been set in the Plugin settings.
*
* @since 2.6.8
*
* @return bool
*/
public function has_recaptcha_secret_key() {

return ! empty( $this->get_recaptcha_secret_key() );

}

/**
* Returns whether the reCAPTCH Site Key and Secret Key are defined
* in the Plugin settings.
*
* @since 2.6.8
*
* @return bool
*/
public function has_recaptcha_site_and_secret_keys() {

return $this->get_recaptcha_site_key() && $this->has_recaptcha_secret_key();

}

/**
* Returns the reCAPTCHA minimum score Plugin setting.
*
* @since 2.6.8
*
* @return float
*/
public function get_recaptcha_minimum_score() {

return (float) $this->settings['recaptcha_minimum_score'];

}

/**
* Returns Restrict Content settings value for the given key.
*
Expand Down Expand Up @@ -114,25 +193,30 @@ public function get_defaults() {

$defaults = array(
// Permit Crawlers.
'permit_crawlers' => '',
'permit_crawlers' => '',

// Google reCAPTCHA.
'recaptcha_site_key' => '',
'recaptcha_secret_key' => '',
'recaptcha_minimum_score' => '0.5',

// Restrict by Product.
'subscribe_heading' => __( 'Read this post with a premium subscription', 'convertkit' ),
'subscribe_text' => __( 'This post is only available to premium subscribers. Join today to get access to all posts.', 'convertkit' ),
'subscribe_heading' => __( 'Read this post with a premium subscription', 'convertkit' ),
'subscribe_text' => __( 'This post is only available to premium subscribers. Join today to get access to all posts.', 'convertkit' ),

// Restrict by Tag.
'subscribe_heading_tag' => __( 'Subscribe to keep reading', 'convertkit' ),
'subscribe_text_tag' => __( 'This post is free to read but only available to subscribers. Join today to get access to all posts.', 'convertkit' ),
'subscribe_heading_tag' => __( 'Subscribe to keep reading', 'convertkit' ),
'subscribe_text_tag' => __( 'This post is free to read but only available to subscribers. Join today to get access to all posts.', 'convertkit' ),

// All.
'subscribe_button_label' => __( 'Subscribe', 'convertkit' ),
'email_text' => __( 'Already subscribed?', 'convertkit' ),
'email_button_label' => __( 'Log in', 'convertkit' ),
'email_heading' => __( 'Log in to read this post', 'convertkit' ),
'email_description_text' => __( 'We\'ll email you a magic code to log you in without a password.', 'convertkit' ),
'email_check_heading' => __( 'We just emailed you a log in code', 'convertkit' ),
'email_check_text' => __( 'Enter the code below to finish logging in', 'convertkit' ),
'no_access_text' => __( 'Your account does not have access to this content. Please use the button above to purchase, or enter the email address you used to purchase the product.', 'convertkit' ),
'subscribe_button_label' => __( 'Subscribe', 'convertkit' ),
'email_text' => __( 'Already subscribed?', 'convertkit' ),
'email_button_label' => __( 'Log in', 'convertkit' ),
'email_heading' => __( 'Log in to read this post', 'convertkit' ),
'email_description_text' => __( 'We\'ll email you a magic code to log you in without a password.', 'convertkit' ),
'email_check_heading' => __( 'We just emailed you a log in code', 'convertkit' ),
'email_check_text' => __( 'Enter the code below to finish logging in', 'convertkit' ),
'no_access_text' => __( 'Your account does not have access to this content. Please use the button above to purchase, or enter the email address you used to purchase the product.', 'convertkit' ),
);

/**
Expand Down
7 changes: 7 additions & 0 deletions resources/frontend/js/restrict-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ document.addEventListener(
}
);

function convertKitRestrictContentTagFormSubmit( token ) {

console.log( token );
document.getElementById( 'convertkit-restrict-content-form' ).submit();

}

/**
* Handles Restrict Content form submission.
*
Expand Down
Loading