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