<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
use Automattic\Jetpack\Assets;
if ( ! defined( 'ABSPATH' ) ) {
* Jetpack_Subscriptions_Widget main view class.
class Jetpack_Subscriptions_Widget extends WP_Widget {
const ID_BASE = 'blog_subscription';
* Track number of rendered Subscription widgets. The count is used for class names and widget IDs.
public static $instance_count = 0;
* When printing the submit button, what tags are allowed.
public static $allowed_html_tags_for_submit_button = array(
* Use this variable when printing the message after submitting an email in subscription widgets
* @var array what tags are allowed
public static $allowed_html_tags_for_message = array(
* Jetpack_Subscriptions_Widget constructor.
public function __construct() {
'classname' => 'widget_blog_subscription jetpack_subscription_widget',
'description' => __( 'Add an email signup form to allow people to subscribe to your blog.', 'jetpack' ),
'customize_selective_refresh' => true,
'show_instance_in_rest' => true,
$name = self::is_jetpack() ?
/** This filter is documented in modules/widgets/facebook-likebox.php */
apply_filters( 'jetpack_widget_name', __( 'Blog Subscriptions', 'jetpack' ) ) :
__( 'Follow Blog', 'jetpack' );
is_active_widget( false, false, $this->id_base ) ||
is_active_widget( false, false, 'monster' ) ||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_style' ) );
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_widget_in_block_editor' ) );
* Remove the "Blog Subscription" widget from the Legacy Widget block
* @param array $widget_types List of widgets that are currently removed from the Legacy Widget block.
* @return array $widget_types New list of widgets that will be removed.
public function hide_widget_in_block_editor( $widget_types ) {
$widget_types[] = self::ID_BASE;
* Enqueue the form's CSS.
public function enqueue_style() {
$path = Assets::get_file_url_for_environment(
'_inc/build/subscriptions/subscriptions.min.css',
'modules/subscriptions/subscriptions.css'
wp_style_add_data( 'jetpack-subscriptions', 'path', $path );
* Renders a full widget either within the context of WordPress widget, or in response to a shortcode.
* @param array $args Display arguments including 'before_title', 'after_title', 'before_widget', and 'after_widget'.
* @param array $instance The settings for the particular instance of the widget.
public function widget( $args, $instance ) {
if ( self::is_wpcom() && ! self::wpcom_has_status_message() && self::is_current_user_subscribed() ) {
if ( self::is_jetpack() &&
/** This filter is documented in \Automattic\Jetpack\Forms\ContactForm\Contact_Form */
false === apply_filters( 'jetpack_auto_fill_logged_in_user', false )
$current_user = wp_get_current_user();
if ( ! empty( $current_user->user_email ) ) {
$subscribe_email = esc_attr( $current_user->user_email );
$stats_action = self::is_jetpack() ? 'jetpack_subscriptions' : 'follow_blog';
/** This action is documented in modules/widgets/gravatar-profile.php */
do_action( 'jetpack_stats_extra', 'widget_view', $stats_action );
$after_widget = isset( $args['after_widget'] ) ? $args['after_widget'] : '';
$before_widget = isset( $args['before_widget'] ) ? $args['before_widget'] : '';
$instance = wp_parse_args( (array) $instance, static::defaults() );
echo $before_widget; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
self::render_widget_title( $args, $instance );
self::render_widget_status_messages( $instance );
self::render_widget_subscription_form( $args, $instance, $subscribe_email );
echo "\n" . $after_widget; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
* Prints the widget's title. If show_only_email_and_button is true, we will not show a title.
* @param array $args Display arguments including 'before_title', 'after_title', 'before_widget', and 'after_widget'.
* @param array $instance The settings for the particular instance of the widget.
public static function render_widget_title( $args, $instance ) {
$show_only_email_and_button = $instance['show_only_email_and_button'];
$before_title = isset( $args['before_title'] ) ? $args['before_title'] : '';
$after_title = isset( $args['after_title'] ) ? $args['after_title'] : '';
if ( self::is_wpcom() && ! $show_only_email_and_button ) {
if ( self::is_current_user_subscribed() ) {
if ( ! empty( $instance['title_following'] ) ) {
'%1$s<label for="subscribe-field%2$s">%3$s</label>%4$s%5$s',
$before_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
( self::$instance_count > 1 ? '-' . (int) self::$instance_count : '' ),
esc_html( $instance['title_following'] ),
$after_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} elseif ( ! empty( $instance['title'] ) ) {
'%1$s<label for="subscribe-field%2$s">%3$s</label>%4$s%5$s',
$before_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
( self::$instance_count > 1 ? '-' . (int) self::$instance_count : '' ),
esc_html( $instance['title'] ),
$after_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( self::is_jetpack() && empty( $instance['show_only_email_and_button'] ) ) {
$before_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html( $instance['title'] ),
$after_title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
* Prints the subscription block's status messages after someone has attempted to subscribe.
* Either a success message or an error message.
* @param array $instance The settings for the particular instance of the widget.
public static function render_widget_status_messages( $instance ) {
if ( self::is_jetpack() && isset( $_GET['subscribe'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Non-sensitive informational output.
$success_message = isset( $instance['success_message'] ) ? stripslashes( $instance['success_message'] ) : '';
$subscribers_total = self::fetch_subscriber_count();
switch ( $_GET['subscribe'] ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended
<p class="error"><?php esc_html_e( 'Oops! The email you used is invalid. Please try again.', 'jetpack' ); ?></p>
/* translators: 1: Link to Subscription Management page https://subscribe.wordpress.com/, 2: Description of this link. */
__( 'Oops! It seems that the email you used has opted out of subscriptions. You can manage your preferences from the <a href="%1$s" title="%2$s" target="_blank">Subscriptions Manager</a>', 'jetpack' ),
self::$allowed_html_tags_for_message
'https://subscribe.wordpress.com/',
esc_attr__( 'Subscriptions Manager', 'jetpack' )
/* translators: 1: Link to Subscription Management page https://subscribe.wordpress.com/, 2: Description of this link. */
__( 'You have already subscribed to this site. Please check your email inbox. You can manage your preferences from the <a href="%1$s" title="%2$s" target="_blank">Subscriptions Manager</a>.', 'jetpack' ),
self::$allowed_html_tags_for_message
'https://subscribe.wordpress.com/',
esc_attr__( 'Subscriptions Manager', 'jetpack' )
case 'many_pending_subs':
/* translators: 1: Link to Subscription Management page https://subscribe.wordpress.com/, 2: Description of this link */
__( 'Oops! It seems you have several subscriptions pending confirmation. You can confirm or unsubscribe some from the <a href="%1$s" title="%2$s" target="_blank" rel="noopener noreferrer">Subscriptions Manager</a> before adding more.', 'jetpack' ),
self::$allowed_html_tags_for_message
'https://subscribe.wordpress.com/',
esc_attr__( 'Subscriptions Manager', 'jetpack' )
/* translators: 1: Link to Subscription Management page https://subscribe.wordpress.com/, 2: Description of this link */
__( 'It seems you already tried to subscribe with this email, but have not confirmed from the email link we sent. Please check your email inbox to confirm or you can manage your preferences from the <a href="%1$s" title="%2$s" target="_blank" rel="noopener noreferrer">Subscriptions Manager</a>.', 'jetpack' ),
self::$allowed_html_tags_for_message
'https://subscribe.wordpress.com/',
esc_attr__( 'Subscriptions Manager', 'jetpack' )
<div class="success"><?php echo wp_kses( wpautop( str_replace( '[total-subscribers]', number_format_i18n( $subscribers_total ), $success_message ) ), 'post' ); ?></div>
<p class="error"><?php esc_html_e( 'Oops! There was an error when subscribing. Please try again.', 'jetpack' ); ?></p>
if ( self::is_wpcom() && self::wpcom_has_status_message() ) {
switch ( $_GET['blogsub'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
$message = __( 'Thank you! You can now check your email to confirm your subscription.', 'jetpack' );
$message = __( 'Sorry but this email has been blocked for this subscription. <a href="https://en.support.wordpress.com/contact/">Contact us</a> if needed.', 'jetpack' );
$message = __( 'Oops! It seems you have several subscriptions pending confirmation. You can confirm or unsubscribe some from the <a href="https://subscribe.wordpress.com/">Subscriptions Manager</a> before adding more.', 'jetpack' );
/* translators: %s is a URL */
$message = sprintf( __( 'Sorry but this email has been blocked. It has too many subscriptions pending confirmation. Please confirm or unsubscribe some from the <a href="%s">Subscriptions Manager</a>.', 'jetpack' ), 'https://subscribe.wordpress.com/' );
$message = __( 'Hey! You were already subscribed.', 'jetpack' );
$message = __( 'It seems you already tried to subscribe. We just sent you another email so you can confirm the subscription.', 'jetpack' );
$message = __( 'Cool! You are now subscribed. Now you can check your email for more details and how to manage the subscription.', 'jetpack' );
$border_color = isset( $themecolors['border'] ) ? " #{$themecolors['border']}" : '';
$redirect_fragment = self::get_redirect_fragment();
'<div id="%1$s" class="jetpack-sub-notification">%3$s</div>',
esc_attr( $redirect_fragment ),
esc_attr( $border_color ),
* Generates the redirect fragment used after form submission.
* @param string $id is the specific id that will appear in the redirect fragment. If none is provided self::$instance_count will be used.
protected static function get_redirect_fragment( $id = null ) {
return 'subscribe-blog' . ( self::$instance_count > 1 ? '-' . self::$instance_count : '' );
return 'subscribe-blog-' . $id;
* Renders a form allowing folks to subscribe to the blog.
* @param array $args Display arguments including 'before_title', 'after_title', 'before_widget', and 'after_widget'.
* @param array $instance The settings for the particular instance of the widget.
* @param string $subscribe_email The email to use to prefill the form.
public static function render_widget_subscription_form( $args, $instance, $subscribe_email ) {
$show_only_email_and_button = $instance['show_only_email_and_button'];
$show_subscribers_total = (bool) $instance['show_subscribers_total'];
$subscribers_total = self::fetch_subscriber_count();
$subscribe_text = empty( $instance['show_only_email_and_button'] ) ?
wp_kses_post( stripslashes( $instance['subscribe_text'] ) ) :
$referer = esc_url_raw( ( is_ssl() ? 'https' : 'http' ) . '://' . ( isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : '' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '' ) );
$widget_id = ! empty( $args['widget_id'] ) ? $args['widget_id'] : self::$instance_count;
$subscribe_button = ! empty( $instance['submit_button_text'] ) ? $instance['submit_button_text'] : $instance['subscribe_button'];
$subscribe_placeholder = isset( $instance['subscribe_placeholder'] ) ? stripslashes( $instance['subscribe_placeholder'] ) : '';
$submit_button_classes = isset( $instance['submit_button_classes'] ) ? 'wp-block-button__link ' . $instance['submit_button_classes'] : 'wp-block-button__link';
$submit_button_styles = isset( $instance['submit_button_styles'] ) ? $instance['submit_button_styles'] : '';
$submit_button_wrapper_styles = isset( $instance['submit_button_wrapper_styles'] ) ? $instance['submit_button_wrapper_styles'] : '';
$email_field_classes = isset( $instance['email_field_classes'] ) ? $instance['email_field_classes'] : '';
$email_field_styles = isset( $instance['email_field_styles'] ) ? $instance['email_field_styles'] : '';
// We need to include those in case Jetpack blocks are disabled
require_once JETPACK__PLUGIN_DIR . 'modules/memberships/class-jetpack-memberships.php';
require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
$post_access_level = \Jetpack_Memberships::get_post_access_level();
if ( self::is_wpcom() && ! self::wpcom_has_status_message() ) {
$url = defined( 'SUBSCRIBE_BLOG_URL' ) ? SUBSCRIBE_BLOG_URL : '';
$form_id = self::get_redirect_fragment();
<div class="wp-block-jetpack-subscriptions__container">
action="<?php echo esc_url( $url ); ?>"
data-blog="<?php echo esc_attr( get_current_blog_id() ); ?>"
data-post_access_level="<?php echo esc_attr( $post_access_level ); ?>"
id="<?php echo esc_attr( $form_id ); ?>"
if ( ! $show_only_email_and_button ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpautop( $subscribe_text );
$email_field_id = 'subscribe-field';
$email_field_id .= self::$instance_count > 1
? '-' . self::$instance_count
$label_field_id = $email_field_id . '-label';
id="<?php echo esc_attr( $label_field_id ); ?>"
for="<?php echo esc_attr( $email_field_id ); ?>"
class="screen-reader-text"
<?php echo esc_html__( 'Email Address:', 'jetpack' ); ?>
( ! empty( $email_field_classes )
? 'class="' . esc_attr( $email_field_classes ) . '"'
( ! empty( $email_field_styles )
? esc_attr( $email_field_styles )
: 'width: 95%; padding: 1px 10px'
( empty( $subscribe_placeholder ) ? esc_attr__( 'Enter your email address', 'jetpack' ) : esc_attr( $subscribe_placeholder ) ),
esc_attr( $email_field_id )
<?php if ( ! empty( $submit_button_wrapper_styles ) ) { ?>
style="<?php echo esc_attr( $submit_button_wrapper_styles ); ?>"
<input type="hidden" name="action" value="subscribe"/>
<input type="hidden" name="blog_id" value="<?php echo (int) $current_blog->blog_id; ?>"/>
<input type="hidden" name="source" value="<?php echo esc_url( $referer ); ?>"/>
<input type="hidden" name="sub-type" value="<?php echo esc_attr( $source ); ?>"/>
<input type="hidden" name="redirect_fragment" value="<?php echo esc_attr( $form_id ); ?>"/>
<?php wp_nonce_field( 'blogsub_subscribe_' . $current_blog->blog_id, '_wpnonce', false ); ?>
<?php if ( ! empty( $submit_button_classes ) ) { ?>
class="<?php echo esc_attr( $submit_button_classes ); ?>"
<?php if ( ! empty( $submit_button_styles ) ) { ?>
style="<?php echo esc_attr( $submit_button_styles ); ?>"
html_entity_decode( $subscribe_button, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ),
self::$allowed_html_tags_for_submit_button
<?php if ( $show_subscribers_total && $subscribers_total > 0 ) { ?>
<div class="wp-block-jetpack-subscriptions__subscount">
echo esc_html( Jetpack_Memberships::get_join_others_text( $subscribers_total ) );
if ( self::is_jetpack() ) {
* Filter the subscription form's ID prefix.