Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
26.33% covered (danger)
26.33%
188 / 714
7.50% covered (danger)
7.50%
3 / 40
CRAP
n/a
0 / 0
Automattic\Jetpack\Extensions\Subscriptions\register_block
85.95% covered (warning)
85.95%
104 / 121
0.00% covered (danger)
0.00%
0 / 1
11.34
Automattic\Jetpack\Extensions\Subscriptions\is_wpcom
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\reject_unknown_access_level
92.00% covered (success)
92.00%
23 / 25
0.00% covered (danger)
0.00%
0 / 1
9.04
Automattic\Jetpack\Extensions\Subscriptions\register_newsletter_access_column
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\add_paywalled_content_post_meta
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
7
Automattic\Jetpack\Extensions\Subscriptions\render_newsletter_access_rows
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
7.77
Automattic\Jetpack\Extensions\Subscriptions\newsletter_access_column_styles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\fetch_subscriber_counts
22.22% covered (danger)
22.22%
6 / 27
0.00% covered (danger)
0.00%
0 / 1
16.76
Automattic\Jetpack\Extensions\Subscriptions\get_subscriber_count
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\has_attribute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\get_attribute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\get_setting_class_name
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\get_element_class_names_from_attributes
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\is_button_only_style
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\get_element_styles_from_attributes
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
182
Automattic\Jetpack\Extensions\Subscriptions\get_attribute_color
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\get_global_style_color
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\get_color_from_slug
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
72
Automattic\Jetpack\Extensions\Subscriptions\is_jetpack_memberships_loaded
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\render_block
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
462
Automattic\Jetpack\Extensions\Subscriptions\get_post_access_level_for_current_post
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\render_for_website
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 1
702
Automattic\Jetpack\Extensions\Subscriptions\render_for_email
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\render_email
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
6
Automattic\Jetpack\Extensions\Subscriptions\add_paywall
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
Automattic\Jetpack\Extensions\Subscriptions\maybe_close_comments
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
Automattic\Jetpack\Extensions\Subscriptions\maybe_gate_existing_comments
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\is_jetpack_token_subscription_service_loaded
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\maybe_prevent_super_cache_caching
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
Automattic\Jetpack\Extensions\Subscriptions\get_paywall_content
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\get_current_url
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
110
Automattic\Jetpack\Extensions\Subscriptions\get_submit_button_text
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\is_top_subscription
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\sanitize_submit_text
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Subscriptions\get_paywall_blocks
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
110
Automattic\Jetpack\Extensions\Subscriptions\is_user_auth
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
Automattic\Jetpack\Extensions\Subscriptions\is_paid_post
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\is_subscribers_post
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
Automattic\Jetpack\Extensions\Subscriptions\get_paywall_blocks_subscribe_pending
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
Automattic\Jetpack\Extensions\Subscriptions\get_paywall_simple
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * Subscriptions Block.
4 *
5 * @package automattic/jetpack
6 */
7
8namespace Automattic\Jetpack\Extensions\Subscriptions;
9
10use Automattic\Jetpack\Blocks;
11use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service;
12use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Jetpack_Token_Subscription_Service;
13use Automattic\Jetpack\Modules;
14use Automattic\Jetpack\Status\Host;
15use Automattic\Jetpack\Status\Request;
16use Jetpack_Gutenberg;
17use Jetpack_Memberships;
18use Jetpack_Subscriptions_Widget;
19
20if ( ! defined( 'ABSPATH' ) ) {
21    exit( 0 );
22}
23
24require_once __DIR__ . '/class-jetpack-subscription-site.php';
25require_once __DIR__ . '/constants.php';
26require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
27
28/**
29 * These block defaults should match ./constants.js
30 */
31const DEFAULT_BORDER_RADIUS_VALUE = 0;
32const DEFAULT_BORDER_WEIGHT_VALUE = 1;
33const DEFAULT_FONTSIZE_VALUE      = '16px';
34const DEFAULT_PADDING_VALUE       = 15;
35const DEFAULT_SPACING_VALUE       = 10;
36const DEFAULT_BUTTON_WIDTH        = 'auto';
37
38/**
39 * Registers the block for use in Gutenberg
40 * This is done via an action so that we can disable
41 * registration if we need to.
42 */
43function register_block() {
44    /*
45     * Disable the feature on P2 blogs
46     */
47    if ( function_exists( '\WPForTeams\is_wpforteams_site' ) &&
48        \WPForTeams\is_wpforteams_site( get_current_blog_id() ) ) {
49        return;
50    }
51
52    /*
53     * Do not proceed if the newsletter feature (Subscriptions module) is not enabled
54     */
55    if ( ! ( new Modules() )->is_active( 'subscriptions' ) ) {
56        Jetpack_Gutenberg::set_extension_unavailable( 'subscriptions', 'missing_module', array( 'module' => 'subscriptions' ) );
57        return;
58    }
59
60    require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
61
62    if ( \Jetpack_Memberships::should_enable_monetize_blocks_in_editor() ) {
63        Blocks::jetpack_register_block(
64            __DIR__,
65            array(
66                'render_callback'       => __NAMESPACE__ . '\render_block',
67                'render_email_callback' => __NAMESPACE__ . '\render_email',
68                'supports'              => array(
69                    'spacing' => array(
70                        'margin'  => true,
71                        'padding' => true,
72                    ),
73                    'align'   => array( 'wide', 'full' ),
74                ),
75            )
76        );
77    }
78
79    register_post_meta(
80        'post',
81        META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS,
82        array(
83            'show_in_rest'      => true,
84            'single'            => true,
85            'type'              => 'string',
86            /* translators: Keep the quoted values, block names and meta keys in English. */
87            'description'       => __( 'Who can read the post on the site: "everybody" (the default, also when empty), "subscribers" (free and paid) or "paid_subscribers" (paid only). Anyone without access sees a subscribe prompt instead. "everybody" and "subscribers" posts are emailed to all subscribers, "paid_subscribers" posts only to paid ones; with newsletter categories on, only to those subscribed to one of its categories or to all content. To show a free preview, add a Paywall block (jetpack/paywall) where it should end: everything above it stays public, the post is emailed to all subscribers and cut off at the paywall for anyone without access, and an "everybody" post becomes subscribers-only below the block. "paid_subscribers" works only on sites that already have a paid newsletter tier. Without one, the owner first connects Stripe and adds a tier under Monetize > Payment Settings in the WordPress.com dashboard (Jetpack Cloud on self-hosted sites). If unsure a tier exists, check with the site owner first.', 'jetpack' ),
88            // The REST schema already rejects non-strings, but non-REST writers
89            // (importers, XML-RPC, WP-CLI, direct update_post_meta, sync) only pass
90            // through sanitize_meta(). Coerce anything that isn't a string to ''
91            // ("everybody") so a corrupt value can't be persisted and later fatal the
92            // strict string-typed access checks.
93            'sanitize_callback' => function ( $value ) {
94                return is_string( $value ) ? $value : '';
95            },
96            'auth_callback'     => function () {
97                return wp_get_current_user()->has_cap( 'edit_posts' );
98            },
99        )
100    );
101
102    // The meta is a "don't send" flag, so invert the blog option (which defaults to true = "send").
103    $dont_email_default = ! get_option( 'wpcom_newsletter_send_default', true );
104
105    register_post_meta(
106        'post',
107        META_NAME_FOR_POST_DONT_EMAIL_TO_SUBS,
108        array(
109            'default'       => $dont_email_default,
110            'show_in_rest'  => true,
111            'single'        => true,
112            'type'          => 'boolean',
113            /* translators: Keep the quoted values, block names and meta keys in English. */
114            'description'   => __( 'Set to true to publish without emailing the post to subscribers. The email is sent once, when the post goes live, so this has to be set in the request that publishes it, or any time before the date of a scheduled post. Once the post is published, changing it neither sends nor recalls the email. The default comes from the site\'s newsletter settings and can be either value, so send it explicitly when the email matters.', 'jetpack' ),
115            'auth_callback' => function () {
116                return wp_get_current_user()->has_cap( 'edit_posts' );
117            },
118        )
119    );
120
121    register_post_meta(
122        'post',
123        META_NAME_FOR_POST_TIER_ID_SETTINGS,
124        array(
125            'show_in_rest'  => true,
126            'single'        => true,
127            'type'          => 'integer',
128            /* translators: Keep the quoted values, block names and meta keys in English. */
129            'description'   => __( 'Only applies when _jetpack_newsletter_access is "paid_subscribers": the ID of the monthly paid newsletter tier a reader needs. Subscribers on that tier, its yearly plan or a plan priced the same or higher in the same currency can read the post and get the email. Unset or 0 lets every paid subscriber in, which is the right value unless the site owner names a specific tier. An ID that matches no tier is ignored.', 'jetpack' ),
130            'auth_callback' => function () {
131                return wp_get_current_user()->has_cap( 'edit_posts' );
132            },
133        )
134    );
135
136    register_post_meta(
137        'post',
138        META_NAME_CONTAINS_PAYWALLED_CONTENT,
139        array(
140            'show_in_rest'  => true,
141            'single'        => true,
142            'type'          => 'boolean',
143            /* translators: Keep the quoted values, block names and meta keys in English. */
144            'description'   => __( 'Computed on every save: true when the post has a Paywall block or its _jetpack_newsletter_access is "subscribers" or "paid_subscribers". Any value sent here is overwritten; change _jetpack_newsletter_access or the content instead.', 'jetpack' ),
145            'auth_callback' => function () {
146                return wp_get_current_user()->has_cap( 'edit_posts' );
147            },
148        )
149    );
150
151    // This ensures Jetpack will sync this post meta to WPCOM.
152    add_filter(
153        'jetpack_sync_post_meta_whitelist',
154        function ( $allowed_meta ) {
155            return array_merge(
156                $allowed_meta,
157                array(
158                    META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS,
159                    META_NAME_FOR_POST_DONT_EMAIL_TO_SUBS,
160                    META_NAME_CONTAINS_PAYWALLED_CONTENT,
161                    META_NAME_FOR_POST_TIER_ID_SETTINGS,
162                    META_NAME_CONTAINS_PAID_CONTENT,
163                )
164            );
165        }
166    );
167
168    // Hide the content â€“ Priority 8 makes it run before do_blocks gets called for the content
169    add_filter( 'the_content', __NAMESPACE__ . '\add_paywall', 8 );
170
171    // Close comments on the front-end
172    add_filter( 'comments_open', __NAMESPACE__ . '\maybe_close_comments', 10, 2 );
173    add_filter( 'pings_open', __NAMESPACE__ . '\maybe_close_comments', 10, 2 );
174
175    // Hide existing comments
176    add_filter( 'get_comment', __NAMESPACE__ . '\maybe_gate_existing_comments' );
177
178    // Add a 'Newsletter' column to the Edit posts page
179    // We only display the "Newsletter" column if we have configured the paid newsletter plan
180    if ( defined( 'WP_ADMIN' ) && WP_ADMIN && Jetpack_Memberships::has_configured_plans_jetpack_recurring_payments( 'newsletter' ) ) {
181        add_action( 'manage_post_posts_columns', __NAMESPACE__ . '\register_newsletter_access_column' );
182        add_action( 'manage_post_posts_custom_column', __NAMESPACE__ . '\render_newsletter_access_rows', 10, 2 );
183        add_action( 'admin_head', __NAMESPACE__ . '\newsletter_access_column_styles' );
184    }
185
186    add_action( 'init', __NAMESPACE__ . '\maybe_prevent_super_cache_caching' );
187
188    add_action( 'wp_after_insert_post', __NAMESPACE__ . '\add_paywalled_content_post_meta', 99, 2 );
189
190    add_filter( 'rest_pre_insert_post', __NAMESPACE__ . '\reject_unknown_access_level', 10, 2 );
191
192    add_filter(
193        'jetpack_options_whitelist',
194        function ( $options ) {
195            $options[] = 'jetpack_subscriptions_subscribe_post_end_enabled';
196            $options[] = 'jetpack_subscriptions_subscribe_navigation_enabled';
197
198            return $options;
199        }
200    );
201
202    // If called via REST API, we need to register later in the lifecycle
203    if ( ( new Host() )->is_wpcom_platform() && ! Request::is_frontend() ) {
204        add_action(
205            'restapi_theme_init',
206            function () {
207                Jetpack_Subscription_Site::init()->handle_subscribe_block_placements();
208            }
209        );
210    } else {
211        Jetpack_Subscription_Site::init()->handle_subscribe_block_placements();
212    }
213}
214add_action( 'init', __NAMESPACE__ . '\register_block', 9 );
215
216/**
217 * Returns true when in a WP.com environment.
218 *
219 * @return boolean
220 */
221function is_wpcom() {
222    return defined( 'IS_WPCOM' ) && IS_WPCOM;
223}
224
225/**
226 * Rejects a REST request that sets an unknown access level, before the post is saved.
227 *
228 * The access check treats an unknown value as "everybody", so a typo like "paid"
229 * would publish a paid post to everyone. This runs here rather than as a schema
230 * enum because core checks meta against the schema only after the post is saved.
231 *
232 * @param \stdClass|\WP_Error $prepared_post Post about to be saved.
233 * @param \WP_REST_Request    $request       Request being handled.
234 * @return \stdClass|\WP_Error
235 */
236function reject_unknown_access_level( $prepared_post, $request ) {
237    // A missing value is left alone, and null deletes the meta, which core handles.
238    if (
239        is_wp_error( $prepared_post )
240        || ! $request instanceof \WP_REST_Request
241        || ! isset( $request['meta'][ META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS ] )
242    ) {
243        return $prepared_post;
244    }
245
246    // Autosaves never store this meta, and the autosaves controller doesn't check
247    // for an error here, so rejecting would drop the author's autosaved edits.
248    if ( str_ends_with( strtolower( $request->get_route() ), '/autosaves' ) ) {
249        return $prepared_post;
250    }
251
252    // PAID_SUBSCRIBERS_ALL_TIERS is left out: no editor or API client sets it, and
253    // WordPress.com would email such a post to every subscriber.
254    $allowed      = array(
255        Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY,
256        Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS,
257        Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS,
258    );
259    $access_level = $request['meta'][ META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS ];
260    // REST returns '' for a post with no value set and the editor sends it back on
261    // save; the access check reads '' as "everybody".
262    if ( '' === $access_level || in_array( $access_level, $allowed, true ) ) {
263        return $prepared_post;
264    }
265
266    // The editor sends every meta value back on save, so a post that already holds an
267    // unknown value must still save as long as the request doesn't change it.
268    if ( ! empty( $prepared_post->ID ) && get_post_meta( $prepared_post->ID, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true ) === $access_level ) {
269        return $prepared_post;
270    }
271
272    return new \WP_Error(
273        'rest_invalid_param',
274        sprintf(
275            /* translators: 1: meta key, 2: comma-separated list of allowed values. Both are displayed in English. */
276            __( '%1$s must be one of %2$s.', 'jetpack' ),
277            META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS,
278            '"' . implode( '", "', $allowed ) . '"'
279        ),
280        array( 'status' => 400 )
281    );
282}
283
284/**
285 * Adds a 'Newsletter' column after the 'Title' column in the post list
286 *
287 * @param array $columns An array of column names.
288 * @return array An array of column names.
289 */
290function register_newsletter_access_column( $columns ) {
291    $position   = array_search( 'title', array_keys( $columns ), true );
292    $new_column = array( NEWSLETTER_COLUMN_ID => __( 'Newsletter', 'jetpack' ) );
293    return array_merge(
294        array_slice( $columns, 0, $position + 1, true ),
295        $new_column,
296        array_slice( $columns, $position, null, true )
297    );
298}
299
300/**
301 * Add a meta to prevent publication on firehose, ES AI or Reader
302 *
303 * @param int      $post_id Post id being saved.
304 * @param \WP_Post $post Post being saved.
305 * @return void
306 */
307function add_paywalled_content_post_meta( int $post_id, \WP_Post $post ) {
308    if ( $post->post_type !== 'post' ) {
309        return;
310    }
311
312    $access_level = get_post_meta( $post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true );
313
314    // A Paywall block gates the post whatever the stored access level; see Jetpack_Memberships::get_post_access_level().
315    $is_paywalled = has_block( \Automattic\Jetpack\Extensions\Paywall\BLOCK_NAME, $post );
316    switch ( $access_level ) {
317        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS_ALL_TIERS:
318        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS:
319        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS:
320            $is_paywalled = true;
321    }
322    if ( $is_paywalled ) {
323        update_post_meta( $post_id, META_NAME_CONTAINS_PAYWALLED_CONTENT, $is_paywalled );
324    }
325    if ( ! $is_paywalled ) {
326        delete_post_meta( $post_id, META_NAME_CONTAINS_PAYWALLED_CONTENT );
327    }
328}
329
330/**
331 * Displays the newsletter access level.
332 *
333 * @param string $column_id The ID of the column to display.
334 * @param int    $post_id The current post ID.
335 */
336function render_newsletter_access_rows( $column_id, $post_id ) {
337    if ( NEWSLETTER_COLUMN_ID !== $column_id ) {
338        return;
339    }
340
341    $access_level = Jetpack_Memberships::get_post_access_level( $post_id );
342
343    switch ( $access_level ) {
344        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS_ALL_TIERS:
345            echo esc_html__( 'Paid Subscribers (all plans)', 'jetpack' );
346            break;
347        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS:
348            echo esc_html__( 'Paid Subscribers', 'jetpack' );
349            break;
350        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS:
351            echo esc_html__( 'Subscribers', 'jetpack' );
352            break;
353        case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY:
354            echo esc_html__( 'Everybody', 'jetpack' );
355            break;
356        default:
357            echo '';
358    }
359}
360
361/**
362 * Adds the Newsletter column styles
363 */
364function newsletter_access_column_styles() {
365    echo '<style id="jetpack-newsletter-newsletter-access-column"> table.fixed .column-newsletter_access { width: 10%; } </style>';
366}
367
368/**
369 * Determine the amount of folks currently subscribed to the blog, splitted out in total_subscribers, email_subscribers, social_followers & paid_subscribers.
370 *
371 * @return array containing ['value' => ['total_subscribers' => 0, 'email_subscribers' => 0, 'paid_subscribers' => 0, 'social_followers' => 0]]
372 */
373function fetch_subscriber_counts() {
374    $subs_count = 0;
375    if ( is_wpcom() ) {
376        $subs_count = array(
377            'value' => \wpcom_fetch_subs_counts( true ),
378        );
379    } else {
380        $cache_key  = 'wpcom_subscribers_totals';
381        $subs_count = get_transient( $cache_key );
382        if ( false === $subs_count || 'failed' === $subs_count['status'] ) {
383            $xml = new \Jetpack_IXR_Client();
384            $xml->query( 'jetpack.fetchSubscriberCounts' );
385
386            if ( $xml->isError() ) { // If we get an error from .com, set the status to failed so that we will try again next time the data is requested.
387                $subs_count = array(
388                    'status'  => 'failed',
389                    'code'    => $xml->getErrorCode(),
390                    'message' => $xml->getErrorMessage(),
391                    'value'   => $subs_count['value'] ?? array(
392                        'total_subscribers' => 0,
393                        'email_subscribers' => 0,
394                        'social_followers'  => 0,
395                        'paid_subscribers'  => 0,
396                    ),
397                );
398            } else {
399                $subs_count = array(
400                    'status' => 'success',
401                    'value'  => $xml->getResponse(),
402                );
403            }
404            set_transient( $cache_key, $subs_count, 3600 ); // Try to cache the result for at least 1 hour.
405        }
406    }
407    return $subs_count;
408}
409
410/**
411 * Returns subscriber count based on include_social_followers attribute
412 *
413 * @param bool $include_social_followers Whether to include social followers in the count.
414 * @return int
415 */
416function get_subscriber_count( $include_social_followers ) {
417    $counts = fetch_subscriber_counts();
418
419    if ( $include_social_followers ) {
420        $subscriber_count = $counts['value']['total_subscribers'] + $counts['value']['social_followers'];
421    } else {
422        $subscriber_count = $counts['value']['total_subscribers'];
423    }
424    return $subscriber_count;
425}
426
427/**
428 * Returns true if the block attributes contain a value for the given key.
429 *
430 * @param array  $attributes Array containing the block attributes.
431 * @param string $key        Block attribute key.
432 *
433 * @return boolean
434 */
435function has_attribute( $attributes, $key ) {
436    return isset( $attributes[ $key ] ) && $attributes[ $key ] !== 'undefined';
437}
438
439/**
440 * Returns the value for the given attribute key, with the option of providing a default fallback value.
441 *
442 * @param array  $attributes Array containing the block attributes.
443 * @param string $key        Block attribute key.
444 * @param mixed  $default    Optional fallback value in case the key doesn't exist.
445 *
446 * @return mixed
447 */
448function get_attribute( $attributes, $key, $default = null ) {
449    return has_attribute( $attributes, $key ) ? $attributes[ $key ] : $default;
450}
451
452/**
453 * Mimics getColorClassName, getFontSizeClass and getGradientClass from @wordpress/block-editor js package.
454 *
455 * @param string $setting Setting name.
456 * @param string $value   Setting value.
457 *
458 * @return string
459 */
460function get_setting_class_name( $setting, $value ) {
461    if ( ! $setting || ! $value ) {
462        return '';
463    }
464
465    return sprintf( 'has-%s-%s', $value, $setting );
466}
467
468/**
469 * Uses block attributes to generate an array containing the classes for various block elements.
470 * Based on Jetpack_Subscriptions_Widget::do_subscription_form() which the block was originally using.
471 *
472 * @param array $attributes Array containing the block attributes.
473 *
474 * @return array
475 */
476function get_element_class_names_from_attributes( $attributes ) {
477    $text_color_class = get_setting_class_name( 'color', get_attribute( $attributes, 'textColor' ) );
478    $font_size_class  = get_setting_class_name( 'font-size', get_attribute( $attributes, 'fontSize' ) );
479    $border_class     = get_setting_class_name( 'border-color', get_attribute( $attributes, 'borderColor' ) );
480
481    $button_background_class = get_setting_class_name( 'background-color', get_attribute( $attributes, 'buttonBackgroundColor' ) );
482    $button_gradient_class   = get_setting_class_name( 'gradient-background', get_attribute( $attributes, 'buttonGradient' ) );
483
484    $email_field_background_class = get_setting_class_name( 'background-color', get_attribute( $attributes, 'emailFieldBackgroundColor' ) );
485    $email_field_gradient_class   = get_setting_class_name( 'gradient-background', get_attribute( $attributes, 'emailFieldGradient' ) );
486
487    $submit_button_classes = array_filter(
488        array(
489            'wp-block-button__link'  => true,
490            'no-border-radius'       => 0 === get_attribute( $attributes, 'borderRadius', 0 ),
491            $font_size_class         => true,
492            $border_class            => true,
493            'has-text-color'         => ! empty( $text_color_class ),
494            $text_color_class        => true,
495            'has-background'         => ! empty( $button_background_class ) || ! empty( $button_gradient_class ),
496            $button_background_class => ! empty( $button_background_class ),
497            $button_gradient_class   => ! empty( $button_gradient_class ),
498        )
499    );
500
501    $email_field_classes = array_filter(
502        array(
503            'no-border-radius'            => 0 === get_attribute( $attributes, 'borderRadius', 0 ),
504            $font_size_class              => true,
505            $border_class                 => true,
506            $email_field_background_class => true,
507            $email_field_gradient_class   => true,
508        )
509    );
510
511    $block_wrapper_classes = array_filter(
512        array(
513            'wp-block-jetpack-subscriptions__supports-newline' => true,
514            'wp-block-jetpack-subscriptions__use-newline' => (bool) get_attribute( $attributes, 'buttonOnNewLine' ),
515            'wp-block-jetpack-subscriptions__show-subs'   => (bool) get_attribute( $attributes, 'showSubscribersTotal' ),
516        )
517    );
518
519    return array(
520        'block_wrapper' => implode( ' ', array_keys( $block_wrapper_classes ) ),
521        'email_field'   => implode( ' ', array_keys( $email_field_classes ) ),
522        'submit_button' => implode( ' ', array_keys( $submit_button_classes ) ),
523    );
524}
525
526/**
527 * Checks if block style is "button only"
528 *
529 * @param string $class_name Block attribute className; multiple names are spearated by space.
530 *
531 * @return bool
532 */
533function is_button_only_style( $class_name ) {
534    if ( empty( $class_name ) ) {
535        return false;
536    }
537
538    $class_names = explode( ' ', $class_name );
539    return in_array( 'is-style-button', $class_names, true );
540}
541
542/**
543 * Uses block attributes to generate an array containing the styles for various block elements.
544 * Based on Jetpack_Subscriptions_Widget::do_subscription_form() which the block was originally using.
545 *
546 * @param array $attributes Array containing the block attributes.
547 *
548 * @return array
549 */
550function get_element_styles_from_attributes( $attributes ) {
551    $is_button_only_style = is_button_only_style( get_attribute( $attributes, 'className', '' ) );
552
553    $button_background_style = ! has_attribute( $attributes, 'buttonBackgroundColor' ) && has_attribute( $attributes, 'customButtonGradient' )
554        ? get_attribute( $attributes, 'customButtonGradient' )
555        : get_attribute( $attributes, 'customButtonBackgroundColor' );
556
557    $email_field_styles           = '';
558    $submit_button_wrapper_styles = '';
559    $submit_button_styles         = '';
560
561    if ( ! empty( $button_background_style ) ) {
562        $submit_button_styles .= sprintf( 'background: %s;', $button_background_style );
563    }
564
565    if ( has_attribute( $attributes, 'customTextColor' ) ) {
566        $submit_button_styles .= sprintf( 'color: %s;', get_attribute( $attributes, 'customTextColor' ) );
567    }
568
569    if ( has_attribute( $attributes, 'buttonWidth' ) ) {
570        $submit_button_wrapper_styles .= sprintf( 'width: %s;', get_attribute( $attributes, 'buttonWidth', DEFAULT_BUTTON_WIDTH ) );
571        $submit_button_wrapper_styles .= 'max-width: 100%;';
572
573        // Account for custom margins on inline forms.
574        $submit_button_styles .= true === get_attribute( $attributes, 'buttonOnNewLine' )
575            ? 'width: 100%;'
576            : sprintf( 'width: calc(100%% - %dpx);', get_attribute( $attributes, 'spacing', DEFAULT_SPACING_VALUE ) );
577    }
578
579    $font_size = get_attribute( $attributes, 'customFontSize', DEFAULT_FONTSIZE_VALUE );
580    $style     = sprintf( 'font-size: %s%s;', $font_size, is_numeric( $font_size ) ? 'px' : '' );
581
582    $submit_button_styles .= $style;
583    $email_field_styles   .= $style;
584
585    $padding = get_attribute( $attributes, 'padding', DEFAULT_PADDING_VALUE );
586    $style   = sprintf( 'padding: %1$dpx %2$dpx %1$dpx %2$dpx;', $padding, round( $padding * 1.5 ) );
587
588    $submit_button_styles .= $style;
589    $email_field_styles   .= $style;
590
591    if ( ! $is_button_only_style ) {
592        $button_spacing = get_attribute( $attributes, 'spacing', DEFAULT_SPACING_VALUE );
593        if ( true === get_attribute( $attributes, 'buttonOnNewLine' ) ) {
594            $submit_button_styles .= sprintf( 'margin-top: %dpx;', $button_spacing );
595        } else {
596            $submit_button_styles .= 'margin: 0; '; // Reset Safari's 2px default margin for buttons affecting input and button union
597            $submit_button_styles .= sprintf( 'margin-left: %dpx;', $button_spacing );
598        }
599    }
600
601    if ( has_attribute( $attributes, 'borderColor' ) ) {
602        $style                 = sprintf( 'border-color: %s;', get_attribute( $attributes, 'borderColor', '' ) );
603        $submit_button_styles .= $style;
604        $email_field_styles   .= $style;
605    }
606
607    $style                 = sprintf( 'border-radius: %dpx;', get_attribute( $attributes, 'borderRadius', DEFAULT_BORDER_RADIUS_VALUE ) );
608    $submit_button_styles .= $style;
609    $email_field_styles   .= $style;
610
611    $style                 = sprintf( 'border-width: %dpx;', get_attribute( $attributes, 'borderWeight', DEFAULT_BORDER_WEIGHT_VALUE ) );
612    $submit_button_styles .= $style;
613    $email_field_styles   .= $style;
614
615    if ( has_attribute( $attributes, 'customBorderColor' ) ) {
616        $style = sprintf( 'border-color: %s; border-style: solid;', get_attribute( $attributes, 'customBorderColor' ) );
617
618        $submit_button_styles .= $style;
619        $email_field_styles   .= $style;
620    }
621
622    if ( ! Request::is_frontend() ) {
623        $background_color_style = get_attribute_color( 'buttonBackgroundColor', $attributes, '#113AF5' /* default lettre theme color */ );
624        $text_color_style       = get_attribute_color( 'textColor', $attributes, '#FFFFFF' );
625        $submit_button_styles  .= sprintf( ' background-color: %s; color: %s;', $background_color_style, $text_color_style );
626    }
627
628    return array(
629        'email_field'           => $email_field_styles,
630        'submit_button'         => $submit_button_styles,
631        'submit_button_wrapper' => $submit_button_wrapper_styles,
632    );
633}
634
635/**
636 * Retrieve the resolved color for a given attribute.
637 *
638 * @param string $attribute_name The name of the attribute to resolve.
639 * @param array  $attributes     An array of all attributes.
640 * @param string $default_color  A fallback color in case no color can be resolved.
641 *
642 * @return string Returns the resolved color or the default color if no color is found.
643 */
644function get_attribute_color( $attribute_name, $attributes, $default_color ) {
645    if ( has_attribute( $attributes, $attribute_name ) ) {
646        $color_slug     = get_attribute( $attributes, $attribute_name );
647        $resolved_color = get_color_from_slug( $color_slug );
648
649        if ( $resolved_color ) {
650            return $resolved_color;
651        }
652    }
653
654    return get_global_style_color( $attribute_name, $default_color );
655}
656
657/**
658 * Retrieve the global style color based on a provided style key.
659 *
660 * @param string $style_key     The key for the desired style.
661 * @param string $default_color A fallback color in case the global style is not set.
662 *
663 * @return string Returns the color defined in global styles or the default color if not defined.
664 */
665function get_global_style_color( $style_key, $default_color ) {
666    $global_styles = wp_get_global_styles(
667        array( 'color' ),
668        array(
669            'block_name' => 'core/button',
670            'transforms' => array( 'resolve-variables' ),
671        )
672    );
673
674    if ( isset( $global_styles[ $style_key ] ) ) {
675        return $global_styles[ $style_key ];
676    }
677
678    return $default_color;
679}
680
681/**
682 * Convert a color slug into its corresponding color value.
683 *
684 * @param string $slug The slug representation of the color.
685 *
686 * @return string|null Returns the color value if found, or null otherwise.
687 */
688function get_color_from_slug( $slug ) {
689    $color_palettes = wp_get_global_settings( array( 'color', 'palette' ) );
690
691    if ( ! is_array( $color_palettes ) ) {
692        return null;
693    }
694
695    foreach ( $color_palettes as $palette ) {
696        if ( is_array( $palette ) ) {
697            foreach ( $palette as $color ) {
698                if ( isset( $color['slug'] ) && $color['slug'] === $slug && isset( $color['color'] ) ) {
699                    return $color['color'];
700                }
701            }
702        }
703    }
704
705    return null;
706}
707
708/**
709 * Is the Jetpack_Memberships class loaded.
710 */
711function is_jetpack_memberships_loaded(): bool {
712    return class_exists( '\Jetpack_Memberships' );
713}
714
715/**
716 * Subscriptions block render callback.
717 *
718 * @param array $attributes Array containing the block attributes.
719 *
720 * @return string
721 */
722function render_block( $attributes ) {
723    // If the Subscriptions module is not active, don't render the block.
724    if ( ! ( new Modules() )->is_active( 'subscriptions' ) ) {
725        return '';
726    }
727
728    if ( is_jetpack_memberships_loaded() ) {
729        // We only want the sites that have newsletter feature enabled to be graced by this JavaScript.
730        Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
731    } else {
732        Jetpack_Gutenberg::load_styles_as_required( FEATURE_NAME );
733    }
734
735    if ( ! class_exists( 'Jetpack_Subscriptions_Widget' ) ) {
736        return '';
737    }
738
739    // Prefill the email field with the current user's email if they are logged in via Memberships premium content token
740    $subscribe_email = Jetpack_Memberships::get_current_user_email();
741
742    // If no email, then prefill the email field with the current user's email if they are logged in
743    if ( empty( $subscribe_email ) ) {
744        $current_user = wp_get_current_user();
745        if ( ! empty( $current_user->user_email ) ) {
746            $subscribe_email = $current_user->user_email;
747        }
748    }
749
750    // The block is using the Jetpack_Subscriptions_Widget backend, hence the need to increase the instance count.
751    ++Jetpack_Subscriptions_Widget::$instance_count;
752
753    $classes = get_element_class_names_from_attributes( $attributes );
754    $styles  = get_element_styles_from_attributes( $attributes );
755
756    // The default value was previously "true" in block.json. We don't want to rely setting "default" in block.json to falsy,
757    // because it would change the setting for previously saved blocks. Block editor doesn't store default values in attributes at all.
758    // Hence users without this set will still get social counts included in the subscriber counter.
759    // Lowering the subscriber count on their behalf with code change would be controversial.
760    // We want to disencourage including social count as it's misleading.
761    $include_social_followers = isset( $attributes['includeSocialFollowers'] ) ? (bool) get_attribute( $attributes, 'includeSocialFollowers' ) : true;
762
763    $data = array(
764        'widget_id'                         => Jetpack_Subscriptions_Widget::$instance_count,
765        'subscribe_email'                   => $subscribe_email,
766        'is_paid_subscriber'                => get_attribute( $attributes, 'isPaidSubscriber', false ),
767        'wrapper_attributes'                => get_block_wrapper_attributes(
768            array(
769                'class' => $classes['block_wrapper'],
770            )
771        ),
772        'subscribe_placeholder'             => get_attribute( $attributes, 'subscribePlaceholder', __( 'Type your email…', 'jetpack' ) ),
773        'submit_button_text'                => get_attribute( $attributes, 'submitButtonText', __( 'Subscribe', 'jetpack' ) ),
774        'submit_button_text_subscribed'     => get_attribute( $attributes, 'submitButtonTextSubscribed', __( 'Subscribed', 'jetpack' ) ),
775        'submit_button_text_upgrade'        => get_attribute( $attributes, 'submitButtonTextUpgrade', __( 'Upgrade subscription', 'jetpack' ) ),
776        'success_message'                   => get_attribute(
777            $attributes,
778            'successMessage',
779            esc_html__( "Success! An email was just sent to confirm your subscription. Please find the email now and click 'Confirm' to start subscribing.", 'jetpack' )
780        ),
781        'show_subscribers_total'            => (bool) get_attribute( $attributes, 'showSubscribersTotal' ),
782        'subscribers_total'                 => get_attribute( $attributes, 'showSubscribersTotal' ) ? get_subscriber_count( $include_social_followers ) : 0,
783        'referer'                           => esc_url_raw(
784            ( is_ssl() ? 'https' : 'http' ) . '://' . ( isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '' ) .
785            ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' )
786        ),
787        'source'                            => 'subscribe-block',
788        'app_source'                        => get_attribute( $attributes, 'appSource', null ),
789        'class_name'                        => get_attribute( $attributes, 'className' ),
790        'selected_newsletter_categories'    => get_attribute( $attributes, 'selectedNewsletterCategoryIds', array() ),
791        'preselected_newsletter_categories' => get_attribute( $attributes, 'preselectNewsletterCategories', false ),
792    );
793
794    // Only render the email version in non-frontend contexts.
795    if ( is_feed() || wp_is_xml_request() ||
796        ( defined( 'REST_REQUEST' ) && REST_REQUEST && ! wp_is_json_request() ) ||
797        ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) ||
798        ( defined( 'WP_CLI' ) && WP_CLI ) ||
799        wp_is_jsonp_request() ) {
800        return render_for_email( $data, $styles );
801    }
802
803    return render_for_website( $data, $classes, $styles );
804}
805
806/**
807 *  Get the post access level for the current post. Defaults to 'everybody' if the query is not for a single post
808 *
809 * @return string the actual post access level (see projects/plugins/jetpack/extensions/blocks/subscriptions/constants.js for the values).
810 */
811function get_post_access_level_for_current_post() {
812    if ( ! is_singular() ) {
813        // There is no "actual" current post.
814        return Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY;
815    }
816
817    return Jetpack_Memberships::get_post_access_level();
818}
819
820/**
821 * Renders the subscriptions block at the site.
822 *
823 * @param array $data    Array containing block view data.
824 * @param array $classes Array containing the classes for different block elements.
825 * @param array $styles  Array containing the styles for different block elements.
826 *
827 * @return string
828 */
829function render_for_website( $data, $classes, $styles ) {
830    $lang                 = get_locale();
831    $blog_id              = \Jetpack_Options::get_option( 'id' );
832    $widget_id_suffix     = Jetpack_Subscriptions_Widget::$instance_count > 1 ? '-' . Jetpack_Subscriptions_Widget::$instance_count : '';
833    $form_id              = 'subscribe-blog' . $widget_id_suffix;
834    $form_url             = 'https://wordpress.com/email-subscriptions';
835    $post_access_level    = get_post_access_level_for_current_post();
836    $is_button_only_style = ! empty( $data['class_name'] ) ? is_button_only_style( $data['class_name'] ) : false;
837
838    // Post ID is used for pulling post-specific paid status, and returning to the right post after confirming subscription
839    $post_id = null;
840    if ( in_the_loop() ) {
841        $post_id = get_the_ID();
842    } elseif ( is_singular( 'post' ) || is_page() ) {
843        $post_id = get_queried_object_id();
844    } else {
845        $post_id = get_option( 'page_on_front' );
846    }
847
848    $subscribe_field_id    = apply_filters( 'subscribe_field_id', 'subscribe-field' . $widget_id_suffix, $data['widget_id'] );
849    $tier_id               = get_post_meta( $post_id, META_NAME_FOR_POST_TIER_ID_SETTINGS, true );
850    $is_subscribed         = Jetpack_Memberships::is_current_user_subscribed();
851    $button_text           = get_submit_button_text( $data );
852    $show_subscriber_count = $data['show_subscribers_total'] && $data['subscribers_total'] && ! $is_subscribed;
853
854    ob_start();
855
856    Jetpack_Subscriptions_Widget::render_widget_status_messages(
857        array(
858            'success_message' => $data['success_message'],
859        )
860    );
861    ?>
862    <div <?php echo wp_kses_data( $data['wrapper_attributes'] ); ?>>
863        <div class="wp-block-jetpack-subscriptions__container<?php echo ! $is_subscribed ? ' is-not-subscriber' : ''; ?>">
864            <?php if ( is_top_subscription() ) : ?>
865                <p id="subscribe-submit" class="is-link"
866                    <?php if ( ! empty( $styles['submit_button_wrapper'] ) ) : ?>
867                        style="<?php echo esc_attr( $styles['submit_button_wrapper'] ); ?>"
868                    <?php endif; ?>
869                >
870                        <a
871                            href="<?php echo esc_url( 'https://wordpress.com/reader/site/subscription/' . $blog_id ); ?>"
872                            <?php if ( ! empty( $classes['submit_button'] ) ) : ?>
873                                class="<?php echo esc_attr( $classes['submit_button'] ); ?>"
874                            <?php endif; ?>
875                            <?php if ( ! empty( $styles['submit_button'] ) ) : ?>
876                                style="<?php echo esc_attr( $styles['submit_button'] ); ?>"
877                            <?php endif; ?>
878                        >
879                            <?php echo sanitize_submit_text( $button_text ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
880                        </a>
881                </p>
882            <?php else : ?>
883                <form
884                    action="<?php echo esc_url( $form_url ); ?>"
885                    method="post"
886                    accept-charset="utf-8"
887                    data-blog="<?php echo esc_attr( $blog_id ); ?>"
888                    data-post_access_level="<?php echo esc_attr( $post_access_level ); ?>"
889                    data-subscriber_email="<?php echo esc_attr( $data['subscribe_email'] ); ?>"
890                    id="<?php echo esc_attr( $form_id ); ?>"
891                >
892                    <div class="wp-block-jetpack-subscriptions__form-elements">
893                        <?php if ( ! $is_subscribed && ! $is_button_only_style ) : ?>
894                        <p id="subscribe-email">
895                            <label
896                                id="<?php echo esc_attr( $subscribe_field_id . '-label' ); ?>"
897                                for="<?php echo esc_attr( $subscribe_field_id ); ?>"
898                                class="screen-reader-text"
899                            >
900                                <?php echo esc_html( $data['subscribe_placeholder'] ); ?>
901                            </label>
902                            <?php
903                            printf(
904                                '<input
905                                    required="required"
906                                    type="email"
907                                    name="email"
908                                    autocomplete="email"
909                                    %1$s
910                                    style="%2$s"
911                                    placeholder="%3$s"
912                                    value="%4$s"
913                                    id="%5$s"
914                                    %6$s
915                                />',
916                                ( ! empty( $classes['email_field'] )
917                                    ? 'class="' . esc_attr( $classes['email_field'] ) . '"'
918                                    : ''
919                                ),
920                                ( ! empty( $styles['email_field'] )
921                                    ? esc_attr( $styles['email_field'] )
922                                    : 'width: 95%; padding: 1px 10px'
923                                ),
924                                esc_attr( $data['subscribe_placeholder'] ),
925                                esc_attr( $data['subscribe_email'] ),
926                                esc_attr( $subscribe_field_id ),
927                                ( ! empty( $data['subscribe_email'] )
928                                    ? 'disabled title="' . esc_attr__( "You're logged in with this email", 'jetpack' ) . '"'
929                                    : 'title="' . esc_attr__( 'Please fill in this field.', 'jetpack' ) . '"'
930                                )
931                            );
932                            ?>
933                        </p>
934                        <?php endif; ?>
935                        <p id="subscribe-submit"
936                            <?php if ( ! empty( $styles['submit_button_wrapper'] ) ) : ?>
937                                style="<?php echo esc_attr( $styles['submit_button_wrapper'] ); ?>"
938                            <?php endif; ?>
939                        >
940                            <input type="hidden" name="action" value="subscribe"/>
941                            <input type="hidden" name="blog_id" value="<?php echo (int) $blog_id; ?>"/>
942                            <input type="hidden" name="source" value="<?php echo esc_url( $data['referer'] ); ?>"/>
943                            <input type="hidden" name="sub-type" value="<?php echo esc_attr( $data['source'] ); ?>"/>
944                            <input type="hidden" name="app_source" value="<?php echo esc_attr( $data['app_source'] ); ?>"/>
945                            <input type="hidden" name="redirect_fragment" value="<?php echo esc_attr( $form_id ); ?>"/>
946                            <input type="hidden" name="lang" value="<?php echo esc_attr( $lang ); ?>"/>
947                            <?php
948                            wp_nonce_field( 'blogsub_subscribe_' . $blog_id );
949
950                            if ( ! empty( $post_id ) ) {
951                                echo '<input type="hidden" name="post_id" value="' . esc_attr( $post_id ) . '"/>';
952                            }
953
954                            if ( ! empty( $tier_id ) ) {
955                                echo '<input type="hidden" name="tier_id" value="' . esc_attr( $tier_id ) . '"/>';
956                            }
957
958                            if ( $data['preselected_newsletter_categories'] && ! empty( $data['selected_newsletter_categories'] ) ) {
959                                echo '<input type="hidden" name="selected_newsletter_categories" value="' . esc_attr( implode( ',', $data['selected_newsletter_categories'] ) ) . '"/>';
960                            }
961                            ?>
962                            <button type="submit"
963                                <?php if ( ! empty( $classes['submit_button'] ) ) : ?>
964                                    class="<?php echo esc_attr( $classes['submit_button'] ); ?>"
965                                <?php endif; ?>
966                                <?php if ( ! empty( $styles['submit_button'] ) ) : ?>
967                                    style="<?php echo esc_attr( $styles['submit_button'] ); ?>"
968                                <?php endif; ?>
969                                name="jetpack_subscriptions_widget"
970                            >
971                                <?php echo sanitize_submit_text( $button_text ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
972                            </button>
973                        </p>
974                    </div>
975                </form>
976            <?php endif; ?>
977            <?php if ( $show_subscriber_count ) : ?>
978                <div class="wp-block-jetpack-subscriptions__subscount">
979                    <?php echo esc_html( Jetpack_Memberships::get_join_others_text( $data['subscribers_total'] ) ); ?>
980                </div>
981            <?php endif; ?>
982        </div>
983    </div>
984    <?php
985    return ob_get_clean();
986}
987
988/**
989 * Renders the email version of the subscriptions block.
990 *
991 * @param array $data    Array containing block view data.
992 * @param array $styles  Array containing the styles for different block elements.
993 *
994 * @return string
995 */
996function render_for_email( $data, $styles ) {
997    $submit_button_wrapper_style = ! empty( $styles['submit_button_wrapper'] ) ? 'style="' . esc_attr( $styles['submit_button_wrapper'] ) . '"' : '';
998    $button_text                 = get_submit_button_text( $data );
999
1000    $html = '<div ' . wp_kses_data( $data['wrapper_attributes'] ) . '>
1001        <div>
1002            <div>
1003                <div>
1004                    <p ' . $submit_button_wrapper_style . '>
1005                        <a href="' . esc_url( get_post_permalink() ) . '" style="' . esc_attr( $styles['submit_button'] ) . ' text-decoration: none; white-space: nowrap; margin-left: 0">' . sanitize_submit_text( $button_text ) . '</a>
1006                    </p>
1007                </div>
1008            </div>
1009        </div>
1010    </div>';
1011
1012    return $html;
1013}
1014
1015/**
1016 * WooCommerce Email Editor render callback for the subscriptions block.
1017 *
1018 * @param string $block_content The block content.
1019 * @param array  $parsed_block  The parsed block data.
1020 * @param object $rendering_context The email rendering context.
1021 *
1022 * @return string
1023 */
1024function render_email( $block_content, array $parsed_block, $rendering_context ) {
1025    if ( ! isset( $parsed_block['attrs'] ) || ! is_array( $parsed_block['attrs'] ) || ! function_exists( '\Automattic\Jetpack\Extensions\Button\render_email' ) || ! class_exists( '\Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks\Button' ) ) {
1026        return '';
1027    }
1028
1029    // Map subscription block attributes to button block attributes
1030    $button_attributes = array(
1031        'text'                  => ! empty( $parsed_block['attrs']['submitButtonText'] ) ? sanitize_text_field( $parsed_block['attrs']['submitButtonText'] ) : __( 'Subscribe', 'jetpack' ),
1032        'url'                   => get_post_permalink(),
1033        'element'               => 'a',
1034        // Map background colors
1035        'backgroundColor'       => $parsed_block['attrs']['buttonBackgroundColor'] ?? null,
1036        'customBackgroundColor' => $parsed_block['attrs']['customButtonBackgroundColor'] ?? null,
1037        // Map text colors
1038        'textColor'             => $parsed_block['attrs']['textColor'] ?? null,
1039        'customTextColor'       => $parsed_block['attrs']['customTextColor'] ?? null,
1040        // Map borders
1041        'borderRadius'          => $parsed_block['attrs']['borderRadius'] ?? 0,
1042        'borderWeight'          => $parsed_block['attrs']['borderWeight'] ?? 1,
1043        'borderColor'           => $parsed_block['attrs']['borderColor'] ?? null,
1044        'customBorderColor'     => $parsed_block['attrs']['customBorderColor'] ?? null,
1045        // Map typography
1046        'fontSize'              => $parsed_block['attrs']['fontSize'] ?? null,
1047        'customFontSize'        => $parsed_block['attrs']['customFontSize'] ?? null,
1048        // Map spacing
1049        'padding'               => $parsed_block['attrs']['padding'] ?? null,
1050    );
1051
1052    // Create a mock button block structure
1053    $button_parsed_block = array(
1054        'attrs'       => $button_attributes,
1055        'email_attrs' => $parsed_block['email_attrs'] ?? array(),
1056    );
1057
1058    // Call the Jetpack button's email rendering
1059    return \Automattic\Jetpack\Extensions\Button\render_email(
1060        $block_content,
1061        $button_parsed_block,
1062        $rendering_context
1063    );
1064}
1065
1066/**
1067 * Gate access to posts
1068 *
1069 * @param string $the_content Post content.
1070 *
1071 * @return string
1072 */
1073function add_paywall( $the_content ) {
1074    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1075
1076    $post_access_level = Jetpack_Memberships::get_post_access_level();
1077
1078    if ( Jetpack_Memberships::user_can_view_post() ) {
1079        if ( $post_access_level !== Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY ) {
1080            do_action(
1081                'earn_track_paywalled_post_view',
1082                array(
1083                    'post_id' => get_the_ID(),
1084                )
1085            );
1086        }
1087        return $the_content;
1088    }
1089
1090    $paywalled_content = get_paywall_content();
1091
1092    if ( has_block( \Automattic\Jetpack\Extensions\Paywall\BLOCK_NAME ) ) {
1093        if ( strpos( $the_content, \Automattic\Jetpack\Extensions\Paywall\BLOCK_HTML ) ) {
1094            return strstr( $the_content, \Automattic\Jetpack\Extensions\Paywall\BLOCK_HTML, true ) . $paywalled_content;
1095        }
1096        // WordPress generates excerpts by either rendering or stripping blocks before invoking the `the_content` filter.
1097        // In the context of generating an excerpt, the Paywall block specifically renders THE_EXCERPT_BLOCK.
1098        if ( strpos( $the_content, \Automattic\Jetpack\Extensions\Paywall\THE_EXCERPT_BLOCK ) ) {
1099            return strstr( $the_content, \Automattic\Jetpack\Extensions\Paywall\THE_EXCERPT_BLOCK, true );
1100        }
1101    }
1102
1103    return $paywalled_content;
1104}
1105
1106/**
1107 * Gate access to comments. We want to close comments on private sites.
1108 *
1109 * @param bool $default_comments_open Default state of the comments_open filter.
1110 * @param int  $post_id Current post id.
1111 *
1112 * @return bool
1113 */
1114function maybe_close_comments( $default_comments_open, $post_id ) {
1115    if ( ! $default_comments_open || ! $post_id ) {
1116        return $default_comments_open;
1117    }
1118
1119    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1120    return Jetpack_Memberships::user_can_view_post();
1121}
1122
1123/**
1124 * Gate access to existing comments
1125 *
1126 * @param string $comment The comment.
1127 *
1128 * @return string
1129 */
1130function maybe_gate_existing_comments( $comment ) {
1131    if ( empty( $comment ) ) {
1132        return $comment;
1133    }
1134
1135    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1136    if ( Jetpack_Memberships::user_can_view_post() ) {
1137        return $comment;
1138    }
1139    return '';
1140}
1141
1142/**
1143 * Is the Jetpack_Token_Subscription_Service class loaded
1144 *
1145 * @return bool
1146 */
1147function is_jetpack_token_subscription_service_loaded(): bool {
1148    return class_exists( 'Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Jetpack_Token_Subscription_Service' );
1149}
1150
1151/**
1152 * Adds support for WP Super cache and Boost cache
1153 */
1154function maybe_prevent_super_cache_caching() {
1155    // Prevents cached page to be served if the Membership cookie is present
1156    if ( is_jetpack_token_subscription_service_loaded() ) {
1157        do_action( 'wpsc_add_cookie', Jetpack_Token_Subscription_Service::JWT_AUTH_TOKEN_COOKIE_NAME );
1158    }
1159
1160    if ( is_user_auth() ) {
1161        // Do not cache the page if user is auth with Membership token
1162        if ( ! defined( 'DONOTCACHEPAGE' ) ) {
1163            define( 'DONOTCACHEPAGE', true );
1164        }
1165    }
1166}
1167
1168/**
1169 * Returns paywall content blocks
1170 *
1171 * @return string
1172 */
1173function get_paywall_content() {
1174    if ( Jetpack_Memberships::user_is_pending_subscriber() ) {
1175        return get_paywall_blocks_subscribe_pending();
1176    }
1177    if ( doing_filter( 'get_the_excerpt' ) ) {
1178        return '';
1179    }
1180    return get_paywall_blocks();
1181}
1182
1183/**
1184 * Returns the current URL.
1185 *
1186 * TODO: Copied from https://github.com/Automattic/jetpack/blob/bb885061dc3ee7a80a78a5f0116ab3fcebfddb09/projects/packages/boost-core/src/lib/class-url.php#L39
1187 * TODO: Move to a shared package
1188 *
1189 * @return string
1190 */
1191function get_current_url() {
1192    // Fallback to the site URL if we're unable to determine the URL from $_SERVER global.
1193    $current_url = site_url();
1194
1195    if ( isset( $_SERVER ) && is_array( $_SERVER ) ) {
1196        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization happens at the end
1197        $scheme = isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ? 'https' : 'http';
1198        $host   = ! empty( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : null;
1199        $path   = ! empty( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1200
1201        // Support for local plugin development and testing using ngrok.
1202        if ( ! empty( $_SERVER['HTTP_X_ORIGINAL_HOST'] ) && str_contains( $_SERVER['HTTP_X_ORIGINAL_HOST'], 'ngrok.io' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
1203            $host = wp_unslash( $_SERVER['HTTP_X_ORIGINAL_HOST'] );
1204        }
1205        // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1206
1207        if ( $host ) {
1208            $current_url = esc_url_raw( sprintf( '%s://%s%s', $scheme, $host, $path ) );
1209        }
1210    }
1211
1212    return $current_url;
1213}
1214
1215/**
1216 * Get the submit button text based on the subscription status.
1217 *
1218 * @param array $data Array containing block view data.
1219 *
1220 * @return string
1221 */
1222function get_submit_button_text( $data ) {
1223    if ( ! Jetpack_Memberships::is_current_user_subscribed() ) {
1224        return $data['submit_button_text'];
1225    }
1226    if ( ! Jetpack_Memberships::user_can_view_post() ) {
1227        return $data['submit_button_text_upgrade'];
1228    }
1229    return '✓ ' . $data['submit_button_text_subscribed'];
1230}
1231
1232/**
1233 * Returns true if there are no more tiers to upgrade to.
1234 *
1235 * @return boolean
1236 */
1237function is_top_subscription() {
1238    if ( ! Jetpack_Memberships::is_current_user_subscribed() ) {
1239        return false;
1240    }
1241    if ( ! Jetpack_Memberships::user_can_view_post() ) {
1242        return false;
1243    }
1244    return true;
1245}
1246
1247/**
1248 * Sanitize the submit button text.
1249 *
1250 * @param string $text String containing the submit button text.
1251 *
1252 * @return string
1253 */
1254function sanitize_submit_text( $text ) {
1255    return wp_kses(
1256        html_entity_decode( $text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
1257        Jetpack_Subscriptions_Widget::$allowed_html_tags_for_submit_button
1258    );
1259}
1260
1261/**
1262 * Returns paywall content blocks if user is not authenticated
1263 *
1264 * @return string
1265 */
1266function get_paywall_blocks() {
1267    $custom_paywall = apply_filters( 'jetpack_custom_paywall_blocks', false );
1268    if ( ! empty( $custom_paywall ) ) {
1269        return $custom_paywall;
1270    }
1271
1272    if ( ! Request::is_frontend() ) { // emails
1273        return get_paywall_simple();
1274    }
1275
1276    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1277    $is_paid_post       = is_paid_post();
1278    $is_paid_subscriber = Jetpack_Memberships::user_is_paid_subscriber();
1279
1280    $access_heading = $is_paid_subscriber
1281        ? esc_html__( 'Upgrade to continue reading', 'jetpack' )
1282        : esc_html__( 'Subscribe to continue reading', 'jetpack' );
1283
1284    $subscribe_text = $is_paid_post
1285        // translators: %s is the name of the site.
1286        ? (
1287            $is_paid_subscriber
1288                ? esc_html__( 'Upgrade to get access to the rest of this post and other exclusive content.', 'jetpack' )
1289                : esc_html__( 'Become a paid subscriber to get access to the rest of this post and other exclusive content.', 'jetpack' )
1290        )
1291        // translators: %s is the name of the site.
1292        : esc_html__( 'Subscribe to get access to the rest of this post and other subscriber-only content.', 'jetpack' );
1293
1294    $login_block = '';
1295
1296    if ( is_user_auth() ) {
1297        if ( ( new Host() )->is_wpcom_simple() ) {
1298            // We cannot use wpcom_logmein_redirect_url since it returns redirect URL when user is already logged in.
1299            $login_link           = add_query_arg(
1300                array(
1301                    'redirect_to' => rawurlencode( get_current_url() ),
1302                    'blog_id'     => get_current_blog_id(),
1303                ),
1304                'https://wordpress.com/log-in/link'
1305            );
1306            $switch_accounts_link = wp_logout_url( $login_link );
1307            $login_block          = '<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"}}} -->
1308<p class="has-text-align-center" style="font-size:14px">
1309    <a href="' . $switch_accounts_link . '">' . __( 'Switch accounts', 'jetpack' ) . '</a>
1310</p>
1311<!-- /wp:paragraph -->';
1312        }
1313    } else {
1314        $access_question = $is_paid_post ? esc_html__( 'Already a paid subscriber?', 'jetpack' ) : esc_html__( 'Already a subscriber?', 'jetpack' );
1315        $login_block     = '<!-- wp:group {"style":{"typography":{"fontSize":"14px"}},"layout":{"type":"flex","justifyContent":"center"}} -->
1316<div class="wp-block-group" style="font-size:14px">
1317    <!-- wp:jetpack/subscriber-login {"logInLabel":"' . $access_question . '"} /-->
1318</div>
1319<!-- /wp:group -->';
1320    }
1321
1322    $lock_svg = plugins_url( 'images/lock-paywall.svg', JETPACK__PLUGIN_FILE );
1323
1324    return '
1325<!-- wp:group {"style":{"border":{"width":"1px","radius":"4px"},"spacing":{"padding":{"top":"32px","bottom":"32px","left":"32px","right":"32px"}}},"borderColor":"primary","className":"jetpack-subscribe-paywall","layout":{"type":"constrained","contentSize":"400px"}} -->
1326<div class="wp-block-group jetpack-subscribe-paywall has-border-color has-primary-border-color" style="border-width:1px;border-radius:4px;padding-top:32px;padding-right:32px;padding-bottom:32px;padding-left:32px">
1327<!-- wp:image {"align":"center","width":24,"height":24,"sizeSlug":"large","linkDestination":"none"} -->
1328<figure class="wp-block-image aligncenter size-large is-resized"><img src="' . $lock_svg . '" alt="" width="24" height="24"/></figure>
1329<!-- /wp:image -->
1330
1331<!-- wp:heading {"textAlign":"center","style":{"typography":{"fontStyle":"normal","fontWeight":"600","fontSize":"24px"},"layout":{"selfStretch":"fit"}}} -->
1332<h2 class="wp-block-heading has-text-align-center" style="font-size:24px;font-style:normal;font-weight:600">' . $access_heading . '</h2>
1333<!-- /wp:heading -->
1334
1335<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"},"spacing":{"margin":{"top":"10px","bottom":"10px"}}}} -->
1336<p class="has-text-align-center" style="margin-top:10px;margin-bottom:10px;font-size:14px">' . $subscribe_text . '</p>
1337<!-- /wp:paragraph -->
1338
1339<!-- wp:jetpack/subscriptions {"borderRadius":50,"borderColor":"primary","className":"is-style-compact","isPaidSubscriber":' . ( $is_paid_subscriber ? 'true' : 'false' ) . '} /-->
1340' . $login_block . '
1341</div>
1342<!-- /wp:group -->
1343';
1344}
1345
1346/**
1347 * Returns true if user is auth for subscriptions check, otherwise returns false.
1348 *
1349 * @return bool
1350 */
1351function is_user_auth(): bool {
1352    if ( ( new Host() )->is_wpcom_simple() && is_user_logged_in() ) {
1353        return true;
1354    }
1355    if ( current_user_can( 'manage_options' ) ) {
1356        return true;
1357    }
1358
1359    if ( is_jetpack_token_subscription_service_loaded() ) {
1360        if ( Jetpack_Token_Subscription_Service::has_token_from_cookie() ) {
1361            return true;
1362        }
1363    }
1364    return false;
1365}
1366
1367/**
1368 * Returns `true` if the post is a paid post.
1369 */
1370function is_paid_post(): bool {
1371    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1372
1373    // Make sure Stripe is connected and the post is marked for paid subscribers.
1374    if ( Jetpack_Memberships::has_connected_account() && is_jetpack_token_subscription_service_loaded() ) {
1375        return Jetpack_Memberships::get_post_access_level() === Jetpack_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS;
1376    }
1377
1378    return false;
1379}
1380
1381/**
1382 * Returns true if the post is a subscribers post.
1383 */
1384function is_subscribers_post(): bool {
1385    require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1386
1387    // Make sure Stripe is connected and the post is marked for paid subscribers.
1388    if ( Jetpack_Memberships::has_connected_account() && is_jetpack_token_subscription_service_loaded() ) {
1389        return Jetpack_Memberships::get_post_access_level() === Jetpack_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS;
1390    }
1391
1392    return false;
1393}
1394
1395/**
1396 * Returns paywall content blocks when email confirmation is pending
1397 *
1398 * @return string
1399 */
1400function get_paywall_blocks_subscribe_pending() {
1401    $subscribe_email = Jetpack_Memberships::get_current_user_email();
1402
1403    /** This filter is documented in \Automattic\Jetpack\Forms\ContactForm\Contact_Form */
1404    if ( is_wpcom() || false !== apply_filters( 'jetpack_auto_fill_logged_in_user', false ) ) {
1405        $current_user = wp_get_current_user();
1406        if ( ! empty( $current_user->user_email ) ) {
1407            $subscribe_email = $current_user->user_email;
1408        }
1409    }
1410
1411    $access_heading = esc_html__( 'Confirm your subscription to continue reading', 'jetpack' );
1412
1413    /* translators: %s: email address */
1414    $subscribe_text = sprintf( esc_html__( 'Head to your inbox and confirm your email address %s.', 'jetpack' ), $subscribe_email );
1415
1416    $lock_svg = plugins_url( 'images/lock-paywall.svg', JETPACK__PLUGIN_FILE );
1417
1418    return '
1419<!-- wp:group {"style":{"border":{"width":"1px","radius":"4px"},"spacing":{"padding":{"top":"32px","bottom":"32px","left":"32px","right":"32px"}}},"borderColor":"primary","className":"jetpack-subscribe-paywall","layout":{"type":"constrained","contentSize":"400px"}} -->
1420<div class="wp-block-group jetpack-subscribe-paywall has-border-color has-primary-border-color" style="border-width:1px;border-radius:4px;padding-top:32px;padding-right:32px;padding-bottom:32px;padding-left:32px">
1421<!-- wp:image {"align":"center","width":24,"height":24,"sizeSlug":"large","linkDestination":"none"} -->
1422<figure class="wp-block-image aligncenter size-large is-resized"><img src="' . $lock_svg . '" alt="" width="24" height="24"/></figure>
1423<!-- /wp:image -->
1424
1425<!-- wp:heading {"textAlign":"center","style":{"typography":{"fontStyle":"normal","fontWeight":"600","fontSize":"24px", "maxWidth":"initial"},"layout":{"selfStretch":"fit"}}} -->
1426<h2 class="wp-block-heading has-text-align-center" style="font-size:24px;font-style:normal;font-weight:600;max-width:initial">' . $access_heading . '</h2>
1427<!-- /wp:heading -->
1428
1429<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"},"spacing":{"margin":{"top":"10px","bottom":"10px"}}}} -->
1430<p class="has-text-align-center" style="margin-top:10px;margin-bottom:10px;font-size:14px">' . $subscribe_text . '</p>
1431<!-- /wp:paragraph -->
1432</div>
1433<!-- /wp:group -->
1434';
1435}
1436
1437/**
1438 * Return content for non frontend views like Reader, emails.
1439 */
1440function get_paywall_simple(): string {
1441    $is_paid_post        = is_paid_post();
1442    $is_subscribers_post = is_subscribers_post();
1443    $is_subscriber       = is_jetpack_memberships_loaded() && Jetpack_Memberships::is_current_user_subscribed();
1444    $paywall_heading     = esc_html__( 'Subscribe to keep reading', 'jetpack' );
1445
1446    if ( $is_subscribers_post && ! $is_subscriber ) {
1447        $paywall_description = esc_html__( "It's a subscribers only post. Subscribe to get access to the rest of this post and other subscriber-only content.", 'jetpack' );
1448        $paywall_action_btn  = esc_html__( 'Subscribe', 'jetpack' );
1449    } elseif ( $is_paid_post && $is_subscriber ) {
1450        $paywall_description = esc_html__( "You're currently a free subscriber. Upgrade your subscription to get access to the rest of this post and other paid-subscriber only content.", 'jetpack' );
1451        $paywall_action_btn  = esc_html__( 'Upgrade subscription', 'jetpack' );
1452    } else {
1453        // - For paid post when the user is not a subscriber.
1454        // - Default for all other cases.
1455        $paywall_description = esc_html__( 'Become a paid subscriber to get access to the rest of this post and other exclusive content.', 'jetpack' );
1456        $paywall_action_btn  = esc_html__( 'Subscribe', 'jetpack' );
1457    }
1458
1459    return '
1460<!-- wp:columns -->
1461<div class="wp-block-columns jetpack-paywall-simple" style="display: inline-block; width: 90%">
1462    <!-- wp:column -->
1463    <div class="wp-block-column" style="background-color: #F6F7F7; padding: 32px; 24px;">
1464        <!-- wp:heading -->
1465        <h2 class="has-text-align-center" style="margin: 0 0 12px; font-weight: 600;">' . $paywall_heading . '</h2>
1466        <!-- /wp:heading -->
1467        <!-- wp:paragraph -->
1468        <p class="has-text-align-center"
1469           style="text-align: center;
1470                  color: #50575E;
1471                  font-weight: 400;
1472                  font-size: 16px;
1473                  font-family: \'SF Pro Text\', sans-serif;
1474                  line-height: 28.8px;">
1475        ' . $paywall_description . '
1476        </p>
1477        <!-- /wp:paragraph -->
1478        <!-- wp:buttons -->
1479        <div class="wp-block-buttons" style="text-align: center;">
1480            <!-- wp:button -->
1481            <div class="wp-block-button" style="display: inline-block; margin: 10px 0; border-style: none; padding: 0;">
1482                <a href="' . esc_url( get_post_permalink() ) . '" class="wp-block-button__link wp-element-button"
1483                   data-wpcom-track data-tracks-link-desc="paywall-email-click"
1484                   style="display: inline-block;
1485                          padding: 12px 15px;
1486                          background-color: #3858e9;
1487                          color: #FFFFFF;
1488                          text-decoration: none;
1489                          border-radius: 5px;
1490                          font-family: \'SF Pro Display\', sans-serif;
1491                          font-weight: 500;
1492                          font-size: 16px;
1493                          text-align: center;">' . $paywall_action_btn . '</a>
1494            </div>
1495            <!-- /wp:button -->
1496        </div>
1497        <!-- /wp:buttons -->
1498    </div>
1499    <!-- /wp:column -->
1500</div>
1501<!-- /wp:columns -->
1502';
1503}