Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
46dd71b
[not verified] Modify the subscribe block by adding 'Add new plan' li…
n3f Sep 24, 2022
ab19490
[not verified] Attach paid subscriptions to subscribe block (#26416)
artpi Sep 24, 2022
1271a3b
[not verified] Added gating for paid newsletter
millerf Sep 24, 2022
087d674
[not verified] Fix linting
millerf Sep 24, 2022
cbe53fc
[not verified] Add Newsletter Post Visibility Setting (#26412)
romarioraffington Sep 24, 2022
ed1ac61
[not verified] Add plans should say "Manage plans" if user has the ri…
millerf Sep 25, 2022
0223deb
[not verified] Sync meta for post level visibility settings
artpi Sep 26, 2022
dda110d
[not verified] Change label to "Manage plans" only if there is no new…
millerf Sep 26, 2022
b24dd3c
[not verified] Develop locked content placeholder further.
mtias Sep 26, 2022
99db1ea
[not verified] Fix issue when not on WPCOM
millerf Sep 26, 2022
d7f8a52
[not verified] Update composer.lock
millerf Sep 26, 2022
35ab003
[not verified] Pass the login string to UI
artpi Sep 26, 2022
af369f9
[not verified] Fix newsletter plans check
millerf Sep 26, 2022
ce51154
[not verified] Add a check to not kick off purchase if site has no co…
artpi Sep 26, 2022
76c82b2
[not verified] Load Javascript only for sites that have paid plans
artpi Sep 26, 2022
39ee6ae
[not verified] pass email
artpi Sep 26, 2022
3361624
[not verified] Various fixes around 'Add paid plan' button
millerf Sep 26, 2022
203a265
[not verified] Prevent panel to stay open
millerf Sep 26, 2022
65df331
[not verified] working version
millerf Sep 26, 2022
84911db
[not verified] Adjust copy of paid plan panel
mtias Sep 26, 2022
659ccd2
[not verified] Simplify newsletter panel title
mtias Sep 26, 2022
7f68bcd
[not verified] Refactor GetAddPaidPlanButton
millerf Sep 27, 2022
29bf653
[not verified] Rename paid_members_only to members_only
millerf Sep 27, 2022
345e0f0
[not verified] fix frontend a bit
artpi Sep 27, 2022
38bc19d
[not verified] remove unecessary fragments
millerf Sep 27, 2022
bc70e83
[not verified] Rename visibility to access
millerf Sep 27, 2022
73e3b32
[not verified] Rename visibility to access and move panels
millerf Sep 27, 2022
11fc230
[not verified] Fix block
millerf Sep 27, 2022
626c376
[not verified] change to make overlay work
artpi Sep 27, 2022
30e6528
[not verified] bug
artpi Sep 27, 2022
48b41d7
[not verified] fix gating content
millerf Sep 27, 2022
2e611ae
[not verified] Change the labels of access levels
artpi Sep 27, 2022
119141e
[not verified] change labels of access levels
artpi Sep 27, 2022
cc95c0c
[not verified] Change naming for level of access
millerf Sep 27, 2022
b7eb523
[not verified] appropriate links for adding a new newsletter plan
artpi Sep 27, 2022
5efd1f4
[not verified] Fix default values for access level
millerf Sep 27, 2022
0081ed3
[not verified] Rename function name
romarioraffington Sep 27, 2022
30825d2
[not verified] Fix unit tests.
n3f Sep 27, 2022
d6c86e6
[not verified] Ignore undefined store in tests.
n3f Oct 6, 2022
b6251bc
[not verified] Fix production build.
n3f Oct 6, 2022
0cddbe3
[not verified] change the description of a change
artpi Oct 12, 2022
7c8df8e
[not verified] rework changelog
artpi Oct 12, 2022
9b96366
[not verified] Correct typo
romarioraffington Oct 21, 2022
adc33c9
[not verified] Move gating logic to Jetpack_Memberships class
romarioraffington Oct 25, 2022
c0a7fd0
[not verified] Move locked content to function and translate text
romarioraffington Oct 25, 2022
91946c9
[not verified] Add return type to function
romarioraffington Oct 25, 2022
86e1846
[not verified] Change hash to add-new-payment-plan
romarioraffington Oct 25, 2022
4b5a48c
[not verified] Change meta jetpack_newsletter_access to _jetpack_news…
millerf Oct 26, 2022
350cbd6
[not verified] Remove #add-new-payment-plan from the Manage plans link
romarioraffington Oct 26, 2022
b3cddf2
[not verified] Update paid plan panel text based on plan availability
romarioraffington Oct 26, 2022
a79d52c
[not verified] Move constants into their own file and fix missing met…
millerf Oct 27, 2022
6aa2106
[not verified] Update composer.lock
millerf Oct 27, 2022
788641a
[not verified] Fix imports
millerf Oct 27, 2022
294bb4c
[not verified] WIP: Gate content for subscribers
romarioraffington Oct 29, 2022
56e6e04
[not verified] Paid newsletter subscribe access level (#27144)
millerf Oct 31, 2022
16962cf
[not verified] Fix translation error when builing for production
romarioraffington Oct 31, 2022
8ea1d32
[not verified] Pass missing prop to the GetAddPaidPlanButton component
romarioraffington Oct 31, 2022
a724d92
[not verified] Newsletters: feature gating (#27142)
artpi Nov 1, 2022
4b5e1ed
[not verified] Remove Work-In-Progress for the gating content
artpi Nov 1, 2022
1c0f93a
[not verified] Add a link + message for subscription excerpts. (#27198)
n3f Nov 1, 2022
4eea37a
[not verified] Unlock content based for blog Subscribers (#27212)
romarioraffington Nov 9, 2022
0822b89
[not verified] fixing logged in check. (#27282)
n3f Nov 9, 2022
8d0a4d3
[not verified] Hide comments based on newsletter setting (#27344)
romarioraffington Nov 11, 2022
0aef57f
[not verified] Add/newsletter offline (#27352)
millerf Nov 21, 2022
58a1c9e
[not verified] Add a default param to postMeta (#27508)
romarioraffington Nov 21, 2022
44ab958
[not verified] Unlock content immediately for posts with the subscrib…
romarioraffington Nov 21, 2022
2aad19b
[not verified] Add missing import
romarioraffington Nov 21, 2022
583701d
[not verified] Refactor subscription tests (#27564)
millerf Nov 25, 2022
a26f179
[not verified] Prevent submission of empty subscribe field (#27597)
romarioraffington Nov 25, 2022
126036b
[not verified] Fix unit test includes
ice9js Nov 30, 2022
104fc40
[not verified] Skip test failing on php 5.6 and 7.0
artpi Dec 1, 2022
d88ce25
Fix post-merge
millerf Dec 13, 2022
97f284a
Added dependency for symfony
millerf Dec 13, 2022
378beed
Remove unused parameter
millerf Dec 13, 2022
0ddeba2
Paid Newsletters: debugging tests (#27905)
jeherve Dec 13, 2022
5130300
Fix checkout
millerf Dec 13, 2022
cf3497a
more whimsical description of a change
artpi Dec 15, 2022
84e6282
bail if form not found
artpi Dec 15, 2022
60ff116
better description
artpi Dec 15, 2022
d8ee251
Added an offline service
millerf Dec 15, 2022
508c450
Remove some styling and shortcode from the subscribe block
artpi Dec 16, 2022
392fd83
Merge branch 'trunk' into paid-newsletters
artpi Dec 16, 2022
c5a48e8
Merge branch 'trunk' into paid-newsletters
artpi Dec 16, 2022
40f6390
Fix tests
millerf Dec 16, 2022
1760e22
relaunch tests
millerf Dec 19, 2022
654b225
[not verified] Merge remote-tracking branch 'origin/trunk' into paid-…
kraftbj Dec 20, 2022
1652658
Update lock file
jeherve Dec 20, 2022
de026c9
Update lock file back from trunk
jeherve Dec 20, 2022
8917fe1
Add missing attributes to form (#28029)
romarioraffington Dec 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions projects/plugins/jetpack/changelog/add-post-visibility-setting
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Significance: major
Type: enhancement

Entire posts can now be restricted to either paid subscribers, or email subscribers. This will enable site owners to:
- reward their paying subscribers with secret Lasagna recipes
- create paid-only issues of their newsletters
- create incentives and landing pages to gain subscribers
- create paid courses, and pursue other subscriber-only models
- support themselves financially without sacrificing creative freedom
- buy literal jetpacks from all the money they make on their sites (outcome not guaranteed)
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/fix-premium-jwt-encode
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: bugfix

Premium-content block is shipped with a JWT lib that had a bug in its encode() method
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Significance: major
Type: enhancement

Paid Newsletter functionality:
If the site owner creates one or more "newsletter" paid plans available for purchase, subscribers will be given an option to pay while subscribing through the subscribe block.
This change goes together with ./add-post-visibility-setting to create a paid newsletter functionality.
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,12 @@ public static function encode( $payload, $key, $alg = 'HS256', $key_id = null, $
}

$segments = array();
$segments[] = static::urlsafe_b64_decode( static::json_encode( $header ) );
$segments[] = static::urlsafe_b64_decode( static::json_encode( $payload ) );
$segments[] = static::urlsafe_b64_encode( static::json_encode( $header ) );
$segments[] = static::urlsafe_b64_encode( static::json_encode( $payload ) );
$signing_input = implode( '.', $segments );

$signature = static::sign( $signing_input, $key, $alg );
$segments[] = static::urlsafe_b64_decode( $signature );
$segments[] = static::urlsafe_b64_encode( $signature );

return implode( '.', $segments );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ public function initialize();
* Given a token (this could be from a cookie, a querystring, or some other means)
* can the visitor see the premium content?
*
* @param array $valid_plan_ids .
* @param array $valid_plan_ids .
* @param string $access_level .
*
* @return boolean
*/
public function visitor_can_view_content( $valid_plan_ids );
public function visitor_can_view_content( $valid_plan_ids, $access_level );

/**
* The current visitor would like to obtain access. Where do they go?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
*/
abstract class Token_Subscription_Service implements Subscription_Service {

const JWT_AUTH_TOKEN_COOKIE_NAME = 'jp-premium-content-session';
const DECODE_EXCEPTION_FEATURE = 'memberships';
const DECODE_EXCEPTION_MESSAGE = 'Problem decoding provided token';
const REST_URL_ORIGIN = 'https://subscribe.wordpress.com/';
const JWT_AUTH_TOKEN_COOKIE_NAME = 'jp-premium-content-session';
const DECODE_EXCEPTION_FEATURE = 'memberships';
const DECODE_EXCEPTION_MESSAGE = 'Problem decoding provided token';
const REST_URL_ORIGIN = 'https://subscribe.wordpress.com/';
const BLOG_SUB_ACTIVE = 'active';
const BLOG_SUB_PENDING = 'pending';
const POST_ACCESS_LEVEL_SUBSCRIBERS = 'subscribers';

/**
* Initialize the token subscription service.
Expand All @@ -46,8 +49,9 @@ public function initialize() {
* @inheritDoc
*
* @param array $valid_plan_ids List of valid plan IDs.
* @param array $access_level Access level for content.
*/
public function visitor_can_view_content( $valid_plan_ids ) {
public function visitor_can_view_content( $valid_plan_ids, $access_level ) {

// URL token always has a precedence, so it can overwrite the cookie when new data available.
$token = $this->token_from_request();
Expand All @@ -70,40 +74,63 @@ public function visitor_can_view_content( $valid_plan_ids ) {
}

if ( $is_valid_token ) {
$subscriptions = (array) $payload['subscriptions'];
} elseif ( is_user_logged_in() ) {
/*
* If there is no token, but the user is logged in,
* get current subscriptions and determine if the user has
* a valid subscription to match the plan ID.
*/

/**
* Filter the subscriptions attached to a specific user on a given site.
*
* @since 9.4.0
* Allow access to the content if:
*
* @param array $subscriptions Array of subscriptions.
* @param int $user_id The user's ID.
* @param int $site_id ID of the current site.
* Active: user has a valid subscription
* Pending: user has subscribed to a free plan
*/
$subscriptions = apply_filters(
'earn_get_user_subscriptions_for_site_id',
array(),
wp_get_current_user()->ID,
$this->get_site_id()
$is_blog_subscriber = in_array(
$payload['blog_sub'],
array(
self::BLOG_SUB_ACTIVE,
self::BLOG_SUB_PENDING,
),
true
);

if ( empty( $subscriptions ) ) {
return false;
}
// format the subscriptions so that they can be validated.
$subscriptions = self::abbreviate_subscriptions( $subscriptions );
$subscriptions = (array) $payload['subscriptions'];
$is_paid_subscriber = $this->validate_subscriptions( $valid_plan_ids, $subscriptions );
} else {
return false;
// Token not valid. We bail even unless the post can be accessed publicly.
return $this->user_has_access( $access_level, false, false, get_the_ID() );
}

return $this->validate_subscriptions( $valid_plan_ids, $subscriptions );
return $this->user_has_access( $access_level, $is_blog_subscriber, $is_paid_subscriber, get_the_ID() );
}

/**
* Return if the user has access to the content depending on the access level and the user rights
*
* @param string $access_level Post or blog access level.
* @param bool $is_blog_subscriber Is user a subscriber of the blog.
* @param bool $is_paid_subscriber Is user a paid subscriber of the blog.
* @param int $post_id Post ID.
*
* @return bool does the user have access to the post/blog.
*/
protected function user_has_access( $access_level, $is_blog_subscriber, $is_paid_subscriber, $post_id ) {

if ( is_user_logged_in() && current_user_can( 'edit_post', $post_id ) ) {
// Admin has access
return true;
}

if ( empty( $access_level ) || $access_level === 'everybody' ) {
// empty level means the post is not gated for paid users
return true;
}

if ( $access_level === 'subscribers' ) {
return $is_blog_subscriber || $is_paid_subscriber;
}

if ( $access_level === 'paid_subscribers' ) {
return $is_paid_subscriber;
}

// This should not be a use case
return false;

}

/**
Expand All @@ -129,13 +156,6 @@ public function decode_token( $token ) {
*/
abstract public function get_key();

/**
* Get the ID of the current site.
*
* @return int
*/
abstract public function get_site_id();

// phpcs:disable
/**
* Get the URL to access the protected content.
Expand Down Expand Up @@ -173,6 +193,10 @@ private function token_from_cookie() {
* @return void
*/
private function set_token_cookie( $token ) {
if ( defined( 'TESTING_IN_JETPACK' ) && TESTING_IN_JETPACK ) {
return;
}

if ( ! empty( $token ) ) {
setcookie( self::JWT_AUTH_TOKEN_COOKIE_NAME, $token, 0, '/', COOKIE_DOMAIN, is_ssl(), true ); // httponly -- used by visitor_can_view_content() within the PHP context.
}
Expand Down Expand Up @@ -205,7 +229,7 @@ private function token_from_request() {
*
* @return bool
*/
protected function validate_subscriptions( $valid_plan_ids, $token_subscriptions ) {
public static function validate_subscriptions( $valid_plan_ids, $token_subscriptions ) {
// Create a list of product_ids to compare against.
$product_ids = array();
foreach ( $valid_plan_ids as $plan_id ) {
Expand All @@ -214,9 +238,8 @@ protected function validate_subscriptions( $valid_plan_ids, $token_subscriptions
$product_ids[] = $product_id;
}
}

foreach ( $token_subscriptions as $product_id => $token_subscription ) {
if ( in_array( $product_id, $product_ids, true ) ) {
if ( in_array( intval( $product_id ), $product_ids, true ) ) {
$end = is_int( $token_subscription->end_date ) ? $token_subscription->end_date : strtotime( $token_subscription->end_date );
if ( $end > time() ) {
return true;
Expand Down Expand Up @@ -247,6 +270,11 @@ private function get_rest_api_token_url( $site_id, $redirect_url ) {
* @return array<int, array>
*/
public static function abbreviate_subscriptions( $subscriptions_from_bd ) {

if ( empty( $subscriptions_from_bd ) ) {
return array();
}

$subscriptions = array();
foreach ( $subscriptions_from_bd as $subscription ) {
// We are picking the expiry date that is the most in the future.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ public function initialize() {
/**
* No subscription service available, no users can see this content.
*
* @param array $valid_plan_ids .
* @param array $valid_plan_ids .
* @param string $access_level .
*/
public function visitor_can_view_content( $valid_plan_ids ) {
public function visitor_can_view_content( $valid_plan_ids, $access_level ) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
<?php
/**
* This subscription service is used when a subscriber is offline and a token is not available.
* This subscription service will be used when rendering content in email and reader on WPCOM only.
* When content is being rendered, the current user and site are set.
* This allows us to lookup a users subscriptions and determine if the
* offline visitor can view content that has been deemed "Premium content".
* This subscription service is used when a subscriber is online
*
* @package Automattic\Jetpack\Extensions\Premium_Content
*/
Expand All @@ -13,65 +9,50 @@

/**
* Class WPCOM_Offline_Subscription_Service
* This subscription service is used when a subscriber is offline and a token is not available.
* This subscription service will be used when sending emails to subscribers
*
* @package Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service
*/
class WPCOM_Offline_Subscription_Service extends WPCOM_Token_Subscription_Service {
class WPCOM_Offline_Subscription_Service extends WPCOM_Online_Subscription_Service {

/**
* Is available()
*
* @return bool
*/
public static function available() {
// Return available if the user is logged in and either
// running a job (sending email subscription) OR
// handling API request on WPCOM (reader).
return (
( defined( 'WPCOM_JOBS' ) && WPCOM_JOBS ) ||
( defined( 'IS_WPCOM' ) && IS_WPCOM === true && ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) )
) && is_user_logged_in();
// Return available if the user is logged in and we are on WPCOM.
return false;
}

/**
* Lookup users subscriptions for a site and determine if the user has a valid subscription to match the plan ID
* Check if the subscriber can receive the newsletter.
* This is the only method where is user does not need to be logged in.
*
* @param int $user_id User id.
* @param int $post_id Post id.
*
* @param array $valid_plan_ids .
* @return bool
* @throws \Exception Throws an exception when used outside of WPCOM.
*/
public function visitor_can_view_content( $valid_plan_ids ) {
/** This filter is already documented in projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-token-subscription-service.php */
$subscriptions = apply_filters( 'earn_get_user_subscriptions_for_site_id', array(), wp_get_current_user()->ID, $this->get_site_id() );
if ( empty( $subscriptions ) ) {
public function subscriber_can_receive_post_by_mail( $user_id, $post_id ) {

if ( 0 === $user_id || empty( $user_id ) ) {
// Email cannot be sent to non-users
return false;
}
// format the subscriptions so that they can be validated.
$subscriptions = self::abbreviate_subscriptions( $subscriptions );
return $this->validate_subscriptions( $valid_plan_ids, $subscriptions );
}

/**
* Report the subscriptions as an ID => [ 'end_date' => ]. mapping
*
* @param array $subscriptions_from_bd .
*
* @return array<int, array>
*/
public static function abbreviate_subscriptions( $subscriptions_from_bd ) {
$subscriptions = array();
foreach ( $subscriptions_from_bd as $subscription ) {
// We are picking the expiry date that is the most in the future.
if (
'active' === $subscription['status'] && (
! isset( $subscriptions[ $subscription['product_id'] ] ) ||
empty( $subscription['end_date'] ) || // Special condition when subscription has no expiry date - we will default to a year from now for the purposes of the token.
strtotime( $subscription['end_date'] ) > strtotime( (string) $subscriptions[ $subscription['product_id'] ]->end_date )
)
) {
$subscriptions[ $subscription['product_id'] ] = new \stdClass();
$subscriptions[ $subscription['product_id'] ]->end_date = empty( $subscription['end_date'] ) ? ( time() + 365 * 24 * 3600 ) : $subscription['end_date'];
}
}
return $subscriptions;
$previous_user = wp_get_current_user();
wp_set_current_user( $user_id );

$access_level = get_post_meta( $post_id, '_jetpack_newsletter_access', true );
$valid_plan_ids = \Jetpack_Memberships::get_all_plans_id_jetpack_recurring_payments();
$is_blog_subscriber = true; // it is a subscriber as this is used in async when lopping through subscribers...
$allowed = $this->user_can_view_content( $valid_plan_ids, $access_level, $is_blog_subscriber, $post_id );

wp_set_current_user( $previous_user->ID );

return $allowed;
}
}
Loading