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