From edda4703318e90829de18304c19221475200c76b Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:49:23 +0530 Subject: [PATCH 01/24] Add `isAccessibleForFree` property in Yoast SEO for Article schema --- includes/class-initializer.php | 1 + includes/class-yoastseo.php | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 includes/class-yoastseo.php diff --git a/includes/class-initializer.php b/includes/class-initializer.php index ac88639..586442e 100644 --- a/includes/class-initializer.php +++ b/includes/class-initializer.php @@ -17,6 +17,7 @@ class Initializer { */ public static function init() { WooCommerce::init(); + YoastSEO::init(); } } diff --git a/includes/class-yoastseo.php b/includes/class-yoastseo.php new file mode 100644 index 0000000..20d2193 --- /dev/null +++ b/includes/class-yoastseo.php @@ -0,0 +1,46 @@ + Date: Wed, 29 Mar 2023 18:33:16 +0530 Subject: [PATCH 02/24] Fix `isAccessibleForFree` assignment --- includes/class-yoastseo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-yoastseo.php b/includes/class-yoastseo.php index 20d2193..ec8c009 100644 --- a/includes/class-yoastseo.php +++ b/includes/class-yoastseo.php @@ -37,7 +37,7 @@ public static function update_wpseo_schema_article( $data ) { // if WooCommerce Membership plugin is installed and active. if ( function_exists( 'wc_memberships_is_post_content_restricted' ) ) { // Add 'isAccessibleForFree' schema for compatibility with Google Extended Access. - $data['isAccessibleForFree'] = wc_memberships_is_post_content_restricted(); + $data['isAccessibleForFree'] = ! wc_memberships_is_post_content_restricted(); } return $data; From ac6b8cce41202b1d3d82690274a407d6978846ac Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Fri, 14 Apr 2023 19:21:17 +0530 Subject: [PATCH 03/24] =?UTF-8?q?=E2=9C=A8=20Add=20Extended=20Access=20fun?= =?UTF-8?q?ctionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/newspack-swg.js | 166 ++++++++++++++++ .../class-extendedaccess-rest-endpoint.php | 186 ++++++++++++++++++ includes/class-google-extendedaccess.php | 121 ++++++++++++ includes/class-initializer.php | 12 +- includes/class-yoastseo.php | 46 ----- newspack-extended-access.php | 2 +- 6 files changed, 484 insertions(+), 49 deletions(-) create mode 100644 assets/newspack-swg.js create mode 100644 includes/class-extendedaccess-rest-endpoint.php create mode 100644 includes/class-google-extendedaccess.php delete mode 100644 includes/class-yoastseo.php diff --git a/assets/newspack-swg.js b/assets/newspack-swg.js new file mode 100644 index 0000000..8a93b31 --- /dev/null +++ b/assets/newspack-swg.js @@ -0,0 +1,166 @@ +/** + * Newspack SwG Library. + * + * Initializes GAA and defines required callbacks to + * register/login to site via SwG, check post status + * and unlock article. + * + * @link https://www.newspack.com + * @file This files defines SwG required methods and callback for Newspack specific functionality. + * @author Newspack + * @since 0.21 + */ + +/** + * Parses JWT token and converts into equivalent JSON object. + * + * @param {string} token JWT Token to be parse. + * @returns {Object} Parsed JWT as JSON Object. + */ +function parseJwt(token) { + var base64Url = token.split('.')[1]; + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + + return JSON.parse(jsonPayload); +} + +/** + * Initializes GaaMetering for SwG. + */ +function InitGaaMetering() { + + /** + * Variables + */ + const allowedReferrers = AuthSettings.allowedReferrers ; // ['extended-access-dev.newspackstaging.com', 'newspackstaging.com'] + + /** + * Login Existing User Promise callback handler. + */ + handleLoginPromise = new Promise(() => { + GaaMetering.getLoginPromise().then(() => { + console.log('handleLoginPromise', 'Redirecting to login URL'); + // Capture full URL, including URL parameters, to redirect the user to after login + const redirectUri = encodeURIComponent(window.location.href); + // Redirect to a login page for existing users to login. + window.location = `${window.location.protocol}//${window.location.hostname}/my-account?redirect_to=${redirectUri}`; + }); + }); + + /** + * Register New User Promise callback handler. + */ + registerUserPromise = new Promise((resolve) => { + // Get the information for the user who has just registered. + GaaMetering.getGaaUserPromise().then((gaaUser) => { + // Send that information to your Registration endpoint to register the user and + // return the userState for the newly registered user. + + const gaaUserDecoded = parseJwt(gaaUser.credential); + + fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/google`, + { + method: 'POST', + headers: { + 'Content-type': 'text/plain', + 'X-WP-Nonce': AuthSettings.nonce + }, + body: gaaUser.credential + }) + .then(response => response.json()) + .then(userState => { + // Refresh page only when it is not already unlocked + if (window.localStorage) { + if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { + localStorage.removeItem('unlocked'); + } + } + resolve(userState); + }); + }); + }); + + /** + * Check whether publisher has provided access to the User or not. + */ + publisherEntitlementPromise = new Promise((resolve) => { + /* Do not grant user, show Google Intervention Dialog. */ + resolve({ granted: false }); + + /* Grant user, do not show Google Intervention Dialog. */ + // resolve({granted: true, grantReason: 'METERING'}); + + /* Do not grant user, show Google Extended Access Dialog. */ + // resolve({ id: 'MTAzMjk1OTEyMjMwNDQ4NjQxMzQz', registrationTimestamp: 1680773672, granted: false }); + }); + + /** + * Check whether publisher has provided access to the User or not. + */ + getUserState = new Promise((resolve) => { + + fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/status`, + { + method: 'GET', + headers: { + 'Content-type': 'text/plain', + 'X-WP-Nonce': AuthSettings.nonce + }, + }) + .then(response => response.json()) + .then(userState => { + // Refresh page only when it is not already unlocked + if (window.localStorage) { + if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { + localStorage.removeItem('unlocked'); + } + } + resolve(userState); + }); + }); + + /** + * Fires when Extended Access grants permission. + */ + unlockArticle = () => { + console.log('unlockArticle callback'); + if (window.localStorage) { + if (!localStorage.getItem('unlocked')) { + localStorage['unlocked'] = true; + window.location.reload(); + } + } + } + + /** + * Display custom paywall instead of Google Intervention Dialog. + */ + showPaywall = () => { + console.log('showPaywall callback'); + } + + /** + * Handles SwG Entitlement callback. + */ + handleSwGEntitlement = () => { + console.log('handleSwGEntitlement callback'); + } + + /** + * Initialize GAA for Extended Access. + */ + GaaMetering.init({ + googleApiClientId: '224001690291-52f6af34qi6b7ug7h6r0vf8tdudlmhi3.apps.googleusercontent.com', + userState: getUserState, + allowedReferrers: allowedReferrers, + handleLoginPromise: handleLoginPromise, + registerUserPromise: registerUserPromise, + publisherEntitlementPromise: getUserState, + unlockArticle: unlockArticle, + showPaywall: showPaywall, + handleSwGEntitlement: handleSwGEntitlement + }); +} diff --git a/includes/class-extendedaccess-rest-endpoint.php b/includes/class-extendedaccess-rest-endpoint.php new file mode 100644 index 0000000..eb1859f --- /dev/null +++ b/includes/class-extendedaccess-rest-endpoint.php @@ -0,0 +1,186 @@ + \WP_REST_Server::READABLE, + 'callback' => array( __CLASS__, 'api_login_status' ), + ) + ); + + \register_rest_route( + 'newspack-extended-access/v1', + '/login/google', + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( __CLASS__, 'api_google_login_register' ), + ) + ); + } + + /** + * Handles Google Extended Access registration route. + * + * @param \WP_REST_Request $request Request object. + * @return mixed Returns Extended Access userState object. + */ + public static function api_google_login_register( $request ) { + // Decode JWT. + $token = json_decode( base64_decode( str_replace( '_', '/', str_replace( '-', '+', explode( '.', $request->get_body() )[1] ) ) ) ); + + // Get Google Email. + $email = $token->email; + + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + self::assign_user_plan( $email ); + } else { + \add_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); + $result = Newspack\Reader_Activation::register_reader( $email, '', true, array() ); + self::assign_user_plan( $email ); + \remove_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); + // At this point the user will be logged in. + } + if ( is_wp_error( $result ) ) { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => $result, + ) + ); + } + + return \rest_ensure_response( + array( + 'id' => base64_encode( $token->sub ), + 'registrationTimestamp' => time(), + 'subscriptionTimestamp' => time(), + 'granted' => true, + 'grantReason' => 'SUBSCRIBER', + ) + ); + } + + /** + * Assign an existing user 'premium-membership' when registered. + * + * @param string $email EmailId of the new registered user. + */ + public static function assign_user_plan( $email ) { + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + // Log the user in. + $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); + + $user_id = $existing_user->ID; + $membership_plan = wc_memberships_get_membership_plan( 'premium-membership' ); + $is_active_member = wc_memberships_is_user_member( $user_id, $membership_plan->id, false ); + $existing_membership = wc_memberships_get_user_membership( $user_id, $membership_plan->id ); + $plans = wc_memberships_get_membership_plans(); + + if ( ! $is_active_member && empty( $existing_membership ) && 0 !== $user_id ) { + $args = array( + // Enter the ID (post ID) of the plan to grant at registration. + 'plan_id' => $membership_plan->id, + 'user_id' => $user_id, + ); + $new_membership = wc_memberships_create_user_membership( $args ); + } + } + } + + /** + * Handles Google Extended Access login status route. + * + * @param \WP_REST_Request $request Request object. + * @return mixed Returns Extended Access userState object. + */ + public static function api_login_status( $request ) { + $logged_in_user = \wp_get_current_user(); + + if ( $logged_in_user ) { + $email = $logged_in_user->user_email; + + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + // Log the user in. + $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); + + if ( is_wp_error( $result ) ) { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => $result, + ) + ); + } + } else { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => $result, + ) + ); + } + + return \rest_ensure_response( + array( + 'id' => base64_encode( $token->sub ), + 'registrationTimestamp' => time(), + 'subscriptionTimestamp' => time(), + 'granted' => true, + 'grantReason' => 'SUBSCRIBER', + ) + ); + } else { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => 'no-logged-in-user', + ) + ); + } + } + + + /** + * Enables registering through SWG even if it is disabled. + * + * @param boolean $is_enabled Existing value of newspack_reader_activation_enabled option. + * @return boolean Returns always true for SWG. + */ + public static function bypass_newspack_reader_activation_enabled( $is_enabled ) { + return true; + } +} diff --git a/includes/class-google-extendedaccess.php b/includes/class-google-extendedaccess.php new file mode 100644 index 0000000..9862083 --- /dev/null +++ b/includes/class-google-extendedaccess.php @@ -0,0 +1,121 @@ + 'https://schema.org', + '@type' => 'Article', + 'isAccessibleForFree' => ! wc_memberships_is_post_content_restricted(), + 'isPartOf' => array( + '@type' => array( 'CreativeWork', 'Product' ), + 'name' => get_bloginfo( 'name' ), + 'productID' => ( isset( $_SERVER['SERVER_NAME'] ) ? filter_var( $_SERVER['SERVER_NAME'], FILTER_SANITIZE_URL ) : '' ) . ':showcase', + ), + 'publisher' => array( + '@type' => 'Organization', + 'name' => get_bloginfo( 'name' ), + ), + ); + + $ld_json = wp_json_encode( $ld_json, $flags ); + $ld_json = \str_replace( "\n", \PHP_EOL . "\t", $ld_json ); + ?> + + wp_create_nonce( 'wp_rest' ), + 'allowedReferrers' => $allowed_referrers, + ) + ); + + // Google Extended Access Scripts. + wp_print_script_tag( + array( + 'id' => 'google-account-gsi-client', + 'async' => true, + 'src' => esc_url( 'https://accounts.google.com/gsi/client' ), + 'defer' => true, + ) + ); + + wp_print_script_tag( + array( + 'id' => 'google-news-swg', + 'async' => true, + 'subscriptions-control' => 'manual', + 'src' => esc_url( 'https://news.google.com/swg/js/v1/swg.js' ), + ) + ); + + wp_print_script_tag( + array( + 'id' => 'google-news-swg-gaa', + 'async' => true, + 'src' => esc_url( 'https://news.google.com/swg/js/v1/swg-gaa.js' ), + 'onload' => 'InitGaaMetering()', + ) + ); + } + } + +} diff --git a/includes/class-initializer.php b/includes/class-initializer.php index 586442e..e00e81c 100644 --- a/includes/class-initializer.php +++ b/includes/class-initializer.php @@ -7,6 +7,8 @@ namespace Newspack_Extended_Access; +use Newspack; + /** * Class to handle the plugin initialization */ @@ -16,8 +18,14 @@ class Initializer { * Runs the initialization. */ public static function init() { - WooCommerce::init(); - YoastSEO::init(); + // TODO (@AnuragVasanwala): Please remove try...catch. It is only enabled for testing purpose. + try { + WooCommerce::init(); + ExtendedAccess_REST_Endpoint::init(); + Google_ExtendedAccess::init(); + } catch ( \Error $er ) { + echo esc_html( $er ); + } } } diff --git a/includes/class-yoastseo.php b/includes/class-yoastseo.php deleted file mode 100644 index ec8c009..0000000 --- a/includes/class-yoastseo.php +++ /dev/null @@ -1,46 +0,0 @@ - Date: Sun, 16 Apr 2023 19:30:58 +0530 Subject: [PATCH 04/24] =?UTF-8?q?=F0=9F=8F=97=20ServerSide=20script=20to?= =?UTF-8?q?=20bypass=20paywall=20restriction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/class-single-post-subscription.php | 50 +++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 includes/class-single-post-subscription.php diff --git a/includes/class-single-post-subscription.php b/includes/class-single-post-subscription.php new file mode 100644 index 0000000..ef2e320 --- /dev/null +++ b/includes/class-single-post-subscription.php @@ -0,0 +1,50 @@ +get_restrictions_instance()->get_posts_restrictions_instance(); + remove_action( 'wp', [ $membership_instance, 'handle_restriction_modes' ] ) ; + remove_filter( 'the_posts', [ $membership_instance, 'exclude_restricted_content_comments' ], 999, 2 ); + remove_filter( 'pre_get_comments', [ $membership_instance, 'exclude_restricted_comments' ], 999 ); + remove_filter( 'get_previous_post_where', [ $membership_instance, 'exclude_restricted_adjacent_posts' ], 1, 5 ); + remove_filter( 'get_next_post_where', [ $membership_instance, 'exclude_restricted_adjacent_posts' ], 1, 5 ); + remove_filter( 'posts_clauses', [ $membership_instance, 'handle_posts_clauses' ], 999, 2 ); + remove_filter( 'get_terms_args', [ $membership_instance, 'handle_get_terms_args' ], 999, 2 ); + remove_filter( 'terms_clauses', [ $membership_instance, 'handle_terms_clauses' ], 999 ); + } + } +} From 485747bf15e19c6cb917c5079cdb9908c4163ddd Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Sun, 16 Apr 2023 19:35:36 +0530 Subject: [PATCH 05/24] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20`Single=5FPost?= =?UTF-8?q?=5FSubscription`=20to=20plugin=20`init`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/class-initializer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-initializer.php b/includes/class-initializer.php index e00e81c..8484b6a 100644 --- a/includes/class-initializer.php +++ b/includes/class-initializer.php @@ -23,6 +23,7 @@ public static function init() { WooCommerce::init(); ExtendedAccess_REST_Endpoint::init(); Google_ExtendedAccess::init(); + Single_Post_Subscription::init(); } catch ( \Error $er ) { echo esc_html( $er ); } From e48a4f717092b6d619595348c14b2625a7102063 Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Sun, 16 Apr 2023 21:23:45 +0530 Subject: [PATCH 06/24] =?UTF-8?q?=E2=9C=A8=20Add=20`Extended=20Access`=20s?= =?UTF-8?q?ubmenu=20option=20page=20inside=20`WooCommerce`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update php and js scripts to use fields defined by option page. --- assets/newspack-swg.js | 2 +- .../class-extendedaccess-rest-endpoint.php | 5 +- includes/class-google-extendedaccess.php | 5 +- includes/class-initializer.php | 2 + includes/class-option-page.php | 164 ++++++++++++++++++ includes/class-single-post-subscription.php | 17 +- 6 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 includes/class-option-page.php diff --git a/assets/newspack-swg.js b/assets/newspack-swg.js index 8a93b31..67d8276 100644 --- a/assets/newspack-swg.js +++ b/assets/newspack-swg.js @@ -153,7 +153,7 @@ function InitGaaMetering() { * Initialize GAA for Extended Access. */ GaaMetering.init({ - googleApiClientId: '224001690291-52f6af34qi6b7ug7h6r0vf8tdudlmhi3.apps.googleusercontent.com', + googleApiClientId: AuthSettings.googleClientId, // For Newspack Staging Site: '224001690291-52f6af34qi6b7ug7h6r0vf8tdudlmhi3.apps.googleusercontent.com' userState: getUserState, allowedReferrers: allowedReferrers, handleLoginPromise: handleLoginPromise, diff --git a/includes/class-extendedaccess-rest-endpoint.php b/includes/class-extendedaccess-rest-endpoint.php index eb1859f..30ea58d 100644 --- a/includes/class-extendedaccess-rest-endpoint.php +++ b/includes/class-extendedaccess-rest-endpoint.php @@ -96,6 +96,7 @@ public static function api_google_login_register( $request ) { * @param string $email EmailId of the new registered user. */ public static function assign_user_plan( $email ) { + $newspack_extended_access_options = get_option( 'newspack_extended_access_configuration' ); $existing_user = \get_user_by( 'email', $email ); if ( $existing_user ) { @@ -103,7 +104,7 @@ public static function assign_user_plan( $email ) { $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); $user_id = $existing_user->ID; - $membership_plan = wc_memberships_get_membership_plan( 'premium-membership' ); + $membership_plan = wc_memberships_get_membership_plan( $newspack_extended_access_options['default_subscription'] ); $is_active_member = wc_memberships_is_user_member( $user_id, $membership_plan->id, false ); $existing_membership = wc_memberships_get_user_membership( $user_id, $membership_plan->id ); $plans = wc_memberships_get_membership_plans(); @@ -149,7 +150,7 @@ public static function api_login_status( $request ) { return \rest_ensure_response( array( 'granted' => false, - 'reason' => $result, + 'reason' => 'not-existing-user', ) ); } diff --git a/includes/class-google-extendedaccess.php b/includes/class-google-extendedaccess.php index 9862083..ec1db43 100644 --- a/includes/class-google-extendedaccess.php +++ b/includes/class-google-extendedaccess.php @@ -70,6 +70,8 @@ public static function add_extended_access_ld_json() { public static function enqueue_script() { // Add scripts only for `post` type. if ( get_post_type() === 'post' ) { // Add slug in condition. + $newspack_extended_access_options = get_option( 'newspack_extended_access_configuration' ); + // Newspack Extended Access Script. wp_register_script( 'newspack-swg', '/wp-content/plugins/newspack-extended-access/assets/newspack-swg.js', array(), '0.21', false ); wp_enqueue_script( 'newspack-swg' ); @@ -83,8 +85,9 @@ public static function enqueue_script() { 'newspack-swg', 'AuthSettings', array( - 'nonce' => wp_create_nonce( 'wp_rest' ), 'allowedReferrers' => $allowed_referrers, + 'googleClientId' => $newspack_extended_access_options['google_client_id'], + 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); diff --git a/includes/class-initializer.php b/includes/class-initializer.php index e00e81c..18156ab 100644 --- a/includes/class-initializer.php +++ b/includes/class-initializer.php @@ -23,6 +23,8 @@ public static function init() { WooCommerce::init(); ExtendedAccess_REST_Endpoint::init(); Google_ExtendedAccess::init(); + Single_Post_Subscription::init(); + Option_Page::init(); } catch ( \Error $er ) { echo esc_html( $er ); } diff --git a/includes/class-option-page.php b/includes/class-option-page.php new file mode 100644 index 0000000..f310a79 --- /dev/null +++ b/includes/class-option-page.php @@ -0,0 +1,164 @@ + + +
Sample description.
+ + + ++ +
+An integration for utilizing Google Extended Access.
'; + $input_desc = 'Refer Google Developer Documents to setup and configure your Google Client API ID.
Make sure to add your domain ' . $allowed_referrers . ' to Authorized JavaScript origins.'; + + // Override existing settings with out 'Newspack Extended Access' tab. + $settings = array( + + array( + 'name' => __( 'Newspack Extended Access', 'newspack-extended-access' ), + 'type' => 'title', + 'desc' => $title_desc, + ), + + array( + 'type' => 'textarea', + 'id' => 'newspack_extended_access__google_client_api_id', + 'name' => __( 'Google Client API ID', 'newspack-extended-access' ), + 'desc' => $input_desc, + 'default' => '', + ), + + array( + 'type' => 'sectionend', + ), + + ); + + } + + return $settings; + } +} diff --git a/newspack-extended-access.php b/newspack-extended-access.php index 77e6667..58ba542 100644 --- a/newspack-extended-access.php +++ b/newspack-extended-access.php @@ -6,9 +6,9 @@ * Author: Automattic * Text Domain: newspack-extended-access * Domain Path: /languages - * Version: 0.22 + * Version: 1.0 * - * @package newspack-extended-access + * @package Newspack\Extended_Access */ defined( 'ABSPATH' ) || exit; @@ -19,4 +19,4 @@ require_once 'vendor/autoload.php'; -Newspack_Extended_Access\Initializer::init(); +Newspack\Extended_Access\Initializer::init(); From 84a9439610ecc23c7a95fb857f0d201c8ca779f2 Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:35:45 +0530 Subject: [PATCH 08/24] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Resolve=20merge=20co?= =?UTF-8?q?nflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/newspack-swg.js | 166 ++++++++++++++++ .../class-extendedaccess-rest-endpoint.php | 187 ++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 assets/newspack-swg.js create mode 100644 includes/class-extendedaccess-rest-endpoint.php diff --git a/assets/newspack-swg.js b/assets/newspack-swg.js new file mode 100644 index 0000000..67d8276 --- /dev/null +++ b/assets/newspack-swg.js @@ -0,0 +1,166 @@ +/** + * Newspack SwG Library. + * + * Initializes GAA and defines required callbacks to + * register/login to site via SwG, check post status + * and unlock article. + * + * @link https://www.newspack.com + * @file This files defines SwG required methods and callback for Newspack specific functionality. + * @author Newspack + * @since 0.21 + */ + +/** + * Parses JWT token and converts into equivalent JSON object. + * + * @param {string} token JWT Token to be parse. + * @returns {Object} Parsed JWT as JSON Object. + */ +function parseJwt(token) { + var base64Url = token.split('.')[1]; + var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + + return JSON.parse(jsonPayload); +} + +/** + * Initializes GaaMetering for SwG. + */ +function InitGaaMetering() { + + /** + * Variables + */ + const allowedReferrers = AuthSettings.allowedReferrers ; // ['extended-access-dev.newspackstaging.com', 'newspackstaging.com'] + + /** + * Login Existing User Promise callback handler. + */ + handleLoginPromise = new Promise(() => { + GaaMetering.getLoginPromise().then(() => { + console.log('handleLoginPromise', 'Redirecting to login URL'); + // Capture full URL, including URL parameters, to redirect the user to after login + const redirectUri = encodeURIComponent(window.location.href); + // Redirect to a login page for existing users to login. + window.location = `${window.location.protocol}//${window.location.hostname}/my-account?redirect_to=${redirectUri}`; + }); + }); + + /** + * Register New User Promise callback handler. + */ + registerUserPromise = new Promise((resolve) => { + // Get the information for the user who has just registered. + GaaMetering.getGaaUserPromise().then((gaaUser) => { + // Send that information to your Registration endpoint to register the user and + // return the userState for the newly registered user. + + const gaaUserDecoded = parseJwt(gaaUser.credential); + + fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/google`, + { + method: 'POST', + headers: { + 'Content-type': 'text/plain', + 'X-WP-Nonce': AuthSettings.nonce + }, + body: gaaUser.credential + }) + .then(response => response.json()) + .then(userState => { + // Refresh page only when it is not already unlocked + if (window.localStorage) { + if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { + localStorage.removeItem('unlocked'); + } + } + resolve(userState); + }); + }); + }); + + /** + * Check whether publisher has provided access to the User or not. + */ + publisherEntitlementPromise = new Promise((resolve) => { + /* Do not grant user, show Google Intervention Dialog. */ + resolve({ granted: false }); + + /* Grant user, do not show Google Intervention Dialog. */ + // resolve({granted: true, grantReason: 'METERING'}); + + /* Do not grant user, show Google Extended Access Dialog. */ + // resolve({ id: 'MTAzMjk1OTEyMjMwNDQ4NjQxMzQz', registrationTimestamp: 1680773672, granted: false }); + }); + + /** + * Check whether publisher has provided access to the User or not. + */ + getUserState = new Promise((resolve) => { + + fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/status`, + { + method: 'GET', + headers: { + 'Content-type': 'text/plain', + 'X-WP-Nonce': AuthSettings.nonce + }, + }) + .then(response => response.json()) + .then(userState => { + // Refresh page only when it is not already unlocked + if (window.localStorage) { + if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { + localStorage.removeItem('unlocked'); + } + } + resolve(userState); + }); + }); + + /** + * Fires when Extended Access grants permission. + */ + unlockArticle = () => { + console.log('unlockArticle callback'); + if (window.localStorage) { + if (!localStorage.getItem('unlocked')) { + localStorage['unlocked'] = true; + window.location.reload(); + } + } + } + + /** + * Display custom paywall instead of Google Intervention Dialog. + */ + showPaywall = () => { + console.log('showPaywall callback'); + } + + /** + * Handles SwG Entitlement callback. + */ + handleSwGEntitlement = () => { + console.log('handleSwGEntitlement callback'); + } + + /** + * Initialize GAA for Extended Access. + */ + GaaMetering.init({ + googleApiClientId: AuthSettings.googleClientId, // For Newspack Staging Site: '224001690291-52f6af34qi6b7ug7h6r0vf8tdudlmhi3.apps.googleusercontent.com' + userState: getUserState, + allowedReferrers: allowedReferrers, + handleLoginPromise: handleLoginPromise, + registerUserPromise: registerUserPromise, + publisherEntitlementPromise: getUserState, + unlockArticle: unlockArticle, + showPaywall: showPaywall, + handleSwGEntitlement: handleSwGEntitlement + }); +} diff --git a/includes/class-extendedaccess-rest-endpoint.php b/includes/class-extendedaccess-rest-endpoint.php new file mode 100644 index 0000000..30ea58d --- /dev/null +++ b/includes/class-extendedaccess-rest-endpoint.php @@ -0,0 +1,187 @@ + \WP_REST_Server::READABLE, + 'callback' => array( __CLASS__, 'api_login_status' ), + ) + ); + + \register_rest_route( + 'newspack-extended-access/v1', + '/login/google', + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( __CLASS__, 'api_google_login_register' ), + ) + ); + } + + /** + * Handles Google Extended Access registration route. + * + * @param \WP_REST_Request $request Request object. + * @return mixed Returns Extended Access userState object. + */ + public static function api_google_login_register( $request ) { + // Decode JWT. + $token = json_decode( base64_decode( str_replace( '_', '/', str_replace( '-', '+', explode( '.', $request->get_body() )[1] ) ) ) ); + + // Get Google Email. + $email = $token->email; + + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + self::assign_user_plan( $email ); + } else { + \add_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); + $result = Newspack\Reader_Activation::register_reader( $email, '', true, array() ); + self::assign_user_plan( $email ); + \remove_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); + // At this point the user will be logged in. + } + if ( is_wp_error( $result ) ) { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => $result, + ) + ); + } + + return \rest_ensure_response( + array( + 'id' => base64_encode( $token->sub ), + 'registrationTimestamp' => time(), + 'subscriptionTimestamp' => time(), + 'granted' => true, + 'grantReason' => 'SUBSCRIBER', + ) + ); + } + + /** + * Assign an existing user 'premium-membership' when registered. + * + * @param string $email EmailId of the new registered user. + */ + public static function assign_user_plan( $email ) { + $newspack_extended_access_options = get_option( 'newspack_extended_access_configuration' ); + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + // Log the user in. + $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); + + $user_id = $existing_user->ID; + $membership_plan = wc_memberships_get_membership_plan( $newspack_extended_access_options['default_subscription'] ); + $is_active_member = wc_memberships_is_user_member( $user_id, $membership_plan->id, false ); + $existing_membership = wc_memberships_get_user_membership( $user_id, $membership_plan->id ); + $plans = wc_memberships_get_membership_plans(); + + if ( ! $is_active_member && empty( $existing_membership ) && 0 !== $user_id ) { + $args = array( + // Enter the ID (post ID) of the plan to grant at registration. + 'plan_id' => $membership_plan->id, + 'user_id' => $user_id, + ); + $new_membership = wc_memberships_create_user_membership( $args ); + } + } + } + + /** + * Handles Google Extended Access login status route. + * + * @param \WP_REST_Request $request Request object. + * @return mixed Returns Extended Access userState object. + */ + public static function api_login_status( $request ) { + $logged_in_user = \wp_get_current_user(); + + if ( $logged_in_user ) { + $email = $logged_in_user->user_email; + + $existing_user = \get_user_by( 'email', $email ); + + if ( $existing_user ) { + // Log the user in. + $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); + + if ( is_wp_error( $result ) ) { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => $result, + ) + ); + } + } else { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => 'not-existing-user', + ) + ); + } + + return \rest_ensure_response( + array( + 'id' => base64_encode( $token->sub ), + 'registrationTimestamp' => time(), + 'subscriptionTimestamp' => time(), + 'granted' => true, + 'grantReason' => 'SUBSCRIBER', + ) + ); + } else { + return \rest_ensure_response( + array( + 'granted' => false, + 'reason' => 'no-logged-in-user', + ) + ); + } + } + + + /** + * Enables registering through SWG even if it is disabled. + * + * @param boolean $is_enabled Existing value of newspack_reader_activation_enabled option. + * @return boolean Returns always true for SWG. + */ + public static function bypass_newspack_reader_activation_enabled( $is_enabled ) { + return true; + } +} From 5e87bef6d2011fb6c405333278532f63e40928d3 Mon Sep 17 00:00:00 2001 From: Anurag Vasanwala <75766877+AnuragVasanwala@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:50:18 +0530 Subject: [PATCH 09/24] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Minor=20Refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/newspack-swg.js | 166 ---------------- .../class-extendedaccess-rest-endpoint.php | 187 ------------------ includes/class-google-extendedaccess.php | 4 +- includes/class-initializer.php | 2 +- includes/class-rest-endpoint.php | 47 ++--- 5 files changed, 28 insertions(+), 378 deletions(-) delete mode 100644 assets/newspack-swg.js delete mode 100644 includes/class-extendedaccess-rest-endpoint.php diff --git a/assets/newspack-swg.js b/assets/newspack-swg.js deleted file mode 100644 index 67d8276..0000000 --- a/assets/newspack-swg.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Newspack SwG Library. - * - * Initializes GAA and defines required callbacks to - * register/login to site via SwG, check post status - * and unlock article. - * - * @link https://www.newspack.com - * @file This files defines SwG required methods and callback for Newspack specific functionality. - * @author Newspack - * @since 0.21 - */ - -/** - * Parses JWT token and converts into equivalent JSON object. - * - * @param {string} token JWT Token to be parse. - * @returns {Object} Parsed JWT as JSON Object. - */ -function parseJwt(token) { - var base64Url = token.split('.')[1]; - var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - - return JSON.parse(jsonPayload); -} - -/** - * Initializes GaaMetering for SwG. - */ -function InitGaaMetering() { - - /** - * Variables - */ - const allowedReferrers = AuthSettings.allowedReferrers ; // ['extended-access-dev.newspackstaging.com', 'newspackstaging.com'] - - /** - * Login Existing User Promise callback handler. - */ - handleLoginPromise = new Promise(() => { - GaaMetering.getLoginPromise().then(() => { - console.log('handleLoginPromise', 'Redirecting to login URL'); - // Capture full URL, including URL parameters, to redirect the user to after login - const redirectUri = encodeURIComponent(window.location.href); - // Redirect to a login page for existing users to login. - window.location = `${window.location.protocol}//${window.location.hostname}/my-account?redirect_to=${redirectUri}`; - }); - }); - - /** - * Register New User Promise callback handler. - */ - registerUserPromise = new Promise((resolve) => { - // Get the information for the user who has just registered. - GaaMetering.getGaaUserPromise().then((gaaUser) => { - // Send that information to your Registration endpoint to register the user and - // return the userState for the newly registered user. - - const gaaUserDecoded = parseJwt(gaaUser.credential); - - fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/google`, - { - method: 'POST', - headers: { - 'Content-type': 'text/plain', - 'X-WP-Nonce': AuthSettings.nonce - }, - body: gaaUser.credential - }) - .then(response => response.json()) - .then(userState => { - // Refresh page only when it is not already unlocked - if (window.localStorage) { - if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { - localStorage.removeItem('unlocked'); - } - } - resolve(userState); - }); - }); - }); - - /** - * Check whether publisher has provided access to the User or not. - */ - publisherEntitlementPromise = new Promise((resolve) => { - /* Do not grant user, show Google Intervention Dialog. */ - resolve({ granted: false }); - - /* Grant user, do not show Google Intervention Dialog. */ - // resolve({granted: true, grantReason: 'METERING'}); - - /* Do not grant user, show Google Extended Access Dialog. */ - // resolve({ id: 'MTAzMjk1OTEyMjMwNDQ4NjQxMzQz', registrationTimestamp: 1680773672, granted: false }); - }); - - /** - * Check whether publisher has provided access to the User or not. - */ - getUserState = new Promise((resolve) => { - - fetch(`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/status`, - { - method: 'GET', - headers: { - 'Content-type': 'text/plain', - 'X-WP-Nonce': AuthSettings.nonce - }, - }) - .then(response => response.json()) - .then(userState => { - // Refresh page only when it is not already unlocked - if (window.localStorage) { - if (localStorage.getItem('unlocked') && localStorage['unlocked'] === "true" && userState.granted === false) { - localStorage.removeItem('unlocked'); - } - } - resolve(userState); - }); - }); - - /** - * Fires when Extended Access grants permission. - */ - unlockArticle = () => { - console.log('unlockArticle callback'); - if (window.localStorage) { - if (!localStorage.getItem('unlocked')) { - localStorage['unlocked'] = true; - window.location.reload(); - } - } - } - - /** - * Display custom paywall instead of Google Intervention Dialog. - */ - showPaywall = () => { - console.log('showPaywall callback'); - } - - /** - * Handles SwG Entitlement callback. - */ - handleSwGEntitlement = () => { - console.log('handleSwGEntitlement callback'); - } - - /** - * Initialize GAA for Extended Access. - */ - GaaMetering.init({ - googleApiClientId: AuthSettings.googleClientId, // For Newspack Staging Site: '224001690291-52f6af34qi6b7ug7h6r0vf8tdudlmhi3.apps.googleusercontent.com' - userState: getUserState, - allowedReferrers: allowedReferrers, - handleLoginPromise: handleLoginPromise, - registerUserPromise: registerUserPromise, - publisherEntitlementPromise: getUserState, - unlockArticle: unlockArticle, - showPaywall: showPaywall, - handleSwGEntitlement: handleSwGEntitlement - }); -} diff --git a/includes/class-extendedaccess-rest-endpoint.php b/includes/class-extendedaccess-rest-endpoint.php deleted file mode 100644 index 30ea58d..0000000 --- a/includes/class-extendedaccess-rest-endpoint.php +++ /dev/null @@ -1,187 +0,0 @@ - \WP_REST_Server::READABLE, - 'callback' => array( __CLASS__, 'api_login_status' ), - ) - ); - - \register_rest_route( - 'newspack-extended-access/v1', - '/login/google', - array( - 'methods' => \WP_REST_Server::CREATABLE, - 'callback' => array( __CLASS__, 'api_google_login_register' ), - ) - ); - } - - /** - * Handles Google Extended Access registration route. - * - * @param \WP_REST_Request $request Request object. - * @return mixed Returns Extended Access userState object. - */ - public static function api_google_login_register( $request ) { - // Decode JWT. - $token = json_decode( base64_decode( str_replace( '_', '/', str_replace( '-', '+', explode( '.', $request->get_body() )[1] ) ) ) ); - - // Get Google Email. - $email = $token->email; - - $existing_user = \get_user_by( 'email', $email ); - - if ( $existing_user ) { - self::assign_user_plan( $email ); - } else { - \add_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); - $result = Newspack\Reader_Activation::register_reader( $email, '', true, array() ); - self::assign_user_plan( $email ); - \remove_filter( 'newspack_reader_activation_enabled', array( __CLASS__, 'bypass_newspack_reader_activation_enabled' ) ); - // At this point the user will be logged in. - } - if ( is_wp_error( $result ) ) { - return \rest_ensure_response( - array( - 'granted' => false, - 'reason' => $result, - ) - ); - } - - return \rest_ensure_response( - array( - 'id' => base64_encode( $token->sub ), - 'registrationTimestamp' => time(), - 'subscriptionTimestamp' => time(), - 'granted' => true, - 'grantReason' => 'SUBSCRIBER', - ) - ); - } - - /** - * Assign an existing user 'premium-membership' when registered. - * - * @param string $email EmailId of the new registered user. - */ - public static function assign_user_plan( $email ) { - $newspack_extended_access_options = get_option( 'newspack_extended_access_configuration' ); - $existing_user = \get_user_by( 'email', $email ); - - if ( $existing_user ) { - // Log the user in. - $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); - - $user_id = $existing_user->ID; - $membership_plan = wc_memberships_get_membership_plan( $newspack_extended_access_options['default_subscription'] ); - $is_active_member = wc_memberships_is_user_member( $user_id, $membership_plan->id, false ); - $existing_membership = wc_memberships_get_user_membership( $user_id, $membership_plan->id ); - $plans = wc_memberships_get_membership_plans(); - - if ( ! $is_active_member && empty( $existing_membership ) && 0 !== $user_id ) { - $args = array( - // Enter the ID (post ID) of the plan to grant at registration. - 'plan_id' => $membership_plan->id, - 'user_id' => $user_id, - ); - $new_membership = wc_memberships_create_user_membership( $args ); - } - } - } - - /** - * Handles Google Extended Access login status route. - * - * @param \WP_REST_Request $request Request object. - * @return mixed Returns Extended Access userState object. - */ - public static function api_login_status( $request ) { - $logged_in_user = \wp_get_current_user(); - - if ( $logged_in_user ) { - $email = $logged_in_user->user_email; - - $existing_user = \get_user_by( 'email', $email ); - - if ( $existing_user ) { - // Log the user in. - $result = Newspack\Reader_Activation::set_current_reader( $existing_user->ID ); - - if ( is_wp_error( $result ) ) { - return \rest_ensure_response( - array( - 'granted' => false, - 'reason' => $result, - ) - ); - } - } else { - return \rest_ensure_response( - array( - 'granted' => false, - 'reason' => 'not-existing-user', - ) - ); - } - - return \rest_ensure_response( - array( - 'id' => base64_encode( $token->sub ), - 'registrationTimestamp' => time(), - 'subscriptionTimestamp' => time(), - 'granted' => true, - 'grantReason' => 'SUBSCRIBER', - ) - ); - } else { - return \rest_ensure_response( - array( - 'granted' => false, - 'reason' => 'no-logged-in-user', - ) - ); - } - } - - - /** - * Enables registering through SWG even if it is disabled. - * - * @param boolean $is_enabled Existing value of newspack_reader_activation_enabled option. - * @return boolean Returns always true for SWG. - */ - public static function bypass_newspack_reader_activation_enabled( $is_enabled ) { - return true; - } -} diff --git a/includes/class-google-extendedaccess.php b/includes/class-google-extendedaccess.php index caae2db..7937d2d 100644 --- a/includes/class-google-extendedaccess.php +++ b/includes/class-google-extendedaccess.php @@ -51,7 +51,7 @@ public static function add_extended_access_ld_json() { ); $ld_json = wp_json_encode( $ld_json, $flags ); - $ld_json = \str_replace( "\n", \PHP_EOL . "\t", $ld_json ); + $ld_json = str_replace( "\n", PHP_EOL . "\t", $ld_json ); ?>