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