From dc08772f5b906f4042690c7a171b1c6021a36064 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 15 Jul 2026 12:30:34 +0800 Subject: [PATCH 1/5] Member Content: Tags: Require Login --- ...vertkit-admin-section-restrict-content.php | 34 -------- ...ass-convertkit-output-restrict-content.php | 78 +------------------ ...s-convertkit-settings-restrict-content.php | 14 ---- views/frontend/restrict-content/tag.php | 5 +- 4 files changed, 5 insertions(+), 126 deletions(-) diff --git a/admin/section/class-convertkit-admin-section-restrict-content.php b/admin/section/class-convertkit-admin-section-restrict-content.php index 7107876bf..0d0cb9955 100644 --- a/admin/section/class-convertkit-admin-section-restrict-content.php +++ b/admin/section/class-convertkit-admin-section-restrict-content.php @@ -166,20 +166,6 @@ public function register_fields() { ) ); - add_settings_field( - 'require_tag_login', - __( 'Require Login', 'convertkit' ), - array( $this, 'require_tag_login_callback' ), - $this->settings_key, - $this->name . '-tags', - array( - 'name' => 'require_tag_login', - 'label_for' => 'require_tag_login', - 'label' => __( 'When checked, subscribers are sent a code in an email to login after being subscribed and tagged.', 'convertkit' ), - 'description' => '', - ) - ); - // All. add_settings_field( 'subscribe_button_label', @@ -453,26 +439,6 @@ public function permit_crawlers_callback( $args ) { } - /** - * Renders the input for the Require Login setting. - * - * @since 2.7.2 - * - * @param array $args Setting field arguments (name,description). - */ - public function require_tag_login_callback( $args ) { - - // Output field. - $this->output_checkbox_field( - $args['name'], - 'on', - $this->settings->require_tag_login(), - $args['label'], - $args['description'] - ); - - } - /** * Renders the input for the text setting. * diff --git a/includes/class-convertkit-output-restrict-content.php b/includes/class-convertkit-output-restrict-content.php index 6809455e6..90b471da0 100644 --- a/includes/class-convertkit-output-restrict-content.php +++ b/includes/class-convertkit-output-restrict-content.php @@ -413,26 +413,8 @@ public function maybe_run_subscriber_authentication() { $this->error = $result; return; } - - // If require login is disabled, return now. - if ( ! $this->restrict_content_settings->require_tag_login() ) { - // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email. - $subscriber = new ConvertKit_Subscriber(); - $subscriber->forget(); - - // Fetch the subscriber ID from the result. - $subscriber_id = $result['subscriber']['id']; - - // Store subscriber ID in cookie. - $this->store_subscriber_id_in_cookie( $subscriber_id ); - - // Redirect. - $this->redirect( $this->post_id ); - return; - } } - // If here, require login is enabled for tags or this is a product/form. // Run subscriber authentication. $result = $this->subscriber_authentication_send_code( $email, $this->post_id ); @@ -1077,32 +1059,14 @@ private function resource_exists() { */ private function subscriber_has_access( $subscriber_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter - // Depending on the resource type, determine if the subscriber has access to it. - // This is deliberately a switch statement, because we will likely add in support - // for restrict by tag and form later. switch ( $this->resource_type ) { case 'product': - // For products, the subscriber ID has to be a signed subscriber ID string. return $this->subscriber_has_access_to_product_by_signed_subscriber_id( $subscriber_id, absint( $this->resource_id ) ); case 'form': - // For forms, the subscriber ID has to be a signed subscriber ID string. return $this->subscriber_has_access_to_form_by_signed_subscriber_id( $subscriber_id, absint( $this->resource_id ) ); case 'tag': - // If the subscriber ID is numeric, check using get_subscriber_tags(). - if ( is_numeric( $subscriber_id ) ) { - // If require login is enabled, only a signed subscriber ID is accepted, as this is generated - // via the subscriber verify email flow. - if ( $this->restrict_content_settings->require_tag_login() ) { - return false; - } - - return $this->subscriber_has_access_to_tag_by_subscriber_id( $subscriber_id, absint( $this->resource_id ) ); - } - - // The subscriber ID is a signed subscriber ID string. - // Check using profile(). return $this->subscriber_has_access_to_tag_by_signed_subscriber_id( $subscriber_id, absint( $this->resource_id ) ); } @@ -1202,44 +1166,6 @@ private function subscriber_has_access_to_tag_by_signed_subscriber_id( $signed_s } - /** - * Determines if the given signed subscriber ID has an active subscription to - * the given tag. - * - * @since 2.7.1 - * - * @param int $subscriber_id Subscriber ID. - * @param int $tag_id Tag ID. - * @return bool Has access to tag - */ - private function subscriber_has_access_to_tag_by_subscriber_id( $subscriber_id, $tag_id ) { - - // Get tags that the subscriber has been assigned. - $tags = $this->api->get_subscriber_tags( $subscriber_id ); - - // If an error occurred, the subscriber ID is invalid. - if ( is_wp_error( $tags ) ) { - return false; - } - - // If no tags exist, there's no access. - if ( ! count( $tags['tags'] ) ) { - return false; - } - - // Iterate through the subscriber's tags to see if they have the required tag. - foreach ( $tags['tags'] as $tag ) { - if ( $tag['id'] === $tag_id ) { - // Subscriber has the required tag assigned to them - grant access. - return true; - } - } - - // If here, the subscriber does not have the tag. - return false; - - } - /** * Gets the subscriber ID from the request (either the cookie or the URL). * @@ -1530,9 +1456,9 @@ function () use ( $post_id, $resource_id, $resource_type ) { $heading = $this->restrict_content_settings->get_by_key( 'subscribe_heading_tag' ); $text = $this->restrict_content_settings->get_by_key( 'subscribe_text_tag' ); - // If require login is enabled and scripts are enabled, output the email login form in a modal, which will be displayed + // If scripts are enabled, output the email login form in a modal, which will be displayed // when the 'log in' link is clicked. - if ( $this->restrict_content_settings->require_tag_login() && ! $this->settings->scripts_disabled() ) { + if ( ! $this->settings->scripts_disabled() ) { add_action( 'wp_footer', function () use ( $post_id, $resource_id, $resource_type ) { diff --git a/includes/class-convertkit-settings-restrict-content.php b/includes/class-convertkit-settings-restrict-content.php index 022aabd75..2330d8a4d 100644 --- a/includes/class-convertkit-settings-restrict-content.php +++ b/includes/class-convertkit-settings-restrict-content.php @@ -77,19 +77,6 @@ public function permit_crawlers() { } - /** - * Returns whether login by email is required for Member Content by Tag functionality. - * - * @since 2.7.2 - * - * @return bool - */ - public function require_tag_login() { - - return ( $this->settings['require_tag_login'] === 'on' ? true : false ); - - } - /** * Returns Restrict Content settings value for the given key. * @@ -140,7 +127,6 @@ public function get_defaults() { // 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' ), - 'require_tag_login' => '', // blank|on. 'no_access_text_tag' => __( 'Your account does not have access to this content. Please use the form above to subscribe.', 'convertkit' ), // All. diff --git a/views/frontend/restrict-content/tag.php b/views/frontend/restrict-content/tag.php index d06112717..685e51f48 100644 --- a/views/frontend/restrict-content/tag.php +++ b/views/frontend/restrict-content/tag.php @@ -43,8 +43,9 @@ restrict_content_settings->require_tag_login() && ! $this->settings->scripts_disabled() ) { + // Output a login link or form if scripts are enabled. Login is always + // required for tag-restricted content, matching form/product behaviour. + if ( ! $this->settings->scripts_disabled() ) { require 'login.php'; } From 16b3784ae15035417e8f0a2570a42f145d6f9d09 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 15 Jul 2026 12:58:55 +0800 Subject: [PATCH 2/5] Update docblock --- includes/class-convertkit-output-restrict-content.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/class-convertkit-output-restrict-content.php b/includes/class-convertkit-output-restrict-content.php index 90b471da0..3bbd7e88c 100644 --- a/includes/class-convertkit-output-restrict-content.php +++ b/includes/class-convertkit-output-restrict-content.php @@ -345,11 +345,11 @@ public function initialize_classes() { /** * If the user isn't using JavaScript, or the Plugin's Disable JS is enabled, checks if the request is a Restrict Content request with an email address. - * Also runs if restrict content by tag and require login is disabled, as we immediately tag and redirect if this is the case. * If so, calls the API depending on the Restrict Content resource that's required: - * - tag: subscribes the email address to the tag, storing the subscriber ID in a cookie and redirecting - * - product: calls the API to send the subscriber a magic link by email containing a code. See maybe_run_subscriber_verification() - * for logic once they click the link in the email or enter the code on screen. + * - tag: subscribes the email address to the tag, and calls the API to send the subscriber a magic link by email containing a code. + * - form + product: calls the API to send the subscriber a magic link by email containing a code. + * + * See maybe_run_subscriber_verification() for logic once they click the link in the email or enter the code on screen. * * @since 2.1.0 */ From c2d272ed6bc8ba1fd63e3309a4c47171c82870e3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 15 Jul 2026 13:00:33 +0800 Subject: [PATCH 3/5] Updated tests --- ...ass-convertkit-output-restrict-content.php | 2 +- .../PluginSettingsToolsCest.php | 9 +- .../general/RestrictContentSettingsCest.php | 3 - .../general/RestrictContentTagCest.php | 140 +----------------- tests/Support/Helper/KitRestrictContent.php | 56 +------ 5 files changed, 10 insertions(+), 200 deletions(-) diff --git a/includes/class-convertkit-output-restrict-content.php b/includes/class-convertkit-output-restrict-content.php index 3bbd7e88c..e43883449 100644 --- a/includes/class-convertkit-output-restrict-content.php +++ b/includes/class-convertkit-output-restrict-content.php @@ -348,7 +348,7 @@ public function initialize_classes() { * If so, calls the API depending on the Restrict Content resource that's required: * - tag: subscribes the email address to the tag, and calls the API to send the subscriber a magic link by email containing a code. * - form + product: calls the API to send the subscriber a magic link by email containing a code. - * + * * See maybe_run_subscriber_verification() for logic once they click the link in the email or enter the code on screen. * * @since 2.1.0 diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsCest.php index 207fe1d25..2751a4571 100644 --- a/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsCest.php +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsCest.php @@ -154,7 +154,7 @@ public function testExportAndImportValidConfiguration(EndToEndTester $I) $I->setupKitPluginRestrictContent( $I, [ - 'require_tag_login' => 'on', + 'permit_crawlers' => 'on', ] ); $I->setupKitPluginBroadcasts( @@ -184,8 +184,7 @@ public function testExportAndImportValidConfiguration(EndToEndTester $I) $I->seeInThisFile('{"settings":{"access_token":"' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'] . '","refresh_token":"' . $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN'] . '"'); // Confirm some expected Restrict Content settings data is included. - $I->seeInThisFile('"restrict_content":{"permit_crawlers":'); - $I->seeInThisFile('require_tag_login":"on"'); + $I->seeInThisFile('"restrict_content":{"permit_crawlers":"on"'); // Confirm some expected Broadcasts settings data is included. $I->seeInThisFile('"broadcasts":{"enabled":"on"'); @@ -221,8 +220,8 @@ public function testExportAndImportValidConfiguration(EndToEndTester $I) // Assert Restrict Content settings updated from imported configuration. $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings_restrict_content'); - $I->assertArrayHasKey('require_tag_login', $settings); - $I->assertEquals($settings['require_tag_login'], 'on'); + $I->assertArrayHasKey('permit_crawlers', $settings); + $I->assertEquals($settings['permit_crawlers'], 'on'); // Assert Broadcasts settings updated from imported configuration. $settings = $I->grabOptionFromDatabase('_wp_convertkit_settings_broadcasts'); diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php index b8ad741bd..ca4b9626e 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php @@ -102,7 +102,6 @@ public function testSaveBlankSettings(EndToEndTester $I) 'subscribe_heading_tag' => '', 'subscribe_text_tag' => '', 'no_access_text_tag' => '', - 'require_tag_login' => '', // All. 'subscribe_button_label' => '', @@ -157,7 +156,6 @@ public function testSaveSettings(EndToEndTester $I) 'subscribe_heading_tag' => 'Subscribe Heading Tag', 'subscribe_text_tag' => 'Subscribe Text Tag', 'no_access_text_tag' => 'No Access Text Tag', - 'require_tag_login' => 'on', // All. 'subscribe_button_label' => 'Subscribe Button Label', @@ -240,7 +238,6 @@ public function _setupKitPluginRestrictContent($I, $settings = false) foreach ( $settings as $key => $value ) { switch ( $key ) { case 'permit_crawlers': - case 'require_tag_login': if ( $value ) { $I->checkOption('_wp_convertkit_settings_restrict_content[' . $key . ']'); } else { diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php index abc1890e8..5c84db649 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php @@ -73,135 +73,6 @@ public function testRestrictContentByTag(EndToEndTester $I) /** * Test that restricting content by a Tag specified in the Page Settings works when: - * - the Plugin is set to Require Login, - * - creating and viewing a new WordPress Page, - * - entering an email address displays the code verification screen - * - using a signed subscriber ID that has access to the Tag displays the content. - * - * @since 2.7.2 - * - * @param EndToEndTester $I Tester. - */ - public function testRestrictContentByTagWithRequireLoginEnabled(EndToEndTester $I) - { - // Setup Kit Plugin, disabling JS. - $I->setupKitPluginDisableJS($I); - $I->setupKitPluginResources($I); - - // Setup Restrict Content functionality with Require Login enabled. - $I->setupKitPluginRestrictContent( - $I, - [ - 'require_tag_login' => 'on', - ] - ); - - // Add a Page using the Gutenberg editor. - $I->addGutenbergPage( - $I, - title: 'Kit: Page: Restrict Content: Tag: Require Login' - ); - - // Configure metabox's Restrict Content setting = Tag name. - $I->configurePluginSidebarSettings( - $I, - form: 'None', - restrictContent: $_ENV['CONVERTKIT_API_TAG_NAME'] - ); - - // Add blocks. - $I->addGutenbergParagraphBlock($I, 'Visible content.'); - $I->addGutenbergBlock( - $I, - blockName: 'More', - blockProgrammaticName: 'more' - ); - $I->addGutenbergParagraphBlock($I, 'Member-only content.'); - - // Publish Page. - $url = $I->publishGutenbergPage($I); - - // Test Restrict Content functionality. - $I->testRestrictedContentByTagOnFrontendWhenRequireLoginEnabled( - $I, - urlOrPageID: $url, - emailAddress: $I->generateEmailAddress() - ); - } - - /** - * Test that restricting content by a Tag specified in the Page Settings works when: - * - the Plugin is set to Require Login, - * - the Plugin has its Recaptcha settings defined, - * - creating and viewing a new WordPress Page, - * - entering an email address displays the code verification screen - * - using a signed subscriber ID that has access to the Tag displays the content. - * - * @since 2.7.2 - * - * @param EndToEndTester $I Tester. - */ - public function testRestrictContentByTagWithRecaptchaAndRequireLoginEnabled(EndToEndTester $I) - { - // Setup Kit Plugin. - $I->setupKitPlugin( - $I, - [ - 'recaptcha_site_key' => $_ENV['CONVERTKIT_API_RECAPTCHA_SITE_KEY'], - 'recaptcha_secret_key' => $_ENV['CONVERTKIT_API_RECAPTCHA_SECRET_KEY'], - 'recaptcha_minimum_score' => '0.01', // Set a low score to ensure reCAPTCHA passes the subscriber. - ] - ); - $I->setupKitPluginResources($I); - - // Define reCAPTCHA settings. - $options = [ - 'settings' => [ - 'require_tag_login' => 'on', - ], - ]; - - // Setup Restrict Content functionality with Require Login and reCAPTCHA enabled. - $I->setupKitPluginRestrictContent($I, $options['settings']); - - // Add a Page using the Gutenberg editor. - $I->addGutenbergPage( - $I, - title: 'Kit: Page: Restrict Content: Tag: Recaptcha and Require Login' - ); - - // Configure metabox's Restrict Content setting = Tag name. - $I->configurePluginSidebarSettings( - $I, - form: 'None', - restrictContent: $_ENV['CONVERTKIT_API_TAG_NAME'] - ); - - // Add blocks. - $I->addGutenbergParagraphBlock($I, 'Visible content.'); - $I->addGutenbergBlock( - $I, - blockName: 'More', - blockProgrammaticName: 'more' - ); - $I->addGutenbergParagraphBlock($I, 'Member-only content.'); - - // Publish Page. - $url = $I->publishGutenbergPage($I); - - // Test Restrict Content functionality. - $I->testRestrictedContentByTagOnFrontendWhenRequireLoginEnabled( - $I, - urlOrPageID: $url, - emailAddress: $I->generateEmailAddress(), - options: $options, - testRecaptcha: true, - ); - } - - /** - * Test that restricting content by a Tag specified in the Page Settings works when: - * - the Plugin is set to Require Login, * - creating and viewing a new WordPress Page, * - entering an email address displays the code verification screen * - using a signed subscriber ID that has access to the Tag displays the content. @@ -216,15 +87,12 @@ public function testRestrictContentByTagUsingLoginModal(EndToEndTester $I) $I->setupKitPlugin($I); $I->setupKitPluginResources($I); - // Define Restrict Content settings. + // Setup Restrict Content functionality. Login is always required for + // tag restrictions, matching form / product restriction behaviour. $options = [ - 'settings' => [ - 'require_tag_login' => 'on', - ], + 'settings' => [], ]; - - // Setup Restrict Content functionality with Require Login enabled. - $I->setupKitPluginRestrictContent($I, $options['settings']); + $I->setupKitPluginRestrictContent($I); // Add a Page using the Gutenberg editor. $I->addGutenbergPage( diff --git a/tests/Support/Helper/KitRestrictContent.php b/tests/Support/Helper/KitRestrictContent.php index 7c325e08b..d3d1c0a56 100644 --- a/tests/Support/Helper/KitRestrictContent.php +++ b/tests/Support/Helper/KitRestrictContent.php @@ -68,7 +68,6 @@ public function getRestrictedContentDefaultSettings() 'subscribe_heading_tag' => 'Subscribe to keep reading', 'subscribe_text_tag' => 'This post is free to read but only available to subscribers. Join today to get access to all posts.', 'no_access_text_tag' => 'Your account does not have access to this content. Please use the form above to subscribe.', - 'require_tag_login' => '', // All. 'subscribe_button_label' => 'Subscribe', @@ -93,7 +92,6 @@ public function checkRestrictContentSettings($I, $settings) foreach ( $settings as $key => $value ) { switch ( $key ) { case 'permit_crawlers': - case 'require_tag_login': if ( $value ) { $I->seeCheckboxIsChecked('_wp_convertkit_settings_restrict_content[' . $key . ']'); } else { @@ -256,58 +254,6 @@ public function testRestrictedContentByProductOnFrontend($I, $urlOrPageID, $opti * Run frontend tests for restricted content by Kit Tag, to confirm that visible and member's content * is / is not displayed when logging in with valid and invalid subscriber email addresses. * - * @since 2.1.0 - * - * @param EndToEndTester $I Tester. - * @param string|int $urlOrPageID URL or ID of Restricted Content Page. - * @param string $emailAddress Email Address. - * @param bool|array $options { - * Optional. An array of settings. - * - * @type string $visible_content Content that should always be visible. - * @type string $member_content Content that should only be available to authenticated subscribers. - * @type array $settings Restrict content settings. If not defined, uses expected defaults. - * } - * @param bool $testRecaptcha Whether to test reCAPTCHA. - */ - public function testRestrictedContentByTagOnFrontend($I, $urlOrPageID, $emailAddress, $options = false, $testRecaptcha = false) - { - // Setup test. - $options = $this->setupRestrictContentTest($I, $options, $urlOrPageID); - - // Confirm Restrict Content CSS is output. - $I->seeInSource(' diff --git a/views/frontend/restrict-content/product.php b/views/frontend/restrict-content/product.php index 4cfde791d..15b496c63 100644 --- a/views/frontend/restrict-content/product.php +++ b/views/frontend/restrict-content/product.php @@ -20,7 +20,7 @@ echo wp_kses( $button, convertkit_kses_allowed_html() ); } - // Output a login link or form, if require login enabled. + // Output a login link or form. require 'login.php'; // Output notices. From 0cc34aea31496d4173119f08a610287ffba6f50f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 15 Jul 2026 14:43:39 +0800 Subject: [PATCH 5/5] Remove Store Subscriber Email as ID --- includes/class-convertkit-output.php | 59 ------- includes/class-convertkit-subscriber.php | 50 ------ resources/frontend/js/convertkit.js | 114 ------------ .../SubscriberEmailToIDOnFormSubmitCest.php | 162 ------------------ .../RESTAPIRestrictContentTest.php | 29 ---- tests/Integration/RESTAPITest.php | 116 ------------- 6 files changed, 530 deletions(-) delete mode 100644 tests/EndToEnd/general/other/SubscriberEmailToIDOnFormSubmitCest.php diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index 93a830528..e1e2f8bc3 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -67,7 +67,6 @@ class ConvertKit_Output { */ public function __construct() { - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); add_action( 'wp', array( $this, 'maybe_tag_subscriber' ) ); add_action( 'template_redirect', array( $this, 'output_form' ) ); add_action( 'template_redirect', array( $this, 'page_takeover' ) ); @@ -80,62 +79,6 @@ public function __construct() { } - /** - * Register REST API routes. - * - * @since 3.1.7 - */ - public function register_routes() { - - // Register route to store the Kit subscriber's email's ID in a cookie. - register_rest_route( - 'kit/v1', - '/subscriber/store-email-as-id-in-cookie', - array( - 'methods' => WP_REST_Server::CREATABLE, - 'args' => array( - // Email: Validate email is included in the request, a valid email address - // and sanitize the email address. - 'email' => array( - 'required' => true, - 'validate_callback' => function ( $param ) { - - return is_string( $param ) && is_email( $param ); - - }, - 'sanitize_callback' => 'sanitize_email', - ), - ), - 'callback' => function ( $request ) { - - // Get email address. - $email = $request->get_param( 'email' ); - - // Get subscriber ID. - $subscriber = new ConvertKit_Subscriber(); - $subscriber_id = $subscriber->validate_and_store_subscriber_email( $email ); - - // Bail if an error occurred i.e. API hasn't been configured. - if ( is_wp_error( $subscriber_id ) ) { - return rest_ensure_response( $subscriber_id ); - } - - // Return the subscriber ID. - return rest_ensure_response( - array( - 'id' => $subscriber_id, - ) - ); - - }, - - // No authentication required, as this is on the frontend site. - 'permission_callback' => '__return_true', - ) - ); - - } - /** * Tags the subscriber, if: * - a subscriber ID exists in the cookie or URL, @@ -820,9 +763,7 @@ public function enqueue_scripts() { 'convertkit-js', 'convertkit', array( - 'ajaxurl' => rest_url( 'kit/v1/subscriber/store-email-as-id-in-cookie' ), 'debug' => $settings->debug_enabled(), - 'nonce' => wp_create_nonce( 'wp_rest' ), 'subscriber_id' => $this->subscriber_id, ) ); diff --git a/includes/class-convertkit-subscriber.php b/includes/class-convertkit-subscriber.php index 5f851000f..34c47df99 100644 --- a/includes/class-convertkit-subscriber.php +++ b/includes/class-convertkit-subscriber.php @@ -50,56 +50,6 @@ public function get_subscriber_id() { } - /** - * Validates the given subscriber email by querying the API to confirm - * the subscriber exists before storing their ID in a cookie. - * - * @since 2.0.0 - * - * @param string $subscriber_email Possible Subscriber Email. - * @return WP_Error|int|string Error | Confirmed Subscriber ID or Signed Subscriber ID - */ - public function validate_and_store_subscriber_email( $subscriber_email ) { - - // Bail if the API hasn't been configured. - $settings = new ConvertKit_Settings(); - if ( ! $settings->has_access_and_refresh_token() ) { - return new WP_Error( - 'convertkit_subscriber_get_subscriber_id_from_request_error', - __( 'Access Token not configured in Plugin Settings.', 'convertkit' ) - ); - } - - // Initialize the API. - $api = new ConvertKit_API_V4( - CONVERTKIT_OAUTH_CLIENT_ID, - CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, - $settings->get_access_token(), - $settings->get_refresh_token(), - $settings->debug_enabled(), - 'subscriber' - ); - - // Get subscriber by email, to ensure they exist. - $subscriber_id = $api->get_subscriber_id( $subscriber_email ); - - // Bail if no subscriber exists with the given subscriber ID, or an error occurred. - if ( is_wp_error( $subscriber_id ) ) { - // Delete the cookie. - $this->forget(); - - // Return error. - return $subscriber_id; - } - - // Store the subscriber ID as a cookie. - $this->set( $subscriber_id ); - - // Return subscriber ID. - return $subscriber_id; - - } - /** * Gets the subscriber ID from the `ck_subscriber_id` cookie. * diff --git a/resources/frontend/js/convertkit.js b/resources/frontend/js/convertkit.js index 48db9ecfe..669791f57 100644 --- a/resources/frontend/js/convertkit.js +++ b/resources/frontend/js/convertkit.js @@ -6,60 +6,6 @@ * @author ConvertKit */ -/** - * Gets the subscriber ID for the given email address, storing - * it in the `ck_subscriber_id` cookie if it exists. - * - * Typically called when the user completes a ConvertKit Form - * that has either "Auto-confirm new subscribers" or - * "Send subscriber to thank you page" enabled (both scenarios - * include a ck_subscriber_id). - * - * @since 1.9.6 - * - * @param {string} emailAddress Email Address - */ -function convertStoreSubscriberEmailAsIDInCookie(emailAddress) { - if (convertkit.debug) { - console.log('convertStoreSubscriberEmailAsIDInCookie'); - console.log(emailAddress); - } - - fetch(convertkit.ajaxurl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'X-WP-Nonce': convertkit.nonce, - }, - body: new URLSearchParams({ - email: emailAddress, - }), - }) - .then(function (response) { - if (convertkit.debug) { - console.log(response); - } - - return response.json(); - }) - .then(function (result) { - if (convertkit.debug) { - console.log(result); - } - - // Emit custom event with subscriber ID. - convertKitEmitCustomEvent('convertkit_user_subscribed', { - id: result.id, - email: emailAddress, - }); - }) - .catch(function (error) { - if (convertkit.debug) { - console.error(error); - } - }); -} - /** * Remove the url subscriber_id url param * @@ -106,21 +52,6 @@ function convertKitRemoveSubscriberIDFromURL(url) { }); } -/** - * Utility function to pause for the given number of milliseconds - * - * @since 1.9.6 - * @param {number} milliseconds Number of milliseconds to pause for. - */ -function convertKitSleep(milliseconds) { - const start = new Date().getTime(); - for (let i = 0; i < 1e7; i++) { - if (new Date().getTime() - start > milliseconds) { - break; - } - } -} - /** * Emit a custom event with optional detail data. * @@ -171,51 +102,6 @@ if (typeof convertkit !== 'undefined') { // Removes `ck_subscriber_id` from the URI. convertKitRemoveSubscriberIDFromURL(window.location.href); - // Store subscriber ID as a cookie from the email address used when a ConvertKit Form is submitted. - document.addEventListener('click', function (e) { - // Check if the form submit button was clicked, or the span element was clicked and its parent is the form submit button. - if ( - !e.target.matches('.formkit-submit') && - (!e.target.parentElement || - !e.target.parentElement.matches('.formkit-submit')) - ) { - if (convertkit.debug) { - console.log('not a ck form'); - } - - return; - } - - // Get email address. - const emailAddress = document.querySelector( - 'input[name="email_address"]' - ).value; - - // If the email address is empty, don't attempt to get the subscriber ID by email. - if (!emailAddress.length) { - if (convertkit.debug) { - console.log('email empty'); - } - - return; - } - - // If the email address is invalid, don't attempt to get the subscriber ID by email. - const validator = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (!validator.test(emailAddress.toLowerCase())) { - if (convertkit.debug) { - console.log('email not an email address'); - } - - return; - } - - // Wait a moment before sending the AJAX request. - convertKitSleep(2000); - convertStoreSubscriberEmailAsIDInCookie(emailAddress); - }); - // Set a cookie if any scripts with data-kit-limit-per-session attribute exist. if ( document.querySelectorAll('script[data-kit-limit-per-session="1"]') diff --git a/tests/EndToEnd/general/other/SubscriberEmailToIDOnFormSubmitCest.php b/tests/EndToEnd/general/other/SubscriberEmailToIDOnFormSubmitCest.php deleted file mode 100644 index 74634add3..000000000 --- a/tests/EndToEnd/general/other/SubscriberEmailToIDOnFormSubmitCest.php +++ /dev/null @@ -1,162 +0,0 @@ -activateKitPlugin($I); - $I->setupKitPlugin($I); - $I->setupKitPluginResources($I); - } - - /** - * Test that no API call to the subscribers endpoint is made to fetch a subscriber ID - * by email address when a Kit Form is submitted with no email address. - * - * @since 1.9.6.7 - * - * @param EndToEndTester $I Tester. - */ - public function testWhenFormSubmittedWithNoEmailAddress(EndToEndTester $I) - { - // Create Page with Shortcode. - $I->havePageInDatabase( - [ - 'post_name' => 'kit-subscriber-email-to-id-no-email', - 'post_content' => 'No Email', - ] - ); - - // Load the Page on the frontend site. - $I->amOnPage('/kit-subscriber-email-to-id-no-email'); - $I->waitForElementVisible('body.page'); - - // Check that no PHP warnings or notices were output. - $I->checkNoWarningsAndNoticesOnScreen($I); - - // Submit Form. - $I->waitForElementVisible('.formkit-submit'); - $I->click('.formkit-submit'); - - // Wait for JS to complete. - $I->wait(2); - - // Check log does not contain get_subscriber_by_email() call with no email value. - $I->loadKitSettingsToolsScreen($I); - $I->dontSeeInSource('API: get_subscriber_by_email(): [ email: ]'); - } - - /** - * Test that no API call to the subscribers endpoint is made to fetch a subscriber ID - * by email address when a Kit Form is submitted with an invalid email address format. - * - * @since 1.9.6.7 - * - * @param EndToEndTester $I Tester. - */ - public function testWhenFormSubmittedWithInvalidEmailAddress(EndToEndTester $I) - { - // Create Page with Shortcode. - $I->havePageInDatabase( - [ - 'post_name' => 'kit-subscriber-email-to-id-invalid-email', - 'post_content' => 'Invalid Email', - ] - ); - - // Load the Page on the frontend site. - $I->amOnPage('/kit-subscriber-email-to-id-invalid-email'); - $I->waitForElementVisible('body.page'); - - // Check that no PHP warnings or notices were output. - $I->checkNoWarningsAndNoticesOnScreen($I); - - // Generate email address for this test. - $emailAddress = 'invalid-email'; - - // Submit Form. - $I->waitForElementVisible('input[name="email_address"]'); - $I->fillField('email_address', $emailAddress); - $I->click('.formkit-submit'); - - // Wait for JS to complete. - $I->wait(2); - - // Check log does not contain get_subscriber_by_email() call with no email value. - $I->loadKitSettingsToolsScreen($I); - $I->dontSeeInSource('API: get_subscriber_by_email(): [ email: ' . $emailAddress . ']'); - } - - /** - * Test that an API call to the subscribers endpoint is made to fetch a subscriber ID - * by email address when a Kit Form is submitted with a valid email address format. - * - * @since 1.9.6.7 - * - * @param EndToEndTester $I Tester. - */ - public function testWhenFormSubmittedWithValidEmailAddress(EndToEndTester $I) - { - // Create Page with Shortcode. - $I->havePageInDatabase( - [ - 'post_name' => 'kit-subscriber-email-to-id-valid-email', - 'post_content' => 'Valid Email', - ] - ); - - // Load the Page on the frontend site. - $I->amOnPage('/kit-subscriber-email-to-id-valid-email'); - $I->waitForElementVisible('body.page'); - - // Check that no PHP warnings or notices were output. - $I->checkNoWarningsAndNoticesOnScreen($I); - - // Generate email address for this test. - $emailAddress = $I->generateEmailAddress(); - - // Submit Form. - $I->waitForElementVisible('input[name="email_address"]'); - $I->fillField('email_address', $emailAddress); - $I->click('.formkit-submit'); - - // Wait for JS and AJAX request to complete. - $I->wait(5); - - // Check log contains get_subscriber_by_email() call with masked email value. - $I->loadKitSettingsToolsScreen($I); - $I->seeInSource('API: GET subscribers: {"email_address":"w********-2***'); - } - - /** - * Deactivate and reset Plugin(s) after each test, if the test passes. - * We don't use _after, as this would provide a screenshot of the Plugin - * deactivation and not the true test error. - * - * @since 1.9.6.7 - * - * @param EndToEndTester $I Tester. - */ - public function _passed(EndToEndTester $I) - { - $I->deactivateKitPlugin($I); - $I->resetKitPlugin($I); - } -} diff --git a/tests/Integration/RESTAPIRestrictContentTest.php b/tests/Integration/RESTAPIRestrictContentTest.php index 02cd7e5eb..b6873b6bc 100644 --- a/tests/Integration/RESTAPIRestrictContentTest.php +++ b/tests/Integration/RESTAPIRestrictContentTest.php @@ -277,33 +277,4 @@ public function testRestrictContentSubscriberAuthenticationProductInvalidEmail() $this->assertFalse( $data['success'] ); $this->assertArrayHasKey( 'data', $data ); } - - /** - * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API route stores - * the subscriber ID in a cookie when a valid email address is given. - * - * @since 3.1.7 - */ - public function testStoreEmailAsIDInCookie() - { - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); - $request->set_header( 'Content-Type', 'application/json' ); - $request->set_body_params( - [ - 'email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], - ], - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertEquals( (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], (int) $data['id'] ); - } } diff --git a/tests/Integration/RESTAPITest.php b/tests/Integration/RESTAPITest.php index 22388667d..a9f59b534 100644 --- a/tests/Integration/RESTAPITest.php +++ b/tests/Integration/RESTAPITest.php @@ -311,122 +311,6 @@ public function testRefreshResourcesRestrictContent() $this->assertArrayHasKeys( $data['products'][0], [ 'id', 'name', 'url', 'published' ] ); } - /** - * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API route stores - * the subscriber ID in a cookie when a valid email address is given. - * - * @since 3.1.7 - */ - public function testStoreEmailAsIDInCookie() - { - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); - $request->set_header( 'Content-Type', 'application/json' ); - $request->set_body_params( - [ - 'email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], - ], - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertEquals( (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], (int) $data['id'] ); - } - - /** - * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API returns - * no subscriber ID when a non-subscriber email address is given. - * - * @since 3.1.7 - */ - public function testStoreEmailAsIDInCookieWithNonSubscriberEmail() - { - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); - $request->set_header( 'Content-Type', 'application/json' ); - $request->set_body_params( - [ - 'email' => 'fail@kit.com', - ], - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response is successful. - $this->assertSame( 200, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertIsArray( $data ); - $this->assertEquals( 0, (int) $data['id'] ); - } - - /** - * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API returns - * an error when no email address is given. - * - * @since 3.1.7 - */ - public function testStoreEmailAsIDInCookieWithNoEmail() - { - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); - $request->set_header( 'Content-Type', 'application/json' ); - $request->set_body_params( - [ - 'email' => '', - ], - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response failed. - $this->assertSame( 400, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertEquals( 'rest_invalid_param', $data['code'] ); - $this->assertEquals( 'Invalid parameter(s): email', $data['message'] ); - } - - /** - * Test that the /wp-json/kit/v1/subscriber/store-email-as-id-in-cookie REST API returns - * an error when an invalid email address is given. - * - * @since 3.1.7 - */ - public function testStoreEmailAsIDInCookieWithInvalidEmail() - { - // Build request. - $request = new \WP_REST_Request( 'POST', '/kit/v1/subscriber/store-email-as-id-in-cookie' ); - $request->set_header( 'Content-Type', 'application/json' ); - $request->set_body_params( - [ - 'email' => 'not-an-email', - ], - ); - - // Send request. - $response = rest_get_server()->dispatch( $request ); - - // Assert response failed. - $this->assertSame( 400, $response->get_status() ); - - // Assert response data has the expected keys and data. - $data = $response->get_data(); - $this->assertEquals( 'rest_invalid_param', $data['code'] ); - $this->assertEquals( 'Invalid parameter(s): email', $data['message'] ); - } - /** * Act as an editor user. *