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