PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.8.1
Jetpack – WP Security, Backup, Speed, & Growth v15.8.1
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / extensions / blocks / subscriptions / subscriptions.php
jetpack / extensions / blocks / subscriptions Last commit date
class-jetpack-subscription-site.php 9 months ago constants.php 2 years ago subscriptions.php 5 months ago
subscriptions.php
1425 lines
1 <?php
2 /**
3 * Subscriptions Block.
4 *
5 * @package automattic/jetpack
6 */
7
8 namespace Automattic\Jetpack\Extensions\Subscriptions;
9
10 use Automattic\Jetpack\Blocks;
11 use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service;
12 use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Jetpack_Token_Subscription_Service;
13 use Automattic\Jetpack\Modules;
14 use Automattic\Jetpack\Status\Host;
15 use Automattic\Jetpack\Status\Request;
16 use Jetpack_Gutenberg;
17 use Jetpack_Memberships;
18 use Jetpack_Subscriptions_Widget;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit( 0 );
22 }
23
24 require_once __DIR__ . '/class-jetpack-subscription-site.php';
25 require_once __DIR__ . '/constants.php';
26 require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
27
28 /**
29 * These block defaults should match ./constants.js
30 */
31 const DEFAULT_BORDER_RADIUS_VALUE = 0;
32 const DEFAULT_BORDER_WEIGHT_VALUE = 1;
33 const DEFAULT_FONTSIZE_VALUE = '16px';
34 const DEFAULT_PADDING_VALUE = 15;
35 const DEFAULT_SPACING_VALUE = 10;
36 const 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 */
43 function 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 return;
57 }
58
59 require_once JETPACK__PLUGIN_DIR . '/modules/memberships/class-jetpack-memberships.php';
60
61 if ( \Jetpack_Memberships::should_enable_monetize_blocks_in_editor() ) {
62 Blocks::jetpack_register_block(
63 __DIR__,
64 array(
65 'render_callback' => __NAMESPACE__ . '\render_block',
66 'render_email_callback' => __NAMESPACE__ . '\render_email',
67 'supports' => array(
68 'spacing' => array(
69 'margin' => true,
70 'padding' => true,
71 ),
72 'align' => array( 'wide', 'full' ),
73 ),
74 )
75 );
76 }
77
78 register_post_meta(
79 'post',
80 META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS,
81 array(
82 'show_in_rest' => true,
83 'single' => true,
84 'type' => 'string',
85 'auth_callback' => function () {
86 return wp_get_current_user()->has_cap( 'edit_posts' );
87 },
88 )
89 );
90
91 // The meta is a "don't send" flag, so invert the blog option (which defaults to true = "send").
92 $dont_email_default = ! get_option( 'wpcom_newsletter_send_default', true );
93
94 register_post_meta(
95 'post',
96 META_NAME_FOR_POST_DONT_EMAIL_TO_SUBS,
97 array(
98 'default' => $dont_email_default,
99 'show_in_rest' => true,
100 'single' => true,
101 'type' => 'boolean',
102 'auth_callback' => function () {
103 return wp_get_current_user()->has_cap( 'edit_posts' );
104 },
105 )
106 );
107
108 register_post_meta(
109 'post',
110 META_NAME_FOR_POST_TIER_ID_SETTINGS,
111 array(
112 'show_in_rest' => true,
113 'single' => true,
114 'type' => 'integer',
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_CONTAINS_PAYWALLED_CONTENT,
124 array(
125 'show_in_rest' => true,
126 'single' => true,
127 'type' => 'boolean',
128 'auth_callback' => function () {
129 return wp_get_current_user()->has_cap( 'edit_posts' );
130 },
131 )
132 );
133
134 // This ensures Jetpack will sync this post meta to WPCOM.
135 add_filter(
136 'jetpack_sync_post_meta_whitelist',
137 function ( $allowed_meta ) {
138 return array_merge(
139 $allowed_meta,
140 array(
141 META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS,
142 META_NAME_FOR_POST_DONT_EMAIL_TO_SUBS,
143 META_NAME_CONTAINS_PAYWALLED_CONTENT,
144 META_NAME_FOR_POST_TIER_ID_SETTINGS,
145 META_NAME_CONTAINS_PAID_CONTENT,
146 )
147 );
148 }
149 );
150
151 // Hide the content – Priority 8 makes it run before do_blocks gets called for the content
152 add_filter( 'the_content', __NAMESPACE__ . '\add_paywall', 8 );
153
154 // Close comments on the front-end
155 add_filter( 'comments_open', __NAMESPACE__ . '\maybe_close_comments', 10, 2 );
156 add_filter( 'pings_open', __NAMESPACE__ . '\maybe_close_comments', 10, 2 );
157
158 // Hide existing comments
159 add_filter( 'get_comment', __NAMESPACE__ . '\maybe_gate_existing_comments' );
160
161 // Add a 'Newsletter' column to the Edit posts page
162 // We only display the "Newsletter" column if we have configured the paid newsletter plan
163 if ( defined( 'WP_ADMIN' ) && WP_ADMIN && Jetpack_Memberships::has_configured_plans_jetpack_recurring_payments( 'newsletter' ) ) {
164 add_action( 'manage_post_posts_columns', __NAMESPACE__ . '\register_newsletter_access_column' );
165 add_action( 'manage_post_posts_custom_column', __NAMESPACE__ . '\render_newsletter_access_rows', 10, 2 );
166 add_action( 'admin_head', __NAMESPACE__ . '\newsletter_access_column_styles' );
167 }
168
169 add_action( 'init', __NAMESPACE__ . '\maybe_prevent_super_cache_caching' );
170
171 add_action( 'wp_after_insert_post', __NAMESPACE__ . '\add_paywalled_content_post_meta', 99, 2 );
172
173 add_filter(
174 'jetpack_options_whitelist',
175 function ( $options ) {
176 $options[] = 'jetpack_subscriptions_subscribe_post_end_enabled';
177 $options[] = 'jetpack_subscriptions_subscribe_navigation_enabled';
178
179 return $options;
180 }
181 );
182
183 // If called via REST API, we need to register later in the lifecycle
184 if ( ( new Host() )->is_wpcom_platform() && ! Request::is_frontend() ) {
185 add_action(
186 'restapi_theme_init',
187 function () {
188 Jetpack_Subscription_Site::init()->handle_subscribe_block_placements();
189 }
190 );
191 } else {
192 Jetpack_Subscription_Site::init()->handle_subscribe_block_placements();
193 }
194 }
195 add_action( 'init', __NAMESPACE__ . '\register_block', 9 );
196
197 /**
198 * Returns true when in a WP.com environment.
199 *
200 * @return boolean
201 */
202 function is_wpcom() {
203 return defined( 'IS_WPCOM' ) && IS_WPCOM;
204 }
205
206 /**
207 * Adds a 'Newsletter' column after the 'Title' column in the post list
208 *
209 * @param array $columns An array of column names.
210 * @return array An array of column names.
211 */
212 function register_newsletter_access_column( $columns ) {
213 $position = array_search( 'title', array_keys( $columns ), true );
214 $new_column = array( NEWSLETTER_COLUMN_ID => __( 'Newsletter', 'jetpack' ) );
215 return array_merge(
216 array_slice( $columns, 0, $position + 1, true ),
217 $new_column,
218 array_slice( $columns, $position, null, true )
219 );
220 }
221
222 /**
223 * Add a meta to prevent publication on firehose, ES AI or Reader
224 *
225 * @param int $post_id Post id being saved.
226 * @param \WP_Post $post Post being saved.
227 * @return void
228 */
229 function add_paywalled_content_post_meta( int $post_id, \WP_Post $post ) {
230 if ( $post->post_type !== 'post' ) {
231 return;
232 }
233
234 $access_level = get_post_meta( $post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true );
235
236 $is_paywalled = false;
237 switch ( $access_level ) {
238 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS_ALL_TIERS:
239 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS:
240 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS:
241 $is_paywalled = true;
242 }
243 if ( $is_paywalled ) {
244 update_post_meta( $post_id, META_NAME_CONTAINS_PAYWALLED_CONTENT, $is_paywalled );
245 }
246 if ( ! $is_paywalled ) {
247 delete_post_meta( $post_id, META_NAME_CONTAINS_PAYWALLED_CONTENT );
248 }
249 }
250
251 /**
252 * Displays the newsletter access level.
253 *
254 * @param string $column_id The ID of the column to display.
255 * @param int $post_id The current post ID.
256 */
257 function render_newsletter_access_rows( $column_id, $post_id ) {
258 if ( NEWSLETTER_COLUMN_ID !== $column_id ) {
259 return;
260 }
261
262 $access_level = get_post_meta( $post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true );
263
264 switch ( $access_level ) {
265 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS_ALL_TIERS:
266 echo esc_html__( 'Paid Subscribers (all plans)', 'jetpack' );
267 break;
268 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS:
269 echo esc_html__( 'Paid Subscribers', 'jetpack' );
270 break;
271 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS:
272 echo esc_html__( 'Subscribers', 'jetpack' );
273 break;
274 case Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY:
275 echo esc_html__( 'Everybody', 'jetpack' );
276 break;
277 default:
278 echo '';
279 }
280 }
281
282 /**
283 * Adds the Newsletter column styles
284 */
285 function newsletter_access_column_styles() {
286 echo '<style id="jetpack-newsletter-newsletter-access-column"> table.fixed .column-newsletter_access { width: 10%; } </style>';
287 }
288
289 /**
290 * Determine the amount of folks currently subscribed to the blog, splitted out in total_subscribers, email_subscribers, social_followers & paid_subscribers.
291 *
292 * @return array containing ['value' => ['total_subscribers' => 0, 'email_subscribers' => 0, 'paid_subscribers' => 0, 'social_followers' => 0]]
293 */
294 function fetch_subscriber_counts() {
295 $subs_count = 0;
296 if ( is_wpcom() ) {
297 $subs_count = array(
298 'value' => \wpcom_fetch_subs_counts( true ),
299 );
300 } else {
301 $cache_key = 'wpcom_subscribers_totals';
302 $subs_count = get_transient( $cache_key );
303 if ( false === $subs_count || 'failed' === $subs_count['status'] ) {
304 $xml = new \Jetpack_IXR_Client();
305 $xml->query( 'jetpack.fetchSubscriberCounts' );
306
307 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.
308 $subs_count = array(
309 'status' => 'failed',
310 'code' => $xml->getErrorCode(),
311 'message' => $xml->getErrorMessage(),
312 'value' => ( isset( $subs_count['value'] ) ) ? $subs_count['value'] : array(
313 'total_subscribers' => 0,
314 'email_subscribers' => 0,
315 'social_followers' => 0,
316 'paid_subscribers' => 0,
317 ),
318 );
319 } else {
320 $subs_count = array(
321 'status' => 'success',
322 'value' => $xml->getResponse(),
323 );
324 }
325 set_transient( $cache_key, $subs_count, 3600 ); // Try to cache the result for at least 1 hour.
326 }
327 }
328 return $subs_count;
329 }
330
331 /**
332 * Returns subscriber count based on include_social_followers attribute
333 *
334 * @param bool $include_social_followers Whether to include social followers in the count.
335 * @return int
336 */
337 function get_subscriber_count( $include_social_followers ) {
338 $counts = fetch_subscriber_counts();
339
340 if ( $include_social_followers ) {
341 $subscriber_count = $counts['value']['total_subscribers'] + $counts['value']['social_followers'];
342 } else {
343 $subscriber_count = $counts['value']['total_subscribers'];
344 }
345 return $subscriber_count;
346 }
347
348 /**
349 * Returns true if the block attributes contain a value for the given key.
350 *
351 * @param array $attributes Array containing the block attributes.
352 * @param string $key Block attribute key.
353 *
354 * @return boolean
355 */
356 function has_attribute( $attributes, $key ) {
357 return isset( $attributes[ $key ] ) && $attributes[ $key ] !== 'undefined';
358 }
359
360 /**
361 * Returns the value for the given attribute key, with the option of providing a default fallback value.
362 *
363 * @param array $attributes Array containing the block attributes.
364 * @param string $key Block attribute key.
365 * @param mixed $default Optional fallback value in case the key doesn't exist.
366 *
367 * @return mixed
368 */
369 function get_attribute( $attributes, $key, $default = null ) {
370 return has_attribute( $attributes, $key ) ? $attributes[ $key ] : $default;
371 }
372
373 /**
374 * Mimics getColorClassName, getFontSizeClass and getGradientClass from @wordpress/block-editor js package.
375 *
376 * @param string $setting Setting name.
377 * @param string $value Setting value.
378 *
379 * @return string
380 */
381 function get_setting_class_name( $setting, $value ) {
382 if ( ! $setting || ! $value ) {
383 return '';
384 }
385
386 return sprintf( 'has-%s-%s', $value, $setting );
387 }
388
389 /**
390 * Uses block attributes to generate an array containing the classes for various block elements.
391 * Based on Jetpack_Subscriptions_Widget::do_subscription_form() which the block was originally using.
392 *
393 * @param array $attributes Array containing the block attributes.
394 *
395 * @return array
396 */
397 function get_element_class_names_from_attributes( $attributes ) {
398 $text_color_class = get_setting_class_name( 'color', get_attribute( $attributes, 'textColor' ) );
399 $font_size_class = get_setting_class_name( 'font-size', get_attribute( $attributes, 'fontSize' ) );
400 $border_class = get_setting_class_name( 'border-color', get_attribute( $attributes, 'borderColor' ) );
401
402 $button_background_class = get_setting_class_name( 'background-color', get_attribute( $attributes, 'buttonBackgroundColor' ) );
403 $button_gradient_class = get_setting_class_name( 'gradient-background', get_attribute( $attributes, 'buttonGradient' ) );
404
405 $email_field_background_class = get_setting_class_name( 'background-color', get_attribute( $attributes, 'emailFieldBackgroundColor' ) );
406 $email_field_gradient_class = get_setting_class_name( 'gradient-background', get_attribute( $attributes, 'emailFieldGradient' ) );
407
408 $submit_button_classes = array_filter(
409 array(
410 'wp-block-button__link' => true,
411 'no-border-radius' => 0 === get_attribute( $attributes, 'borderRadius', 0 ),
412 $font_size_class => true,
413 $border_class => true,
414 'has-text-color' => ! empty( $text_color_class ),
415 $text_color_class => true,
416 'has-background' => ! empty( $button_background_class ) || ! empty( $button_gradient_class ),
417 $button_background_class => ! empty( $button_background_class ),
418 $button_gradient_class => ! empty( $button_gradient_class ),
419 )
420 );
421
422 $email_field_classes = array_filter(
423 array(
424 'no-border-radius' => 0 === get_attribute( $attributes, 'borderRadius', 0 ),
425 $font_size_class => true,
426 $border_class => true,
427 $email_field_background_class => true,
428 $email_field_gradient_class => true,
429 )
430 );
431
432 $block_wrapper_classes = array_filter(
433 array(
434 'wp-block-jetpack-subscriptions__supports-newline' => true,
435 'wp-block-jetpack-subscriptions__use-newline' => (bool) get_attribute( $attributes, 'buttonOnNewLine' ),
436 'wp-block-jetpack-subscriptions__show-subs' => (bool) get_attribute( $attributes, 'showSubscribersTotal' ),
437 )
438 );
439
440 return array(
441 'block_wrapper' => implode( ' ', array_keys( $block_wrapper_classes ) ),
442 'email_field' => implode( ' ', array_keys( $email_field_classes ) ),
443 'submit_button' => implode( ' ', array_keys( $submit_button_classes ) ),
444 );
445 }
446
447 /**
448 * Checks if block style is "button only"
449 *
450 * @param string $class_name Block attribute className; multiple names are spearated by space.
451 *
452 * @return bool
453 */
454 function is_button_only_style( $class_name ) {
455 if ( empty( $class_name ) ) {
456 return false;
457 }
458
459 $class_names = explode( ' ', $class_name );
460 return in_array( 'is-style-button', $class_names, true );
461 }
462
463 /**
464 * Uses block attributes to generate an array containing the styles for various block elements.
465 * Based on Jetpack_Subscriptions_Widget::do_subscription_form() which the block was originally using.
466 *
467 * @param array $attributes Array containing the block attributes.
468 *
469 * @return array
470 */
471 function get_element_styles_from_attributes( $attributes ) {
472 $is_button_only_style = is_button_only_style( get_attribute( $attributes, 'className', '' ) );
473
474 $button_background_style = ! has_attribute( $attributes, 'buttonBackgroundColor' ) && has_attribute( $attributes, 'customButtonGradient' )
475 ? get_attribute( $attributes, 'customButtonGradient' )
476 : get_attribute( $attributes, 'customButtonBackgroundColor' );
477
478 $email_field_styles = '';
479 $submit_button_wrapper_styles = '';
480 $submit_button_styles = '';
481
482 if ( ! empty( $button_background_style ) ) {
483 $submit_button_styles .= sprintf( 'background: %s;', $button_background_style );
484 }
485
486 if ( has_attribute( $attributes, 'customTextColor' ) ) {
487 $submit_button_styles .= sprintf( 'color: %s;', get_attribute( $attributes, 'customTextColor' ) );
488 }
489
490 if ( has_attribute( $attributes, 'buttonWidth' ) ) {
491 $submit_button_wrapper_styles .= sprintf( 'width: %s;', get_attribute( $attributes, 'buttonWidth', DEFAULT_BUTTON_WIDTH ) );
492 $submit_button_wrapper_styles .= 'max-width: 100%;';
493
494 // Account for custom margins on inline forms.
495 $submit_button_styles .= true === get_attribute( $attributes, 'buttonOnNewLine' )
496 ? 'width: 100%;'
497 : sprintf( 'width: calc(100%% - %dpx);', get_attribute( $attributes, 'spacing', DEFAULT_SPACING_VALUE ) );
498 }
499
500 $font_size = get_attribute( $attributes, 'customFontSize', DEFAULT_FONTSIZE_VALUE );
501 $style = sprintf( 'font-size: %s%s;', $font_size, is_numeric( $font_size ) ? 'px' : '' );
502
503 $submit_button_styles .= $style;
504 $email_field_styles .= $style;
505
506 $padding = get_attribute( $attributes, 'padding', DEFAULT_PADDING_VALUE );
507 $style = sprintf( 'padding: %1$dpx %2$dpx %1$dpx %2$dpx;', $padding, round( $padding * 1.5 ) );
508
509 $submit_button_styles .= $style;
510 $email_field_styles .= $style;
511
512 if ( ! $is_button_only_style ) {
513 $button_spacing = get_attribute( $attributes, 'spacing', DEFAULT_SPACING_VALUE );
514 if ( true === get_attribute( $attributes, 'buttonOnNewLine' ) ) {
515 $submit_button_styles .= sprintf( 'margin-top: %dpx;', $button_spacing );
516 } else {
517 $submit_button_styles .= 'margin: 0; '; // Reset Safari's 2px default margin for buttons affecting input and button union
518 $submit_button_styles .= sprintf( 'margin-left: %dpx;', $button_spacing );
519 }
520 }
521
522 if ( has_attribute( $attributes, 'borderColor' ) ) {
523 $style = sprintf( 'border-color: %s;', get_attribute( $attributes, 'borderColor', '' ) );
524 $submit_button_styles .= $style;
525 $email_field_styles .= $style;
526 }
527
528 $style = sprintf( 'border-radius: %dpx;', get_attribute( $attributes, 'borderRadius', DEFAULT_BORDER_RADIUS_VALUE ) );
529 $submit_button_styles .= $style;
530 $email_field_styles .= $style;
531
532 $style = sprintf( 'border-width: %dpx;', get_attribute( $attributes, 'borderWeight', DEFAULT_BORDER_WEIGHT_VALUE ) );
533 $submit_button_styles .= $style;
534 $email_field_styles .= $style;
535
536 if ( has_attribute( $attributes, 'customBorderColor' ) ) {
537 $style = sprintf( 'border-color: %s; border-style: solid;', get_attribute( $attributes, 'customBorderColor' ) );
538
539 $submit_button_styles .= $style;
540 $email_field_styles .= $style;
541 }
542
543 if ( ! Request::is_frontend() ) {
544 $background_color_style = get_attribute_color( 'buttonBackgroundColor', $attributes, '#113AF5' /* default lettre theme color */ );
545 $text_color_style = get_attribute_color( 'textColor', $attributes, '#FFFFFF' );
546 $submit_button_styles .= sprintf( ' background-color: %s; color: %s;', $background_color_style, $text_color_style );
547 }
548
549 return array(
550 'email_field' => $email_field_styles,
551 'submit_button' => $submit_button_styles,
552 'submit_button_wrapper' => $submit_button_wrapper_styles,
553 );
554 }
555
556 /**
557 * Retrieve the resolved color for a given attribute.
558 *
559 * @param string $attribute_name The name of the attribute to resolve.
560 * @param array $attributes An array of all attributes.
561 * @param string $default_color A fallback color in case no color can be resolved.
562 *
563 * @return string Returns the resolved color or the default color if no color is found.
564 */
565 function get_attribute_color( $attribute_name, $attributes, $default_color ) {
566 if ( has_attribute( $attributes, $attribute_name ) ) {
567 $color_slug = get_attribute( $attributes, $attribute_name );
568 $resolved_color = get_color_from_slug( $color_slug );
569
570 if ( $resolved_color ) {
571 return $resolved_color;
572 }
573 }
574
575 return get_global_style_color( $attribute_name, $default_color );
576 }
577
578 /**
579 * Retrieve the global style color based on a provided style key.
580 *
581 * @param string $style_key The key for the desired style.
582 * @param string $default_color A fallback color in case the global style is not set.
583 *
584 * @return string Returns the color defined in global styles or the default color if not defined.
585 */
586 function get_global_style_color( $style_key, $default_color ) {
587 $global_styles = wp_get_global_styles(
588 array( 'color' ),
589 array(
590 'block_name' => 'core/button',
591 'transforms' => array( 'resolve-variables' ),
592 )
593 );
594
595 if ( isset( $global_styles[ $style_key ] ) ) {
596 return $global_styles[ $style_key ];
597 }
598
599 return $default_color;
600 }
601
602 /**
603 * Convert a color slug into its corresponding color value.
604 *
605 * @param string $slug The slug representation of the color.
606 *
607 * @return string|null Returns the color value if found, or null otherwise.
608 */
609 function get_color_from_slug( $slug ) {
610 $color_palettes = wp_get_global_settings( array( 'color', 'palette' ) );
611
612 if ( ! is_array( $color_palettes ) ) {
613 return null;
614 }
615
616 foreach ( $color_palettes as $palette ) {
617 if ( is_array( $palette ) ) {
618 foreach ( $palette as $color ) {
619 if ( isset( $color['slug'] ) && $color['slug'] === $slug && isset( $color['color'] ) ) {
620 return $color['color'];
621 }
622 }
623 }
624 }
625
626 return null;
627 }
628
629 /**
630 * Is the Jetpack_Memberships class loaded.
631 */
632 function is_jetpack_memberships_loaded(): bool {
633 return class_exists( '\Jetpack_Memberships' );
634 }
635
636 /**
637 * Subscriptions block render callback.
638 *
639 * @param array $attributes Array containing the block attributes.
640 *
641 * @return string
642 */
643 function render_block( $attributes ) {
644 // If the Subscriptions module is not active, don't render the block.
645 if ( ! ( new Modules() )->is_active( 'subscriptions' ) ) {
646 return '';
647 }
648
649 if ( is_jetpack_memberships_loaded() ) {
650 // We only want the sites that have newsletter feature enabled to be graced by this JavaScript.
651 Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
652 } else {
653 Jetpack_Gutenberg::load_styles_as_required( FEATURE_NAME );
654 }
655
656 if ( ! class_exists( 'Jetpack_Subscriptions_Widget' ) ) {
657 return '';
658 }
659
660 // Prefill the email field with the current user's email if they are logged in via Memberships premium content token
661 $subscribe_email = Jetpack_Memberships::get_current_user_email();
662
663 // If no email, then prefill the email field with the current user's email if they are logged in
664 if ( empty( $subscribe_email ) ) {
665 $current_user = wp_get_current_user();
666 if ( ! empty( $current_user->user_email ) ) {
667 $subscribe_email = $current_user->user_email;
668 }
669 }
670
671 // The block is using the Jetpack_Subscriptions_Widget backend, hence the need to increase the instance count.
672 ++Jetpack_Subscriptions_Widget::$instance_count;
673
674 $classes = get_element_class_names_from_attributes( $attributes );
675 $styles = get_element_styles_from_attributes( $attributes );
676
677 // The default value was previously "true" in block.json. We don't want to rely setting "default" in block.json to falsy,
678 // because it would change the setting for previously saved blocks. Block editor doesn't store default values in attributes at all.
679 // Hence users without this set will still get social counts included in the subscriber counter.
680 // Lowering the subscriber count on their behalf with code change would be controversial.
681 // We want to disencourage including social count as it's misleading.
682 $include_social_followers = isset( $attributes['includeSocialFollowers'] ) ? (bool) get_attribute( $attributes, 'includeSocialFollowers' ) : true;
683
684 $data = array(
685 'widget_id' => Jetpack_Subscriptions_Widget::$instance_count,
686 'subscribe_email' => $subscribe_email,
687 'is_paid_subscriber' => get_attribute( $attributes, 'isPaidSubscriber', false ),
688 'wrapper_attributes' => get_block_wrapper_attributes(
689 array(
690 'class' => $classes['block_wrapper'],
691 )
692 ),
693 'subscribe_placeholder' => get_attribute( $attributes, 'subscribePlaceholder', __( 'Type your email…', 'jetpack' ) ),
694 'submit_button_text' => get_attribute( $attributes, 'submitButtonText', __( 'Subscribe', 'jetpack' ) ),
695 'submit_button_text_subscribed' => get_attribute( $attributes, 'submitButtonTextSubscribed', __( 'Subscribed', 'jetpack' ) ),
696 'submit_button_text_upgrade' => get_attribute( $attributes, 'submitButtonTextUpgrade', __( 'Upgrade subscription', 'jetpack' ) ),
697 'success_message' => get_attribute(
698 $attributes,
699 'successMessage',
700 esc_html__( "Success! An email was just sent to confirm your subscription. Please find the email now and click 'Confirm' to start subscribing.", 'jetpack' )
701 ),
702 'show_subscribers_total' => (bool) get_attribute( $attributes, 'showSubscribersTotal' ),
703 'subscribers_total' => get_attribute( $attributes, 'showSubscribersTotal' ) ? get_subscriber_count( $include_social_followers ) : 0,
704 'referer' => esc_url_raw(
705 ( is_ssl() ? 'https' : 'http' ) . '://' . ( isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '' ) .
706 ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' )
707 ),
708 'source' => 'subscribe-block',
709 'app_source' => get_attribute( $attributes, 'appSource', null ),
710 'class_name' => get_attribute( $attributes, 'className' ),
711 'selected_newsletter_categories' => get_attribute( $attributes, 'selectedNewsletterCategoryIds', array() ),
712 'preselected_newsletter_categories' => get_attribute( $attributes, 'preselectNewsletterCategories', false ),
713 );
714
715 // Only render the email version in non-frontend contexts.
716 if ( is_feed() || wp_is_xml_request() ||
717 ( defined( 'REST_REQUEST' ) && REST_REQUEST && ! wp_is_json_request() ) ||
718 ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) ||
719 ( defined( 'WP_CLI' ) && WP_CLI ) ||
720 wp_is_jsonp_request() ) {
721 return render_for_email( $data, $styles );
722 }
723
724 return render_for_website( $data, $classes, $styles );
725 }
726
727 /**
728 * Get the post access level for the current post. Defaults to 'everybody' if the query is not for a single post
729 *
730 * @return string the actual post access level (see projects/plugins/jetpack/extensions/blocks/subscriptions/constants.js for the values).
731 */
732 function get_post_access_level_for_current_post() {
733 if ( ! is_singular() ) {
734 // There is no "actual" current post.
735 return Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY;
736 }
737
738 return Jetpack_Memberships::get_post_access_level();
739 }
740
741 /**
742 * Renders the subscriptions block at the site.
743 *
744 * @param array $data Array containing block view data.
745 * @param array $classes Array containing the classes for different block elements.
746 * @param array $styles Array containing the styles for different block elements.
747 *
748 * @return string
749 */
750 function render_for_website( $data, $classes, $styles ) {
751 $lang = get_locale();
752 $blog_id = \Jetpack_Options::get_option( 'id' );
753 $widget_id_suffix = Jetpack_Subscriptions_Widget::$instance_count > 1 ? '-' . Jetpack_Subscriptions_Widget::$instance_count : '';
754 $form_id = 'subscribe-blog' . $widget_id_suffix;
755 $form_url = 'https://wordpress.com/email-subscriptions';
756 $post_access_level = get_post_access_level_for_current_post();
757 $is_button_only_style = ! empty( $data['class_name'] ) ? is_button_only_style( $data['class_name'] ) : false;
758
759 // Post ID is used for pulling post-specific paid status, and returning to the right post after confirming subscription
760 $post_id = null;
761 if ( in_the_loop() ) {
762 $post_id = get_the_ID();
763 } elseif ( is_singular( 'post' ) || is_page() ) {
764 $post_id = get_queried_object_id();
765 } else {
766 $post_id = get_option( 'page_on_front' );
767 }
768
769 $subscribe_field_id = apply_filters( 'subscribe_field_id', 'subscribe-field' . $widget_id_suffix, $data['widget_id'] );
770 $tier_id = get_post_meta( $post_id, META_NAME_FOR_POST_TIER_ID_SETTINGS, true );
771 $is_subscribed = Jetpack_Memberships::is_current_user_subscribed();
772 $button_text = get_submit_button_text( $data );
773 $show_subscriber_count = $data['show_subscribers_total'] && $data['subscribers_total'] && ! $is_subscribed;
774
775 ob_start();
776
777 Jetpack_Subscriptions_Widget::render_widget_status_messages(
778 array(
779 'success_message' => $data['success_message'],
780 )
781 );
782 ?>
783 <div <?php echo wp_kses_data( $data['wrapper_attributes'] ); ?>>
784 <div class="wp-block-jetpack-subscriptions__container<?php echo ! $is_subscribed ? ' is-not-subscriber' : ''; ?>">
785 <?php if ( is_top_subscription() ) : ?>
786 <p id="subscribe-submit" class="is-link"
787 <?php if ( ! empty( $styles['submit_button_wrapper'] ) ) : ?>
788 style="<?php echo esc_attr( $styles['submit_button_wrapper'] ); ?>"
789 <?php endif; ?>
790 >
791 <a
792 href="<?php echo esc_url( 'https://wordpress.com/reader/site/subscription/' . $blog_id ); ?>"
793 <?php if ( ! empty( $classes['submit_button'] ) ) : ?>
794 class="<?php echo esc_attr( $classes['submit_button'] ); ?>"
795 <?php endif; ?>
796 <?php if ( ! empty( $styles['submit_button'] ) ) : ?>
797 style="<?php echo esc_attr( $styles['submit_button'] ); ?>"
798 <?php endif; ?>
799 >
800 <?php echo sanitize_submit_text( $button_text ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
801 </a>
802 </p>
803 <?php else : ?>
804 <form
805 action="<?php echo esc_url( $form_url ); ?>"
806 method="post"
807 accept-charset="utf-8"
808 data-blog="<?php echo esc_attr( $blog_id ); ?>"
809 data-post_access_level="<?php echo esc_attr( $post_access_level ); ?>"
810 data-subscriber_email="<?php echo esc_attr( $data['subscribe_email'] ); ?>"
811 id="<?php echo esc_attr( $form_id ); ?>"
812 >
813 <div class="wp-block-jetpack-subscriptions__form-elements">
814 <?php if ( ! $is_subscribed && ! $is_button_only_style ) : ?>
815 <p id="subscribe-email">
816 <label
817 id="<?php echo esc_attr( $subscribe_field_id . '-label' ); ?>"
818 for="<?php echo esc_attr( $subscribe_field_id ); ?>"
819 class="screen-reader-text"
820 >
821 <?php echo esc_html( $data['subscribe_placeholder'] ); ?>
822 </label>
823 <?php
824 printf(
825 '<input
826 required="required"
827 type="email"
828 name="email"
829 autocomplete="email"
830 %1$s
831 style="%2$s"
832 placeholder="%3$s"
833 value="%4$s"
834 id="%5$s"
835 %6$s
836 />',
837 ( ! empty( $classes['email_field'] )
838 ? 'class="' . esc_attr( $classes['email_field'] ) . '"'
839 : ''
840 ),
841 ( ! empty( $styles['email_field'] )
842 ? esc_attr( $styles['email_field'] )
843 : 'width: 95%; padding: 1px 10px'
844 ),
845 esc_attr( $data['subscribe_placeholder'] ),
846 esc_attr( $data['subscribe_email'] ),
847 esc_attr( $subscribe_field_id ),
848 ( ! empty( $data['subscribe_email'] )
849 ? 'disabled title="' . esc_attr__( "You're logged in with this email", 'jetpack' ) . '"'
850 : 'title="' . esc_attr__( 'Please fill in this field.', 'jetpack' ) . '"'
851 )
852 );
853 ?>
854 </p>
855 <?php endif; ?>
856 <p id="subscribe-submit"
857 <?php if ( ! empty( $styles['submit_button_wrapper'] ) ) : ?>
858 style="<?php echo esc_attr( $styles['submit_button_wrapper'] ); ?>"
859 <?php endif; ?>
860 >
861 <input type="hidden" name="action" value="subscribe"/>
862 <input type="hidden" name="blog_id" value="<?php echo (int) $blog_id; ?>"/>
863 <input type="hidden" name="source" value="<?php echo esc_url( $data['referer'] ); ?>"/>
864 <input type="hidden" name="sub-type" value="<?php echo esc_attr( $data['source'] ); ?>"/>
865 <input type="hidden" name="app_source" value="<?php echo esc_attr( $data['app_source'] ); ?>"/>
866 <input type="hidden" name="redirect_fragment" value="<?php echo esc_attr( $form_id ); ?>"/>
867 <input type="hidden" name="lang" value="<?php echo esc_attr( $lang ); ?>"/>
868 <?php
869 wp_nonce_field( 'blogsub_subscribe_' . $blog_id );
870
871 if ( ! empty( $post_id ) ) {
872 echo '<input type="hidden" name="post_id" value="' . esc_attr( $post_id ) . '"/>';
873 }
874
875 if ( ! empty( $tier_id ) ) {
876 echo '<input type="hidden" name="tier_id" value="' . esc_attr( $tier_id ) . '"/>';
877 }
878
879 if ( $data['preselected_newsletter_categories'] && ! empty( $data['selected_newsletter_categories'] ) ) {
880 echo '<input type="hidden" name="selected_newsletter_categories" value="' . esc_attr( implode( ',', $data['selected_newsletter_categories'] ) ) . '"/>';
881 }
882 ?>
883 <button type="submit"
884 <?php if ( ! empty( $classes['submit_button'] ) ) : ?>
885 class="<?php echo esc_attr( $classes['submit_button'] ); ?>"
886 <?php endif; ?>
887 <?php if ( ! empty( $styles['submit_button'] ) ) : ?>
888 style="<?php echo esc_attr( $styles['submit_button'] ); ?>"
889 <?php endif; ?>
890 name="jetpack_subscriptions_widget"
891 >
892 <?php echo sanitize_submit_text( $button_text ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
893 </button>
894 </p>
895 </div>
896 </form>
897 <?php endif; ?>
898 <?php if ( $show_subscriber_count ) : ?>
899 <div class="wp-block-jetpack-subscriptions__subscount">
900 <?php echo esc_html( Jetpack_Memberships::get_join_others_text( $data['subscribers_total'] ) ); ?>
901 </div>
902 <?php endif; ?>
903 </div>
904 </div>
905 <?php
906 return ob_get_clean();
907 }
908
909 /**
910 * Renders the email version of the subscriptions block.
911 *
912 * @param array $data Array containing block view data.
913 * @param array $styles Array containing the styles for different block elements.
914 *
915 * @return string
916 */
917 function render_for_email( $data, $styles ) {
918 $submit_button_wrapper_style = ! empty( $styles['submit_button_wrapper'] ) ? 'style="' . esc_attr( $styles['submit_button_wrapper'] ) . '"' : '';
919 $button_text = get_submit_button_text( $data );
920
921 $html = '<div ' . wp_kses_data( $data['wrapper_attributes'] ) . '>
922 <div>
923 <div>
924 <div>
925 <p ' . $submit_button_wrapper_style . '>
926 <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>
927 </p>
928 </div>
929 </div>
930 </div>
931 </div>';
932
933 return $html;
934 }
935
936 /**
937 * WooCommerce Email Editor render callback for the subscriptions block.
938 *
939 * @param string $block_content The block content.
940 * @param array $parsed_block The parsed block data.
941 * @param object $rendering_context The email rendering context.
942 *
943 * @return string
944 */
945 function render_email( $block_content, array $parsed_block, $rendering_context ) {
946 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' ) ) {
947 return '';
948 }
949
950 // Map subscription block attributes to button block attributes
951 $button_attributes = array(
952 'text' => ! empty( $parsed_block['attrs']['submitButtonText'] ) ? sanitize_text_field( $parsed_block['attrs']['submitButtonText'] ) : __( 'Subscribe', 'jetpack' ),
953 'url' => get_post_permalink(),
954 'element' => 'a',
955 // Map background colors
956 'backgroundColor' => $parsed_block['attrs']['buttonBackgroundColor'] ?? null,
957 'customBackgroundColor' => $parsed_block['attrs']['customButtonBackgroundColor'] ?? null,
958 // Map text colors
959 'textColor' => $parsed_block['attrs']['textColor'] ?? null,
960 'customTextColor' => $parsed_block['attrs']['customTextColor'] ?? null,
961 // Map borders
962 'borderRadius' => $parsed_block['attrs']['borderRadius'] ?? 0,
963 'borderWeight' => $parsed_block['attrs']['borderWeight'] ?? 1,
964 'borderColor' => $parsed_block['attrs']['borderColor'] ?? null,
965 'customBorderColor' => $parsed_block['attrs']['customBorderColor'] ?? null,
966 // Map typography
967 'fontSize' => $parsed_block['attrs']['fontSize'] ?? null,
968 'customFontSize' => $parsed_block['attrs']['customFontSize'] ?? null,
969 // Map spacing
970 'padding' => $parsed_block['attrs']['padding'] ?? null,
971 );
972
973 // Create a mock button block structure
974 $button_parsed_block = array(
975 'attrs' => $button_attributes,
976 'email_attrs' => $parsed_block['email_attrs'] ?? array(),
977 );
978
979 // Call the Jetpack button's email rendering
980 return \Automattic\Jetpack\Extensions\Button\render_email(
981 $block_content,
982 $button_parsed_block,
983 $rendering_context
984 );
985 }
986
987 /**
988 * Gate access to posts
989 *
990 * @param string $the_content Post content.
991 *
992 * @return string
993 */
994 function add_paywall( $the_content ) {
995 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
996
997 $post_access_level = Jetpack_Memberships::get_post_access_level();
998
999 if ( Jetpack_Memberships::user_can_view_post() ) {
1000 if ( $post_access_level !== Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY ) {
1001 do_action(
1002 'earn_track_paywalled_post_view',
1003 array(
1004 'post_id' => get_the_ID(),
1005 )
1006 );
1007 }
1008 return $the_content;
1009 }
1010
1011 $paywalled_content = get_paywall_content();
1012
1013 if ( has_block( \Automattic\Jetpack\Extensions\Paywall\BLOCK_NAME ) ) {
1014 if ( strpos( $the_content, \Automattic\Jetpack\Extensions\Paywall\BLOCK_HTML ) ) {
1015 return strstr( $the_content, \Automattic\Jetpack\Extensions\Paywall\BLOCK_HTML, true ) . $paywalled_content;
1016 }
1017 // WordPress generates excerpts by either rendering or stripping blocks before invoking the `the_content` filter.
1018 // In the context of generating an excerpt, the Paywall block specifically renders THE_EXCERPT_BLOCK.
1019 if ( strpos( $the_content, \Automattic\Jetpack\Extensions\Paywall\THE_EXCERPT_BLOCK ) ) {
1020 return strstr( $the_content, \Automattic\Jetpack\Extensions\Paywall\THE_EXCERPT_BLOCK, true );
1021 }
1022 }
1023
1024 return $paywalled_content;
1025 }
1026
1027 /**
1028 * Gate access to comments. We want to close comments on private sites.
1029 *
1030 * @param bool $default_comments_open Default state of the comments_open filter.
1031 * @param int $post_id Current post id.
1032 *
1033 * @return bool
1034 */
1035 function maybe_close_comments( $default_comments_open, $post_id ) {
1036 if ( ! $default_comments_open || ! $post_id ) {
1037 return $default_comments_open;
1038 }
1039
1040 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1041 return Jetpack_Memberships::user_can_view_post();
1042 }
1043
1044 /**
1045 * Gate access to existing comments
1046 *
1047 * @param string $comment The comment.
1048 *
1049 * @return string
1050 */
1051 function maybe_gate_existing_comments( $comment ) {
1052 if ( empty( $comment ) ) {
1053 return $comment;
1054 }
1055
1056 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1057 if ( Jetpack_Memberships::user_can_view_post() ) {
1058 return $comment;
1059 }
1060 return '';
1061 }
1062
1063 /**
1064 * Is the Jetpack_Token_Subscription_Service class loaded
1065 *
1066 * @return bool
1067 */
1068 function is_jetpack_token_subscription_service_loaded(): bool {
1069 return class_exists( 'Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Jetpack_Token_Subscription_Service' );
1070 }
1071
1072 /**
1073 * Adds support for WP Super cache and Boost cache
1074 */
1075 function maybe_prevent_super_cache_caching() {
1076 // Prevents cached page to be served if the Membership cookie is present
1077 if ( is_jetpack_token_subscription_service_loaded() ) {
1078 do_action( 'wpsc_add_cookie', Jetpack_Token_Subscription_Service::JWT_AUTH_TOKEN_COOKIE_NAME );
1079 }
1080
1081 if ( is_user_auth() ) {
1082 // Do not cache the page if user is auth with Membership token
1083 if ( ! defined( 'DONOTCACHEPAGE' ) ) {
1084 define( 'DONOTCACHEPAGE', true );
1085 }
1086 }
1087 }
1088
1089 /**
1090 * Returns paywall content blocks
1091 *
1092 * @return string
1093 */
1094 function get_paywall_content() {
1095 if ( Jetpack_Memberships::user_is_pending_subscriber() ) {
1096 return get_paywall_blocks_subscribe_pending();
1097 }
1098 if ( doing_filter( 'get_the_excerpt' ) ) {
1099 return '';
1100 }
1101 return get_paywall_blocks();
1102 }
1103
1104 /**
1105 * Returns the current URL.
1106 *
1107 * TODO: Copied from https://github.com/Automattic/jetpack/blob/bb885061dc3ee7a80a78a5f0116ab3fcebfddb09/projects/packages/boost-core/src/lib/class-url.php#L39
1108 * TODO: Move to a shared package
1109 *
1110 * @return string
1111 */
1112 function get_current_url() {
1113 // Fallback to the site URL if we're unable to determine the URL from $_SERVER global.
1114 $current_url = site_url();
1115
1116 if ( isset( $_SERVER ) && is_array( $_SERVER ) ) {
1117 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitization happens at the end
1118 $scheme = isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ? 'https' : 'http';
1119 $host = ! empty( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : null;
1120 $path = ! empty( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
1121
1122 // Support for local plugin development and testing using ngrok.
1123 if ( ! empty( $_SERVER['HTTP_X_ORIGINAL_HOST'] ) && str_contains( $_SERVER['HTTP_X_ORIGINAL_HOST'], 'ngrok.io' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
1124 $host = wp_unslash( $_SERVER['HTTP_X_ORIGINAL_HOST'] );
1125 }
1126 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1127
1128 if ( $host ) {
1129 $current_url = esc_url_raw( sprintf( '%s://%s%s', $scheme, $host, $path ) );
1130 }
1131 }
1132
1133 return $current_url;
1134 }
1135
1136 /**
1137 * Get the submit button text based on the subscription status.
1138 *
1139 * @param array $data Array containing block view data.
1140 *
1141 * @return string
1142 */
1143 function get_submit_button_text( $data ) {
1144 if ( ! Jetpack_Memberships::is_current_user_subscribed() ) {
1145 return $data['submit_button_text'];
1146 }
1147 if ( ! Jetpack_Memberships::user_can_view_post() ) {
1148 return $data['submit_button_text_upgrade'];
1149 }
1150 return '✓ ' . $data['submit_button_text_subscribed'];
1151 }
1152
1153 /**
1154 * Returns true if there are no more tiers to upgrade to.
1155 *
1156 * @return boolean
1157 */
1158 function is_top_subscription() {
1159 if ( ! Jetpack_Memberships::is_current_user_subscribed() ) {
1160 return false;
1161 }
1162 if ( ! Jetpack_Memberships::user_can_view_post() ) {
1163 return false;
1164 }
1165 return true;
1166 }
1167
1168 /**
1169 * Sanitize the submit button text.
1170 *
1171 * @param string $text String containing the submit button text.
1172 *
1173 * @return string
1174 */
1175 function sanitize_submit_text( $text ) {
1176 return wp_kses(
1177 html_entity_decode( $text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
1178 Jetpack_Subscriptions_Widget::$allowed_html_tags_for_submit_button
1179 );
1180 }
1181
1182 /**
1183 * Returns paywall content blocks if user is not authenticated
1184 *
1185 * @return string
1186 */
1187 function get_paywall_blocks() {
1188 $custom_paywall = apply_filters( 'jetpack_custom_paywall_blocks', false );
1189 if ( ! empty( $custom_paywall ) ) {
1190 return $custom_paywall;
1191 }
1192
1193 if ( ! Request::is_frontend() ) { // emails
1194 return get_paywall_simple();
1195 }
1196
1197 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1198 $is_paid_post = is_paid_post();
1199 $is_paid_subscriber = Jetpack_Memberships::user_is_paid_subscriber();
1200
1201 $access_heading = $is_paid_subscriber
1202 ? esc_html__( 'Upgrade to continue reading', 'jetpack' )
1203 : esc_html__( 'Subscribe to continue reading', 'jetpack' );
1204
1205 $subscribe_text = $is_paid_post
1206 // translators: %s is the name of the site.
1207 ? (
1208 $is_paid_subscriber
1209 ? esc_html__( 'Upgrade to get access to the rest of this post and other exclusive content.', 'jetpack' )
1210 : esc_html__( 'Become a paid subscriber to get access to the rest of this post and other exclusive content.', 'jetpack' )
1211 )
1212 // translators: %s is the name of the site.
1213 : esc_html__( 'Subscribe to get access to the rest of this post and other subscriber-only content.', 'jetpack' );
1214
1215 $login_block = '';
1216
1217 if ( is_user_auth() ) {
1218 if ( ( new Host() )->is_wpcom_simple() ) {
1219 // We cannot use wpcom_logmein_redirect_url since it returns redirect URL when user is already logged in.
1220 $login_link = add_query_arg(
1221 array(
1222 'redirect_to' => rawurlencode( get_current_url() ),
1223 'blog_id' => get_current_blog_id(),
1224 ),
1225 'https://wordpress.com/log-in/link'
1226 );
1227 $switch_accounts_link = wp_logout_url( $login_link );
1228 $login_block = '<!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"}}} -->
1229 <p class="has-text-align-center" style="font-size:14px">
1230 <a href="' . $switch_accounts_link . '">' . __( 'Switch accounts', 'jetpack' ) . '</a>
1231 </p>
1232 <!-- /wp:paragraph -->';
1233 }
1234 } else {
1235 $access_question = $is_paid_post ? esc_html__( 'Already a paid subscriber?', 'jetpack' ) : esc_html__( 'Already a subscriber?', 'jetpack' );
1236 $login_block = '<!-- wp:group {"style":{"typography":{"fontSize":"14px"}},"layout":{"type":"flex","justifyContent":"center"}} -->
1237 <div class="wp-block-group" style="font-size:14px">
1238 <!-- wp:jetpack/subscriber-login {"logInLabel":"' . $access_question . '"} /-->
1239 </div>
1240 <!-- /wp:group -->';
1241 }
1242
1243 $lock_svg = plugins_url( 'images/lock-paywall.svg', JETPACK__PLUGIN_FILE );
1244
1245 return '
1246 <!-- 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"}} -->
1247 <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">
1248 <!-- wp:image {"align":"center","width":24,"height":24,"sizeSlug":"large","linkDestination":"none"} -->
1249 <figure class="wp-block-image aligncenter size-large is-resized"><img src="' . $lock_svg . '" alt="" width="24" height="24"/></figure>
1250 <!-- /wp:image -->
1251
1252 <!-- wp:heading {"textAlign":"center","style":{"typography":{"fontStyle":"normal","fontWeight":"600","fontSize":"24px"},"layout":{"selfStretch":"fit"}}} -->
1253 <h2 class="wp-block-heading has-text-align-center" style="font-size:24px;font-style:normal;font-weight:600">' . $access_heading . '</h2>
1254 <!-- /wp:heading -->
1255
1256 <!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"},"spacing":{"margin":{"top":"10px","bottom":"10px"}}}} -->
1257 <p class="has-text-align-center" style="margin-top:10px;margin-bottom:10px;font-size:14px">' . $subscribe_text . '</p>
1258 <!-- /wp:paragraph -->
1259
1260 <!-- wp:jetpack/subscriptions {"borderRadius":50,"borderColor":"primary","className":"is-style-compact","isPaidSubscriber":' . ( $is_paid_subscriber ? 'true' : 'false' ) . '} /-->
1261 ' . $login_block . '
1262 </div>
1263 <!-- /wp:group -->
1264 ';
1265 }
1266
1267 /**
1268 * Returns true if user is auth for subscriptions check, otherwise returns false.
1269 *
1270 * @return bool
1271 */
1272 function is_user_auth(): bool {
1273 if ( ( new Host() )->is_wpcom_simple() && is_user_logged_in() ) {
1274 return true;
1275 }
1276 if ( current_user_can( 'manage_options' ) ) {
1277 return true;
1278 }
1279
1280 if ( is_jetpack_token_subscription_service_loaded() ) {
1281 if ( Jetpack_Token_Subscription_Service::has_token_from_cookie() ) {
1282 return true;
1283 }
1284 }
1285 return false;
1286 }
1287
1288 /**
1289 * Returns `true` if the post is a paid post.
1290 */
1291 function is_paid_post(): bool {
1292 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1293
1294 // Make sure Stripe is connected and the post is marked for paid subscribers.
1295 if ( Jetpack_Memberships::has_connected_account() && is_jetpack_token_subscription_service_loaded() ) {
1296 return Jetpack_Memberships::get_post_access_level() === Jetpack_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS;
1297 }
1298
1299 return false;
1300 }
1301
1302 /**
1303 * Returns true if the post is a subscribers post.
1304 */
1305 function is_subscribers_post(): bool {
1306 require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
1307
1308 // Make sure Stripe is connected and the post is marked for paid subscribers.
1309 if ( Jetpack_Memberships::has_connected_account() && is_jetpack_token_subscription_service_loaded() ) {
1310 return Jetpack_Memberships::get_post_access_level() === Jetpack_Token_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS;
1311 }
1312
1313 return false;
1314 }
1315
1316 /**
1317 * Returns paywall content blocks when email confirmation is pending
1318 *
1319 * @return string
1320 */
1321 function get_paywall_blocks_subscribe_pending() {
1322 $subscribe_email = Jetpack_Memberships::get_current_user_email();
1323
1324 /** This filter is documented in \Automattic\Jetpack\Forms\ContactForm\Contact_Form */
1325 if ( is_wpcom() || false !== apply_filters( 'jetpack_auto_fill_logged_in_user', false ) ) {
1326 $current_user = wp_get_current_user();
1327 if ( ! empty( $current_user->user_email ) ) {
1328 $subscribe_email = $current_user->user_email;
1329 }
1330 }
1331
1332 $access_heading = esc_html__( 'Confirm your subscription to continue reading', 'jetpack' );
1333
1334 /* translators: %s: email address */
1335 $subscribe_text = sprintf( esc_html__( 'Head to your inbox and confirm your email address %s.', 'jetpack' ), $subscribe_email );
1336
1337 $lock_svg = plugins_url( 'images/lock-paywall.svg', JETPACK__PLUGIN_FILE );
1338
1339 return '
1340 <!-- 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"}} -->
1341 <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">
1342 <!-- wp:image {"align":"center","width":24,"height":24,"sizeSlug":"large","linkDestination":"none"} -->
1343 <figure class="wp-block-image aligncenter size-large is-resized"><img src="' . $lock_svg . '" alt="" width="24" height="24"/></figure>
1344 <!-- /wp:image -->
1345
1346 <!-- wp:heading {"textAlign":"center","style":{"typography":{"fontStyle":"normal","fontWeight":"600","fontSize":"24px", "maxWidth":"initial"},"layout":{"selfStretch":"fit"}}} -->
1347 <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>
1348 <!-- /wp:heading -->
1349
1350 <!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"14px"},"spacing":{"margin":{"top":"10px","bottom":"10px"}}}} -->
1351 <p class="has-text-align-center" style="margin-top:10px;margin-bottom:10px;font-size:14px">' . $subscribe_text . '</p>
1352 <!-- /wp:paragraph -->
1353 </div>
1354 <!-- /wp:group -->
1355 ';
1356 }
1357
1358 /**
1359 * Return content for non frontend views like Reader, emails.
1360 */
1361 function get_paywall_simple(): string {
1362 $is_paid_post = is_paid_post();
1363 $is_subscribers_post = is_subscribers_post();
1364 $is_subscriber = is_jetpack_memberships_loaded() && Jetpack_Memberships::is_current_user_subscribed();
1365 $paywall_heading = esc_html__( 'Subscribe to keep reading', 'jetpack' );
1366
1367 if ( $is_subscribers_post && ! $is_subscriber ) {
1368 $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' );
1369 $paywall_action_btn = esc_html__( 'Subscribe', 'jetpack' );
1370 } elseif ( $is_paid_post && $is_subscriber ) {
1371 $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' );
1372 $paywall_action_btn = esc_html__( 'Upgrade subscription', 'jetpack' );
1373 } else {
1374 // - For paid post when the user is not a subscriber.
1375 // - Default for all other cases.
1376 $paywall_description = esc_html__( 'Become a paid subscriber to get access to the rest of this post and other exclusive content.', 'jetpack' );
1377 $paywall_action_btn = esc_html__( 'Subscribe', 'jetpack' );
1378 }
1379
1380 return '
1381 <!-- wp:columns -->
1382 <div class="wp-block-columns jetpack-paywall-simple" style="display: inline-block; width: 90%">
1383 <!-- wp:column -->
1384 <div class="wp-block-column" style="background-color: #F6F7F7; padding: 32px; 24px;">
1385 <!-- wp:heading -->
1386 <h2 class="has-text-align-center" style="margin: 0 0 12px; font-weight: 600;">' . $paywall_heading . '</h2>
1387 <!-- /wp:heading -->
1388 <!-- wp:paragraph -->
1389 <p class="has-text-align-center"
1390 style="text-align: center;
1391 color: #50575E;
1392 font-weight: 400;
1393 font-size: 16px;
1394 font-family: \'SF Pro Text\', sans-serif;
1395 line-height: 28.8px;">
1396 ' . $paywall_description . '
1397 </p>
1398 <!-- /wp:paragraph -->
1399 <!-- wp:buttons -->
1400 <div class="wp-block-buttons" style="text-align: center;">
1401 <!-- wp:button -->
1402 <div class="wp-block-button" style="display: inline-block; margin: 10px 0; border-style: none; padding: 0;">
1403 <a href="' . esc_url( get_post_permalink() ) . '" class="wp-block-button__link wp-element-button"
1404 data-wpcom-track data-tracks-link-desc="paywall-email-click"
1405 style="display: inline-block;
1406 padding: 12px 15px;
1407 background-color: #3858e9;
1408 color: #FFFFFF;
1409 text-decoration: none;
1410 border-radius: 5px;
1411 font-family: \'SF Pro Display\', sans-serif;
1412 font-weight: 500;
1413 font-size: 16px;
1414 text-align: center;">' . $paywall_action_btn . '</a>
1415 </div>
1416 <!-- /wp:button -->
1417 </div>
1418 <!-- /wp:buttons -->
1419 </div>
1420 <!-- /wp:column -->
1421 </div>
1422 <!-- /wp:columns -->
1423 ';
1424 }
1425