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