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