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