diff --git a/includes/class-convertkit-ajax.php b/includes/class-convertkit-ajax.php index 3c93e3f89..86c342b54 100644 --- a/includes/class-convertkit-ajax.php +++ b/includes/class-convertkit-ajax.php @@ -23,12 +23,6 @@ public function __construct() { add_action( 'wp_ajax_nopriv_convertkit_store_subscriber_email_as_id_in_cookie', array( $this, 'store_subscriber_email_as_id_in_cookie' ) ); add_action( 'wp_ajax_convertkit_store_subscriber_email_as_id_in_cookie', array( $this, 'store_subscriber_email_as_id_in_cookie' ) ); - add_action( 'wp_ajax_nopriv_convertkit_subscriber_authentication_send_code', array( $this, 'subscriber_authentication_send_code' ) ); - add_action( 'wp_ajax_convertkit_subscriber_authentication_send_code', array( $this, 'subscriber_authentication_send_code' ) ); - - add_action( 'wp_ajax_nopriv_convertkit_subscriber_verification', array( $this, 'subscriber_verification' ) ); - add_action( 'wp_ajax_convertkit_subscriber_verification', array( $this, 'subscriber_verification' ) ); - } /** @@ -78,79 +72,4 @@ public function store_subscriber_email_as_id_in_cookie() { } - /** - * Calls the API to send the subscriber a magic link by email containing a code when - * the modal version of Restrict Content is used, and the user has submitted their email address. - * - * Returns a view of either: - * - an error message and email input i.e. the user entered an invalid email address, - * - the code input, which is then displayed in the modal for the user to enter the code sent by email. - * - * See maybe_run_subscriber_verification() for logic once they enter the code on screen. - * - * @since 2.3.8 - */ - public function subscriber_authentication_send_code() { - - // Load Restrict Content class. - $output_restrict_content = WP_ConvertKit()->get_class( 'output_restrict_content' ); - - // Run subscriber authentication. - $output_restrict_content->maybe_run_subscriber_authentication(); - - // Fetch Post ID, Resource Type and Resource ID for the view. - $post_id = $output_restrict_content->post_id; - $resource_type = $output_restrict_content->resource_type; - $resource_id = $output_restrict_content->resource_id; - - // If an error occured, build the email form view with the error message. - if ( is_wp_error( $output_restrict_content->error ) ) { - ob_start(); - include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-email.php'; - $output = trim( ob_get_clean() ); - wp_send_json_success( $output ); - } - - // Build authentication code view to return for output. - ob_start(); - include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-code.php'; - $output = trim( ob_get_clean() ); - wp_send_json_success( $output ); - - } - - /** - * Calls the API to verify the token and entered subscriber code, which tells us that the email - * address supplied truly belongs to the user, and that we can safely trust their subscriber ID - * to be valid. - * - * @since 2.3.8 - */ - public function subscriber_verification() { - - // Load Restrict Content class. - $output_restrict_content = WP_ConvertKit()->get_class( 'output_restrict_content' ); - - // Run subscriber authentication. - $output_restrict_content->maybe_run_subscriber_verification(); - - // Fetch Post ID, Resource Type and Resource ID for the view. - $post_id = $output_restrict_content->post_id; - $resource_type = $output_restrict_content->resource_type; - $resource_id = $output_restrict_content->resource_id; - - // If an error occured, build the code form view with the error message. - if ( is_wp_error( $output_restrict_content->error ) ) { - ob_start(); - include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-code.php'; - $output = trim( ob_get_clean() ); - wp_send_json_error( $output ); - } - - // Return success with the URL to the Post, including the `ck-cache-bust` parameter. - // JS will load the given URL to show the restricted content. - wp_send_json_success( $output_restrict_content->get_url( true ) ); - - } - } diff --git a/includes/class-convertkit-output-restrict-content.php b/includes/class-convertkit-output-restrict-content.php index 43a122c68..d07fcfdc9 100644 --- a/includes/class-convertkit-output-restrict-content.php +++ b/includes/class-convertkit-output-restrict-content.php @@ -106,17 +106,8 @@ class ConvertKit_Output_Restrict_Content { */ public function __construct() { - // Initialize classes that will be used. - $this->settings = new ConvertKit_Settings(); - $this->restrict_content_settings = new ConvertKit_Settings_Restrict_Content(); - - // Don't register any hooks if this is an AJAX request, otherwise - // maybe_run_subscriber_authentication() and maybe_run_subscriber_verification() will run - // twice in an AJAX request (once here, and once when called by the ConvertKit_AJAX class). - if ( wp_doing_ajax() ) { - return; - } - + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + add_action( 'init', array( $this, 'initialize_classes' ), 2 ); add_action( 'init', array( $this, 'maybe_run_subscriber_authentication' ), 3 ); add_action( 'wp', array( $this, 'maybe_run_subscriber_verification' ), 4 ); add_action( 'wp', array( $this, 'register_content_filter' ), 5 ); @@ -128,7 +119,149 @@ public function __construct() { } /** - * Checks if the request is a Restrict Content request with an email address. + * Register REST API routes. + * + * @since 3.1.0 + */ + public function register_routes() { + + // Register route to run subscriber authentication. + register_rest_route( + 'kit/v1', + '/restrict-content/subscriber-authentication', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => function ( $request ) { + + // Initialize classes that will be used. + $output_restrict_content = WP_ConvertKit()->get_class( 'output_restrict_content' ); + $output_restrict_content->initialize_classes(); + + // Fetch Post ID, Resource Type and Resource ID for the view. + $email = $request->get_param( 'convertkit_email' ); + $post_id = $request->get_param( 'convertkit_post_id' ); + $resource_type = $request->get_param( 'convertkit_resource_type' ); + $resource_id = $request->get_param( 'convertkit_resource_id' ); + + // Run subscriber authentication. + $result = $output_restrict_content->subscriber_authentication_send_code( + $email, + $post_id + ); + + // If an error occured, build the email form view with the error message. + if ( is_wp_error( $result ) ) { + // Set error to display on screen. + $output_restrict_content->error = $result; + + // Build email form view to return for output with error message. + ob_start(); + include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-email.php'; + $output = trim( ob_get_clean() ); + return rest_ensure_response( + array( + 'success' => false, + 'data' => $output, + ) + ); + } + + // Set token and Post ID for authentication code view. + $output_restrict_content->token = $result; + $output_restrict_content->post_id = $post_id; + + // Build authentication code view to return for output. + ob_start(); + include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-code.php'; + $output = trim( ob_get_clean() ); + return rest_ensure_response( + array( + 'success' => true, + 'data' => $output, + ) + ); + }, + 'permission_callback' => '__return_true', + ) + ); + + // Register route to run subscriber verification. + register_rest_route( + 'kit/v1', + '/restrict-content/subscriber-verification', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => function ( $request ) { + + // Initialize classes that will be used. + $output_restrict_content = WP_ConvertKit()->get_class( 'output_restrict_content' ); + $output_restrict_content->initialize_classes(); + + // Fetch Post ID, Resource Type and Resource ID for the view. + $post_id = $request->get_param( 'convertkit_post_id' ); + $token = $request->get_param( 'token' ); + $subscriber_code = $request->get_param( 'subscriber_code' ); + + // Run subscriber authentication. + $result = $output_restrict_content->subscriber_authentication_verify( $post_id, $token, $subscriber_code ); + + // If an error occured, build the code form view with the error message. + if ( is_wp_error( $result ) ) { + // Set error to display on screen. + $output_restrict_content->error = $result; + + // Set token and post ID for authentication code view. + $output_restrict_content->token = $token; + $output_restrict_content->post_id = $post_id; + + // Build code form view to return for output with error message. + ob_start(); + include CONVERTKIT_PLUGIN_PATH . '/views/frontend/restrict-content/login-modal-content-code.php'; + $output = trim( ob_get_clean() ); + return rest_ensure_response( + array( + 'success' => false, + 'data' => $output, + ) + ); + } + + // Return success with the URL to the Post, including the `ck-cache-bust` parameter. + return rest_ensure_response( + array( + 'success' => true, + 'url' => $output_restrict_content->get_url( $post_id, true ), + ) + ); + }, + 'permission_callback' => '__return_true', + ) + ); + } + + /** + * Initialize classes that will be used. + * + * @since 3.1.0 + */ + public function initialize_classes() { + + $this->settings = new ConvertKit_Settings(); + $this->restrict_content_settings = new ConvertKit_Settings_Restrict_Content(); + $this->api = new ConvertKit_API_V4( + CONVERTKIT_OAUTH_CLIENT_ID, + CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, + $this->settings->get_access_token(), + $this->settings->get_refresh_token(), + $this->settings->debug_enabled(), + 'restrict_content' + ); + + } + + /** + * 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() @@ -138,17 +271,17 @@ public function __construct() { */ public function maybe_run_subscriber_authentication() { - // Bail if no nonce was specified. + // Bail if no nonce was specified via form submission. if ( ! array_key_exists( '_wpnonce', $_REQUEST ) ) { return; } - // Bail if the nonce failed validation. + // Bail if the request is a form submission and the nonce failed validation. if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'convertkit_restrict_content_login' ) ) { return; } - // Bail if the expected email, resource ID or Post ID are missing. + // Bail if the expected email, resource type, resource ID or Post ID are missing from the request. if ( ! array_key_exists( 'convertkit_email', $_REQUEST ) ) { return; } @@ -167,105 +300,38 @@ public function maybe_run_subscriber_authentication() { return; } - // Initialize the API. - $this->api = new ConvertKit_API_V4( - CONVERTKIT_OAUTH_CLIENT_ID, - CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, - $this->settings->get_access_token(), - $this->settings->get_refresh_token(), - $this->settings->debug_enabled(), - 'restrict_content' - ); - // Sanitize inputs. $email = sanitize_text_field( wp_unslash( $_REQUEST['convertkit_email'] ) ); $this->resource_type = sanitize_text_field( wp_unslash( $_REQUEST['convertkit_resource_type'] ) ); $this->resource_id = absint( $_REQUEST['convertkit_resource_id'] ); $this->post_id = absint( $_REQUEST['convertkit_post_id'] ); - // Run subscriber authentication / subscription depending on the resource type. - switch ( $this->resource_type ) { - case 'product': - case 'form': - // Send email to subscriber with a link to authenticate they have access to the email address submitted. - $result = $this->api->subscriber_authentication_send_code( - $email, - $this->get_url() - ); - - // Bail if an error occured. - if ( is_wp_error( $result ) ) { - $this->error = $result; - return; - } - - // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email. - $subscriber = new ConvertKit_Subscriber(); - $subscriber->forget(); - - // Store the token so it's included in the subscriber code form. - $this->token = $result; - break; - - case 'tag': - // If require login is enabled, show the login screen. - if ( $this->restrict_content_settings->require_tag_login() ) { - // Tag the subscriber, unless this is an AJAX request. - if ( ! wp_doing_ajax() ) { - $result = $this->api->tag_subscribe( $this->resource_id, $email ); - - // Bail if an error occured. - if ( is_wp_error( $result ) ) { - $this->error = $result; - return; - } - } - - // Send email to subscriber with a link to authenticate they have access to the email address submitted. - $result = $this->api->subscriber_authentication_send_code( - $email, - $this->get_url() - ); - - // Bail if an error occured. - if ( is_wp_error( $result ) ) { - $this->error = $result; - return; - } - - // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email. - $subscriber = new ConvertKit_Subscriber(); - $subscriber->forget(); - - // Store the token so it's included in the subscriber code form. - $this->token = $result; - break; - } - - // If here, require login is disabled. - // Check reCAPTCHA, tag subscriber and assign subscriber ID integer to cookie - // without email link. - $recaptcha = new ConvertKit_Recaptcha(); - $recaptcha_response = $recaptcha->verify_recaptcha( - ( isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : '' ), - 'convertkit_restrict_content_tag' - ); + // If Restrict Content is by tag, tag the subscriber. + if ( $this->resource_type === 'tag' ) { + // Check reCAPTCHA. + $recaptcha = new ConvertKit_Recaptcha(); + $recaptcha_response = $recaptcha->verify_recaptcha( + ( isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : '' ), + 'convertkit_restrict_content_tag' + ); - // Bail if reCAPTCHA failed. - if ( is_wp_error( $recaptcha_response ) ) { - $this->error = $recaptcha_response; - return; - } + // Bail if reCAPTCHA failed. + if ( is_wp_error( $recaptcha_response ) ) { + $this->error = $recaptcha_response; + return; + } - // Tag the subscriber. - $result = $this->api->tag_subscribe( $this->resource_id, $email ); + // Tag subscriber. + $result = $this->api->tag_subscribe( $this->resource_id, $email ); - // Bail if an error occured. - if ( is_wp_error( $result ) ) { - $this->error = $result; - return; - } + // Bail if an error occured. + if ( is_wp_error( $result ) ) { + $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(); @@ -276,19 +342,32 @@ public function maybe_run_subscriber_authentication() { // Store subscriber ID in cookie. $this->store_subscriber_id_in_cookie( $subscriber_id ); - // If this isn't an AJAX request, redirect now to reload the Post. - if ( ! wp_doing_ajax() ) { - $this->redirect(); - } - break; + // 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 ); + // Bail if an error occured. + if ( is_wp_error( $result ) ) { + $this->error = $result; + return; } + // Store the token so it's included in the subscriber code form. + $this->token = $result; + } /** - * Checks if the request contains a token and subscriber_code i.e. the subscriber clicked - * the link in the email sent by the maybe_run_subscriber_authentication() function above. + * If the user isn't using JavaScript, or the Plugin's Disable JS is enabled, checks if the request contains a token and subscriber_code, + * which happens when the subscriber either: + * - clicked the link in the email sent by run_subscriber_authentication(), or + * - entered the code from the email on the screen * * This calls the API to verify the token and subscriber code, which tells us that the email * address supplied truly belongs to the user, and that we can safely trust their subscriber ID @@ -308,7 +387,7 @@ public function maybe_run_subscriber_verification() { // If a nonce was specified, validate it now. // It won't be provided if clicking the link in the magic link email. - if ( array_key_exists( '_wpnonce', $_REQUEST ) ) { + if ( array_key_exists( '_wpnonce', $_REQUEST ) && ! is_null( $_REQUEST['_wpnonce'] ) ) { if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'convertkit_restrict_content_subscriber_code' ) ) { return; } @@ -331,35 +410,80 @@ public function maybe_run_subscriber_verification() { $this->post_id = get_the_ID(); } - // Initialize the API. - $this->api = new ConvertKit_API_V4( - CONVERTKIT_OAUTH_CLIENT_ID, - CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, - $this->settings->get_access_token(), - $this->settings->get_refresh_token(), - $this->settings->debug_enabled(), - 'restrict_content' + // Run subscriber verification. + $subscriber_id = $this->subscriber_authentication_verify( $this->post_id, sanitize_text_field( wp_unslash( $_REQUEST['token'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['subscriber_code'] ) ) ); + + // Bail if an error occured. + if ( is_wp_error( $subscriber_id ) ) { + $this->error = $subscriber_id; + return; + } + + // Redirect now to reload the Post. + $this->redirect( $this->post_id ); + + } + + /** + * Sends an email to the subscriber with a code and link to authenticate they have access to the email address submitted. + * + * @since 3.1.0 + * + * @param string $email Email address. + * @param int $post_id Post ID. + * + * @return WP_Error|string Error or Token. + */ + public function subscriber_authentication_send_code( $email, $post_id ) { + + // Send email to subscriber with a link to authenticate they have access to the email address submitted. + $token = $this->api->subscriber_authentication_send_code( + $email, + $this->get_url( $post_id ) ); + // Bail if an error occured. + if ( is_wp_error( $token ) ) { + return $token; + } + + // Clear any existing subscriber ID cookie, as the authentication flow has started by sending the email. + $subscriber = new ConvertKit_Subscriber(); + $subscriber->forget(); + + // Return the token. + return $token; + + } + + /** + * Verifies the token and subscriber code, which tells us that the email + * address supplied truly belongs to the user, and that we can safely + * trust their subscriber ID to be valid. + * + * @since 3.1.0 + * + * @param int $post_id Post ID. + * @param string $token Token. + * @param string $subscriber_code Subscriber code. + * + * @return WP_Error|string Error or Signed Subscriber ID. + */ + public function subscriber_authentication_verify( $post_id, $token, $subscriber_code ) { + // Verify the token and subscriber code. - $subscriber_id = $this->api->subscriber_authentication_verify( - sanitize_text_field( wp_unslash( $_REQUEST['token'] ) ), - sanitize_text_field( wp_unslash( $_REQUEST['subscriber_code'] ) ) - ); + $subscriber_id = $this->api->subscriber_authentication_verify( $token, $subscriber_code ); // Bail if an error occured. if ( is_wp_error( $subscriber_id ) ) { - $this->error = $subscriber_id; - return; + return $subscriber_id; } // Store subscriber ID in cookie. $this->store_subscriber_id_in_cookie( $subscriber_id ); - // If this isn't an AJAX request, redirect now to reload the Post. - if ( ! wp_doing_ajax() ) { - $this->redirect(); - } + // Return signed subscriber ID. + return $subscriber_id; } @@ -611,14 +735,16 @@ private function store_subscriber_id_in_cookie( $subscriber_id ) { * a ck-cache-bust query parameter to beat caching plugins. * * @since 2.3.7 + * + * @param int $post_id Post ID. */ - private function redirect() { + private function redirect( $post_id ) { // Redirect to the Post, appending a query parameter to the URL to prevent caching plugins and // aggressive cache hosting configurations from serving a cached page, which would // result in maybe_restrict_content() not showing an error message or permitting // access to the content. - wp_safe_redirect( $this->get_url( true ) ); + wp_safe_redirect( $this->get_url( $post_id, true ) ); exit; } @@ -628,13 +754,14 @@ private function redirect() { * * @since 2.1.0 * + * @param int $post_id Post ID. * @param bool $cache_bust Include `ck-cache-bust` parameter in URL. - * @return string URL. + * @return string URL. */ - public function get_url( $cache_bust = false ) { + public function get_url( $post_id, $cache_bust = false ) { // Get URL of Post. - $url = get_permalink( $this->post_id ); + $url = get_permalink( $post_id ); // If no cache busting required, return the URL now. if ( ! $cache_bust ) { @@ -866,16 +993,6 @@ private function resource_exists() { */ private function subscriber_has_access( $subscriber_id ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter - // Initialize the API. - $this->api = new ConvertKit_API_V4( - CONVERTKIT_OAUTH_CLIENT_ID, - CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, - $this->settings->get_access_token(), - $this->settings->get_refresh_token(), - $this->settings->debug_enabled(), - 'restrict_content' - ); - // 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. @@ -1214,8 +1331,10 @@ private function get_call_to_action( $post_id ) { // phpcs:ignore Generic.CodeAn 'convertkit-restrict-content', 'convertkit_restrict_content', array( - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'debug' => $this->settings->debug_enabled(), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'subscriber_authentication_url' => rest_url( 'kit/v1/restrict-content/subscriber-authentication' ), + 'subscriber_verification_url' => rest_url( 'kit/v1/restrict-content/subscriber-verification' ), + 'debug' => $this->settings->debug_enabled(), ) ); diff --git a/resources/frontend/js/restrict-content.js b/resources/frontend/js/restrict-content.js index 5063629f3..48dd67290 100644 --- a/resources/frontend/js/restrict-content.js +++ b/resources/frontend/js/restrict-content.js @@ -77,7 +77,7 @@ function convertKitRestrictContentFormSubmit(e) { if (isCodeSubmission) { // Code submission. convertKitRestrictContentSubscriberVerification( - e.target.querySelector('input[name="_wpnonce"]').value, + convertkit_restrict_content.nonce, e.target.querySelector('input[name="subscriber_code"]').value, e.target.querySelector('input[name="token"]').value, e.target.querySelector('input[name="convertkit_post_id"]').value @@ -88,7 +88,7 @@ function convertKitRestrictContentFormSubmit(e) { // Email submission. convertKitRestrictContentSubscriberAuthenticationSendCode( - e.target.querySelector('input[name="_wpnonce"]').value, + convertkit_restrict_content.nonce, e.target.querySelector('input[name="convertkit_email"]').value, e.target.querySelector('input[name="convertkit_resource_type"]').value, e.target.querySelector('input[name="convertkit_resource_id"]').value, @@ -127,17 +127,17 @@ function convertKitRestrictContentCloseModal() { } /** - * Submits the given email address to maybe_run_subscriber_authentication(), which - * will return either: + * Submits the given email address to the WP REST API kit/v1/restrict-content/subscriber-authentication + * endpoint, which will return either: * - the email form view, with an error message e.g. invalid email, * - the code form view, where the user can enter the OTP. * * @since 2.3.8 * * @param {string} nonce WordPress nonce. - * @param {string} email Email address. resource_type Resource Type (tag|product). - * @param {string} resource_type Resource Type (tag|product). - * @param {string} resource_id Resource ID (ConvertKit Tag or Product ID). + * @param {string} email Email address. + * @param {string} resource_type Resource Type (form|tag|product). + * @param {string} resource_id Resource ID (Kit Form,Tag or Product ID). * @param {number} post_id WordPress Post ID being viewed / accessed. */ function convertKitRestrictContentSubscriberAuthenticationSendCode( @@ -147,14 +147,13 @@ function convertKitRestrictContentSubscriberAuthenticationSendCode( resource_id, post_id ) { - fetch(convertkit_restrict_content.ajaxurl, { + fetch(convertkit_restrict_content.subscriber_authentication_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', + 'X-WP-Nonce': nonce, }, body: new URLSearchParams({ - action: 'convertkit_subscriber_authentication_send_code', - _wpnonce: nonce, convertkit_email: email, convertkit_resource_type: resource_type, convertkit_resource_id: resource_id, @@ -173,10 +172,19 @@ function convertKitRestrictContentSubscriberAuthenticationSendCode( console.log(result); } - // Output response, which will be a form with/without an error message. - document.querySelector( - '#convertkit-restrict-content-modal-content' - ).innerHTML = result.data; + // Output error message if the response contains a code. + if (typeof result.code !== 'undefined') { + document.querySelector( + '#convertkit-restrict-content-modal-content' + ).innerHTML = result.message; + } else { + // Output response, which will be either: + // - the email form view, with an error message e.g. invalid email, + // - the code form view, where the user can enter the OTP. + document.querySelector( + '#convertkit-restrict-content-modal-content' + ).innerHTML = result.data; + } // Hide loading overlay. document.querySelector( @@ -194,8 +202,8 @@ function convertKitRestrictContentSubscriberAuthenticationSendCode( } /** - * Submits the given email address to maybe_run_subscriber_verification(), which - * will return either: + * Submits the given email address to the WP REST API kit/v1/restrict-content/subscriber-verification + * endpoint, which will return either: * - the code form view, with an error message e.g. invalid code entered, * - the Post's URL, with a `ck-cache-bust` parameter appended, which can then be loaded to show the content. * @@ -212,14 +220,13 @@ function convertKitRestrictContentSubscriberVerification( token, post_id ) { - fetch(convertkit_restrict_content.ajaxurl, { + fetch(convertkit_restrict_content.subscriber_verification_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', + 'X-WP-Nonce': nonce, }, body: new URLSearchParams({ - action: 'convertkit_subscriber_verification', - _wpnonce: nonce, subscriber_code, token, convertkit_post_id: post_id, @@ -254,7 +261,7 @@ function convertKitRestrictContentSubscriberVerification( } // Code entered is valid; load the URL in the response data. - window.location = result.data; + window.location = result.url; }) .catch(function (error) { if (convertkit_restrict_content.debug) { diff --git a/tests/EndToEnd/landing-pages/PageLandingPageCest.php b/tests/EndToEnd/landing-pages/PageLandingPageCest.php index 466bca0b3..7d31404ad 100644 --- a/tests/EndToEnd/landing-pages/PageLandingPageCest.php +++ b/tests/EndToEnd/landing-pages/PageLandingPageCest.php @@ -542,7 +542,7 @@ public function testAddNewPageUsingDefinedLandingPageWithWPRocket(EndToEndTester * Test that the Landing Page specified in the Page Settings works when * creating and viewing a new WordPress Page, with the Rocket LazyLoad * Plugin active (https://wordpress.org/plugins/rocket-lazy-load/). - * + * * This differs from the WP-Rocket Plugin. * * @since 3.1.0 @@ -558,7 +558,7 @@ public function testAddNewPageUsingDefinedLandingPageWithRocketLazyLoadPlugin(En $I->haveOptionInDatabase( 'rocket_lazyload_options', [ - 'images' => 1, + 'images' => 1, 'iframes' => 1, ] ); diff --git a/tests/Integration/RESTAPITest.php b/tests/Integration/RESTAPITest.php index 6d30cbc72..46510f122 100644 --- a/tests/Integration/RESTAPITest.php +++ b/tests/Integration/RESTAPITest.php @@ -301,6 +301,222 @@ public function testRefreshResourcesRestrictContent() $this->assertArrayHasKeys( $data['products'][0], [ 'id', 'name', 'url', 'published' ] ); } + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Form ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationForm() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'form', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Form ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationFormInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'form', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Tag ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationTag() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'tag', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Tag ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationTagInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'tag', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_TAG_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Product ID and subscriber + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationProduct() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'], + 'convertkit_resource_type' => 'product', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertTrue( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + + /** + * Test that the /wp-json/kit/v1/restrict-content/subscriber-authentication REST API route when + * requesting the subscriber authentication email to be sent for a given Product ID and an invalid subscriber email is given + * + * @since 3.1.0 + */ + public function testRestrictContentSubscriberAuthenticationProductInvalidEmail() + { + // Create a Post. + $post_id = static::factory()->post->create( [ 'post_title' => 'Test Post' ] ); + + // Build request. + $request = new \WP_REST_Request( 'POST', '/kit/v1/restrict-content/subscriber-authentication' ); + $request->set_header( 'Content-Type', 'application/x-www-form-urlencoded' ); + $request->set_body_params( + [ + 'convertkit_email' => 'fail@kit.com', + 'convertkit_resource_type' => 'product', + 'convertkit_resource_id' => $_ENV['CONVERTKIT_API_PRODUCT_ID'], + 'convertkit_post_id' => $post_id, + ] + ); + + // 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->assertFalse( $data['success'] ); + $this->assertArrayHasKey( 'data', $data ); + } + /** * Act as an editor user. *