Skip to content

Commit

Permalink
Merge pull request #752 from Kit/restrict-content-add-recaptcha-settings
Browse files Browse the repository at this point in the history
Restrict Content: Add reCAPTCHA for Tags
  • Loading branch information
n7studios authored Dec 11, 2024
2 parents cbdf777 + b6a1ed5 commit 84461aa
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 60 deletions.
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

0 comments on commit 84461aa

Please sign in to comment.