2022-04-02 10:26:41 +02:00
< ? php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName)
2019-11-15 23:26:29 +01:00
/**
2023-09-26 10:24:36 +02:00
* Module Name : Newsletter
2019-11-15 23:26:29 +01:00
* Module Description : Let visitors subscribe to new posts and comments via email
* Sort Order : 9
* Recommendation Order : 8
* First Introduced : 1.2
* Requires Connection : Yes
2021-04-27 08:32:47 +02:00
* Requires User Connection : Yes
2019-11-15 23:26:29 +01:00
* Auto Activate : No
* Module Tags : Social
* Feature : Engagement
2023-12-07 09:44:11 +01:00
* Additional Search Queries : subscriptions , subscription , email , follow , followers , subscribers , signup , newsletter , creator
2019-11-15 23:26:29 +01:00
*/
2023-04-26 17:39:43 +02:00
// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
2025-02-28 08:42:11 +01:00
use Automattic\Jetpack\Admin_UI\Admin_Menu ;
2023-09-26 10:24:36 +02:00
use Automattic\Jetpack\Connection\Manager as Connection_Manager ;
2020-09-15 14:30:05 +02:00
use Automattic\Jetpack\Connection\XMLRPC_Async_Call ;
2023-09-26 10:24:36 +02:00
use Automattic\Jetpack\Redirect ;
use Automattic\Jetpack\Status ;
use Automattic\Jetpack\Status\Host ;
2025-04-25 12:30:07 +02:00
use Automattic\Jetpack\Subscribers_Dashboard\Dashboard as Subscribers_Dashboard ;
2020-09-15 14:30:05 +02:00
2019-11-15 23:26:29 +01:00
add_action ( 'jetpack_modules_loaded' , 'jetpack_subscriptions_load' );
2023-12-07 09:44:11 +01:00
// Loads the User Content Link Redirection feature.
require_once __DIR__ . '/subscriptions/jetpack-user-content-link-redirection.php' ;
2022-04-02 10:26:41 +02:00
/**
* Loads the Subscriptions module .
*/
2019-11-15 23:26:29 +01:00
function jetpack_subscriptions_load () {
Jetpack :: enable_module_configurable ( __FILE__ );
}
/**
* Cherry picks keys from `$_SERVER` array .
*
* @ since 6.0 . 0
*
* @ return array An array of server data .
*/
function jetpack_subscriptions_cherry_pick_server_data () {
$data = array ();
foreach ( $_SERVER as $key => $value ) {
2023-12-07 09:44:11 +01:00
if ( ! is_string ( $value ) || str_starts_with ( $key , 'HTTP_COOKIE' ) ) {
2019-11-15 23:26:29 +01:00
continue ;
}
2023-12-07 09:44:11 +01:00
if ( str_starts_with ( $key , 'HTTP_' ) || in_array ( $key , array ( 'REMOTE_ADDR' , 'REQUEST_URI' , 'DOCUMENT_URI' ), true ) ) {
2019-11-15 23:26:29 +01:00
$data [ $key ] = $value ;
}
}
return $data ;
}
2022-04-02 10:26:41 +02:00
/**
* Main class file for the Subscriptions module .
*/
2019-11-15 23:26:29 +01:00
class Jetpack_Subscriptions {
2022-04-02 10:26:41 +02:00
/**
* Whether Jetpack has been instantiated or not .
*
* @ var bool
*/
2019-11-15 23:26:29 +01:00
public $jetpack = false ;
2022-04-02 10:26:41 +02:00
/**
* Hash of the siteurl option .
*
* @ var string
*/
2019-11-15 23:26:29 +01:00
public static $hash ;
/**
* Singleton
2022-04-02 10:26:41 +02:00
*
2019-11-15 23:26:29 +01:00
* @ static
*/
2022-04-02 10:26:41 +02:00
public static function init () {
2019-11-15 23:26:29 +01:00
static $instance = false ;
2022-04-02 10:26:41 +02:00
if ( ! $instance ) {
$instance = new Jetpack_Subscriptions ();
2019-11-15 23:26:29 +01:00
}
return $instance ;
}
2022-04-02 10:26:41 +02:00
/**
* Jetpack_Subscriptions constructor .
*/
public function __construct () {
2019-11-15 23:26:29 +01:00
$this -> jetpack = Jetpack :: init ();
// Don't use COOKIEHASH as it could be shared across installs && is non-unique in multisite.
2022-04-02 10:26:41 +02:00
// @see: https://twitter.com/nacin/status/378246957451333632 .
2019-11-15 23:26:29 +01:00
self :: $hash = md5 ( get_option ( 'siteurl' ) );
add_filter ( 'jetpack_xmlrpc_methods' , array ( $this , 'xmlrpc_methods' ) );
// @todo remove sync from subscriptions and move elsewhere...
2022-04-02 10:26:41 +02:00
// Add Configuration Page.
2019-11-15 23:26:29 +01:00
add_action ( 'admin_init' , array ( $this , 'configure' ) );
2022-04-02 10:26:41 +02:00
// Catch subscription widget submits.
if ( isset ( $_REQUEST [ 'jetpack_subscriptions_widget' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce checked in widget_submit() for logged in users.
2019-11-15 23:26:29 +01:00
add_action ( 'template_redirect' , array ( $this , 'widget_submit' ) );
2022-04-02 10:26:41 +02:00
}
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
// Set up the comment subscription checkboxes.
2019-11-15 23:26:29 +01:00
add_filter ( 'comment_form_submit_field' , array ( $this , 'comment_subscribe_init' ), 10 , 2 );
// Catch comment posts and check for subscriptions.
add_action ( 'comment_post' , array ( $this , 'comment_subscribe_submit' ), 50 , 2 );
2022-04-02 10:26:41 +02:00
// Adds post meta checkbox in the post submit metabox.
2019-11-15 23:26:29 +01:00
add_action ( 'post_submitbox_misc_actions' , array ( $this , 'subscription_post_page_metabox' ) );
add_action ( 'transition_post_status' , array ( $this , 'maybe_send_subscription_email' ), 10 , 3 );
add_filter ( 'jetpack_published_post_flags' , array ( $this , 'set_post_flags' ), 10 , 2 );
add_filter ( 'post_updated_messages' , array ( $this , 'update_published_message' ), 18 , 1 );
2020-01-26 12:53:20 +01:00
2020-03-03 18:49:45 +01:00
// Set "social_notifications_subscribe" option during the first-time activation.
2022-04-02 10:26:41 +02:00
add_action ( 'jetpack_activate_module_subscriptions' , array ( $this , 'set_social_notifications_subscribe' ) );
2022-12-15 17:41:53 +01:00
// Hide subscription messaging in Publish panel for posts that were published in the past
add_action ( 'init' , array ( $this , 'register_post_meta' ), 20 );
add_action ( 'transition_post_status' , array ( $this , 'maybe_set_first_published_status' ), 10 , 3 );
2023-09-26 10:24:36 +02:00
// Add Subscribers menu to Jetpack navigation.
add_action ( 'jetpack_admin_menu' , array ( $this , 'add_subscribers_menu' ) );
2024-04-17 11:32:24 +02:00
// Customize the configuration URL to lead to the Subscriptions settings.
add_filter (
'jetpack_module_configuration_url_subscriptions' ,
function () {
return Jetpack :: admin_url ( array ( 'page' => 'jetpack#/newsletter' ) );
}
);
// Track categories created through the category editor page
add_action ( 'wp_ajax_add-tag' , array ( $this , 'track_newsletter_category_creation' ), 1 );
2025-04-25 12:30:07 +02:00
$subscribers_dashboard = new Subscribers_Dashboard ();
$subscribers_dashboard :: init ();
2019-11-15 23:26:29 +01:00
}
/**
* Jetpack_Subscriptions :: xmlrpc_methods ()
*
* Register subscriptions methods with the Jetpack XML - RPC server .
2022-04-02 10:26:41 +02:00
*
* @ param array $methods Methods being registered .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function xmlrpc_methods ( $methods ) {
2019-11-15 23:26:29 +01:00
return array_merge (
$methods ,
array (
'jetpack.subscriptions.subscribe' => array ( $this , 'subscribe' ),
)
);
}
2022-04-02 10:26:41 +02:00
/**
2019-11-15 23:26:29 +01:00
* Disable Subscribe on Single Post
* Register post meta
*/
2022-04-02 10:26:41 +02:00
public function subscription_post_page_metabox () {
2019-11-15 23:26:29 +01:00
if (
/**
* Filter whether or not to show the per - post subscription option .
*
* @ module subscriptions
*
* @ since 3.7 . 0
*
* @ param bool true = show checkbox option on all new posts | false = hide the option .
*/
2022-04-02 10:26:41 +02:00
! apply_filters ( 'jetpack_allow_per_post_subscriptions' , false ) ) {
2019-11-15 23:26:29 +01:00
return ;
}
if ( has_filter ( 'jetpack_subscriptions_exclude_these_categories' ) || has_filter ( 'jetpack_subscriptions_include_only_these_categories' ) ) {
return ;
}
global $post ;
$disable_subscribe_value = get_post_meta ( $post -> ID , '_jetpack_dont_email_post_to_subs' , true );
// only show checkbox if post hasn't been published and is a 'post' post type.
2022-04-02 10:26:41 +02:00
if ( get_post_status ( $post -> ID ) !== 'publish' && get_post_type ( $post -> ID ) === 'post' ) :
// Nonce it.
2019-11-15 23:26:29 +01:00
wp_nonce_field ( 'disable_subscribe' , 'disable_subscribe_nonce' );
?>
< div class = " misc-pub-section " >
2022-04-02 10:26:41 +02:00
< label for = " _jetpack_dont_email_post_to_subs " >< ? php esc_html_e ( 'Jetpack Subscriptions:' , 'jetpack' ); ?> </label><br>
2019-11-15 23:26:29 +01:00
< input type = " checkbox " name = " _jetpack_dont_email_post_to_subs " id = " jetpack-per-post-subscribe " value = " 1 " < ? php checked ( $disable_subscribe_value , 1 , true ); ?> />
2022-04-02 10:26:41 +02:00
< ? php esc_html_e ( 'Don’t send this to subscribers' , 'jetpack' ); ?>
2019-11-15 23:26:29 +01:00
</ div >
2022-04-02 10:26:41 +02:00
< ? php
endif ;
2019-11-15 23:26:29 +01:00
}
/**
* Checks whether or not the post should be emailed to subscribers
*
* It checks for the following things in order :
* - Usage of filter jetpack_subscriptions_exclude_these_categories
* - Usage of filter jetpack_subscriptions_include_only_these_categories
* - Existence of the per - post checkbox option
*
* Only one of these can be used at any given time .
*
2022-04-02 10:26:41 +02:00
* @ param string $new_status Tthe " new " post status of the transition when saved .
* @ param string $old_status The " old " post status of the transition when saved .
* @ param object $post obj The post object .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function maybe_send_subscription_email ( $new_status , $old_status , $post ) {
2019-11-15 23:26:29 +01:00
if ( defined ( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return ;
}
2022-04-02 10:26:41 +02:00
// Make sure that the checkbox is preseved.
2022-06-16 14:01:47 +02:00
if ( ! empty ( $_POST [ 'disable_subscribe_nonce' ] ) && wp_verify_nonce ( $_POST [ 'disable_subscribe_nonce' ], 'disable_subscribe' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP Core doesn't unslash or sanitize nonces either.
2019-11-15 23:26:29 +01:00
$set_checkbox = isset ( $_POST [ '_jetpack_dont_email_post_to_subs' ] ) ? 1 : 0 ;
update_post_meta ( $post -> ID , '_jetpack_dont_email_post_to_subs' , $set_checkbox );
}
}
2022-04-02 10:26:41 +02:00
/**
* Message used when publishing a post .
*
* @ param array $messages Message array for a post .
*/
public function update_published_message ( $messages ) {
2019-11-15 23:26:29 +01:00
global $post ;
if ( ! $this -> should_email_post_to_subscribers ( $post ) ) {
return $messages ;
}
2022-04-02 10:26:41 +02:00
$view_post_link_html = sprintf (
' <a href="%1$s">%2$s</a>' ,
2019-11-15 23:26:29 +01:00
esc_url ( get_permalink ( $post ) ),
__ ( 'View post' , 'jetpack' )
);
$messages [ 'post' ][ 6 ] = sprintf (
/* translators: Message shown after a post is published */
esc_html__ ( 'Post published and sending emails to subscribers.' , 'jetpack' )
2022-04-02 10:26:41 +02:00
) . $view_post_link_html ;
2019-11-15 23:26:29 +01:00
return $messages ;
}
2022-04-02 10:26:41 +02:00
/**
* Determine if a post should notifiy subscribers via email .
*
* @ param object $post The post .
*/
2019-11-15 23:26:29 +01:00
public function should_email_post_to_subscribers ( $post ) {
$should_email = true ;
if ( get_post_meta ( $post -> ID , '_jetpack_dont_email_post_to_subs' , true ) ) {
return false ;
}
2022-04-02 10:26:41 +02:00
// Only posts are currently supported.
if ( 'post' !== $post -> post_type ) {
2019-11-15 23:26:29 +01:00
return false ;
}
// Private posts are not sent to subscribers.
if ( 'private' === $post -> post_status ) {
return false ;
}
/**
* Array of categories that will never trigger subscription emails .
*
* Will not send subscription emails from any post from within these categories .
*
* @ module subscriptions
*
* @ since 3.7 . 0
*
* @ param array $args Array of category slugs or ID ' s .
*/
$excluded_categories = apply_filters ( 'jetpack_subscriptions_exclude_these_categories' , array () );
2022-04-02 10:26:41 +02:00
// Never email posts from these categories.
2019-11-15 23:26:29 +01:00
if ( ! empty ( $excluded_categories ) && in_category ( $excluded_categories , $post -> ID ) ) {
$should_email = false ;
}
/**
* ONLY send subscription emails for these categories
*
* Will ONLY send subscription emails to these categories .
*
* @ module subscriptions
*
* @ since 3.7 . 0
*
* @ param array $args Array of category slugs or ID ' s .
*/
$only_these_categories = apply_filters ( 'jetpack_subscriptions_exclude_all_categories_except' , array () );
2022-04-02 10:26:41 +02:00
// Only emails posts from these categories.
2019-11-15 23:26:29 +01:00
if ( ! empty ( $only_these_categories ) && ! in_category ( $only_these_categories , $post -> ID ) ) {
$should_email = false ;
}
return $should_email ;
}
2022-04-02 10:26:41 +02:00
/**
* Retrieve which flags should be added to a particular post .
*
* @ param array $flags Flags to be added .
* @ param object $post A post object .
*/
public function set_post_flags ( $flags , $post ) {
2019-11-15 23:26:29 +01:00
$flags [ 'send_subscription' ] = $this -> should_email_post_to_subscribers ( $post );
return $flags ;
}
/**
* Jetpack_Subscriptions :: configure ()
*
* Jetpack Subscriptions configuration screen .
*/
2022-04-02 10:26:41 +02:00
public function configure () {
// Create the section.
2019-11-15 23:26:29 +01:00
add_settings_section (
'jetpack_subscriptions' ,
__ ( 'Jetpack Subscriptions Settings' , 'jetpack' ),
array ( $this , 'subscriptions_settings_section' ),
'discussion'
);
2022-04-02 10:26:41 +02:00
/** Subscribe to Posts */
2019-11-15 23:26:29 +01:00
add_settings_field (
'jetpack_subscriptions_post_subscribe' ,
__ ( 'Follow Blog' , 'jetpack' ),
array ( $this , 'subscription_post_subscribe_setting' ),
'discussion' ,
'jetpack_subscriptions'
);
register_setting (
'discussion' ,
'stb_enabled'
);
2022-04-02 10:26:41 +02:00
/** Subscribe to Comments */
2019-11-15 23:26:29 +01:00
add_settings_field (
'jetpack_subscriptions_comment_subscribe' ,
__ ( 'Follow Comments' , 'jetpack' ),
array ( $this , 'subscription_comment_subscribe_setting' ),
'discussion' ,
'jetpack_subscriptions'
);
register_setting (
'discussion' ,
'stc_enabled'
);
2024-04-17 11:32:24 +02:00
/** Email me whenever: Someone subscribes to my blog */
2020-01-26 12:53:20 +01:00
/* @since 8.1 */
add_settings_section (
'notifications_section' ,
2024-04-17 11:32:24 +02:00
__ ( 'Someone subscribes to my blog' , 'jetpack' ),
2020-01-26 12:53:20 +01:00
array ( $this , 'social_notifications_subscribe_section' ),
'discussion'
);
add_settings_field (
'jetpack_subscriptions_social_notifications_subscribe' ,
__ ( 'Email me whenever' , 'jetpack' ),
array ( $this , 'social_notifications_subscribe_field' ),
'discussion' ,
'notifications_section'
);
register_setting (
'discussion' ,
'social_notifications_subscribe' ,
array ( $this , 'social_notifications_subscribe_validate' )
);
2019-11-15 23:26:29 +01:00
}
/**
2022-04-02 10:26:41 +02:00
* Discussions setting section blurb .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function subscriptions_settings_section () {
?>
< p id = " jetpack-subscriptions-settings " >< ? php esc_html_e ( 'Change whether your visitors can subscribe to your posts or comments or both.' , 'jetpack' ); ?> </p>
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
< ? php
2019-11-15 23:26:29 +01:00
}
/**
2022-04-02 10:26:41 +02:00
* Post Subscriptions Toggle .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function subscription_post_subscribe_setting () {
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
$stb_enabled = get_option ( 'stb_enabled' , 1 );
?>
2019-11-15 23:26:29 +01:00
< p class = " description " >
< input type = " checkbox " name = " stb_enabled " id = " jetpack-post-subscribe " value = " 1 " < ? php checked ( $stb_enabled , 1 ); ?> />
2022-04-02 10:26:41 +02:00
< ? php
echo wp_kses (
__ (
" Show a <em>'follow blog'</em> option in the comment form " ,
'jetpack'
),
array ( 'em' => array () )
);
?>
2019-11-15 23:26:29 +01:00
</ p >
2022-04-02 10:26:41 +02:00
< ? php
2019-11-15 23:26:29 +01:00
}
/**
2022-04-02 10:26:41 +02:00
* Comments Subscriptions Toggle .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function subscription_comment_subscribe_setting () {
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
$stc_enabled = get_option ( 'stc_enabled' , 1 );
?>
2019-11-15 23:26:29 +01:00
< p class = " description " >
< input type = " checkbox " name = " stc_enabled " id = " jetpack-comment-subscribe " value = " 1 " < ? php checked ( $stc_enabled , 1 ); ?> />
2022-04-02 10:26:41 +02:00
< ? php
echo wp_kses (
__ (
" Show a <em>'follow comments'</em> option in the comment form " ,
'jetpack'
),
array ( 'em' => array () )
);
?>
2019-11-15 23:26:29 +01:00
</ p >
2022-04-02 10:26:41 +02:00
< ? php
2019-11-15 23:26:29 +01:00
}
2020-01-26 12:53:20 +01:00
/**
2024-04-17 11:32:24 +02:00
* Someone subscribes to my blog section
2020-01-26 12:53:20 +01:00
*
* @ since 8.1
*/
public function social_notifications_subscribe_section () {
2022-04-02 10:26:41 +02:00
// Atypical usage here. We emit jquery to move subscribe notification checkbox to be with the rest of the email notification settings.
2020-01-26 12:53:20 +01:00
?>
< script type = " text/javascript " >
jQuery ( function ( $ ) {
var table = $ ( '#social_notifications_subscribe' ) . parents ( 'table:first' ),
header = table . prevAll ( 'h2:first' ),
newParent = $ ( '#moderation_notify' ) . parent ( 'label' ) . parent ();
if ( ! table . length || ! header . length || ! newParent . length ) {
return ;
}
newParent . append ( '<br/>' ) . append ( table . end () . parent ( 'label' ) . siblings () . andSelf () );
header . remove ();
table . remove ();
} );
</ script >
< ? php
}
/**
2024-04-17 11:32:24 +02:00
* Someone subscribes to my blog Toggle
2020-01-26 12:53:20 +01:00
*
* @ since 8.1
*/
public function social_notifications_subscribe_field () {
2020-11-18 09:10:44 +01:00
$checked = ( int ) ( 'on' === get_option ( 'social_notifications_subscribe' , 'on' ) );
2020-01-26 12:53:20 +01:00
?>
< label >
< input type = " checkbox " name = " social_notifications_subscribe " id = " social_notifications_subscribe " value = " 1 " < ? php checked ( $checked ); ?> />
< ? php
/* translators: this is a label for a setting that starts with "Email me whenever" */
2024-04-17 11:32:24 +02:00
esc_html_e ( 'Someone subscribes to my blog' , 'jetpack' );
2020-01-26 12:53:20 +01:00
?>
</ label >
< ? php
}
/**
2024-04-17 11:32:24 +02:00
* Validate " Someone subscribes to my blog " option
2020-01-26 12:53:20 +01:00
*
* @ since 8.1
*
* @ param String $input the input string to be validated .
* @ return string on | off
*/
public function social_notifications_subscribe_validate ( $input ) {
// If it's not set (was unchecked during form submission) or was set to off (during option update), return 'off'.
if ( ! $input || 'off' === $input ) {
return 'off' ;
}
// Otherwise we return 'on'.
return 'on' ;
}
2019-11-15 23:26:29 +01:00
/**
* Jetpack_Subscriptions :: subscribe ()
*
* Send a synchronous XML - RPC subscribe to blog posts or subscribe to post comments request .
*
2022-04-02 10:26:41 +02:00
* @ param string $email being subscribed .
* @ param array $post_ids ( optional ) defaults to 0 for blog posts only : array of post IDs to subscribe to blog ' s posts .
2019-11-15 23:26:29 +01:00
* @ param bool $async ( optional ) Should the subscription be performed asynchronously ? Defaults to true .
2022-04-02 10:26:41 +02:00
* @ param array $extra_data Additional data passed to the `jetpack.subscribeToSite` call .
2019-11-15 23:26:29 +01:00
*
2020-09-15 14:30:05 +02:00
* @ return true | WP_Error true on success
2022-04-02 10:26:41 +02:00
* invalid_email : not a valid email address
* invalid_post_id : not a valid post ID
* unknown_post_id : unknown post
* not_subscribed : strange error . Jetpack servers at WordPress . com could subscribe the email .
* disabled : Site owner has disabled subscriptions .
* active : Already subscribed .
* pending : Tried to subscribe before but the confirmation link is never clicked . No confirmation email is sent .
* unknown : strange error . Jetpack servers at WordPress . com returned something malformed .
* unknown_status : strange error . Jetpack servers at WordPress . com returned something I didn ' t understand .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function subscribe ( $email , $post_ids = 0 , $async = true , $extra_data = array () ) {
if ( ! is_email ( $email ) ) {
2020-09-15 14:30:05 +02:00
return new WP_Error ( 'invalid_email' );
2019-11-15 23:26:29 +01:00
}
2022-04-02 10:26:41 +02:00
if ( ! $async ) {
2019-11-15 23:26:29 +01:00
$xml = new Jetpack_IXR_ClientMulticall ();
}
foreach ( ( array ) $post_ids as $post_id ) {
$post_id = ( int ) $post_id ;
if ( $post_id < 0 ) {
2020-09-15 14:30:05 +02:00
return new WP_Error ( 'invalid_post_id' );
2022-04-02 10:26:41 +02:00
} elseif ( $post_id && ! get_post ( $post_id ) ) {
2020-09-15 14:30:05 +02:00
return new WP_Error ( 'unknown_post_id' );
2019-11-15 23:26:29 +01:00
}
if ( $async ) {
2020-09-15 14:30:05 +02:00
XMLRPC_Async_Call :: add_call ( 'jetpack.subscribeToSite' , 0 , $email , $post_id , serialize ( $extra_data ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2019-11-15 23:26:29 +01:00
} else {
2022-04-02 10:26:41 +02:00
$xml -> addCall ( 'jetpack.subscribeToSite' , $email , $post_id , serialize ( $extra_data ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2019-11-15 23:26:29 +01:00
}
}
if ( $async ) {
return ;
}
2022-04-02 10:26:41 +02:00
// Call.
2019-11-15 23:26:29 +01:00
$xml -> query ();
if ( $xml -> isError () ) {
return $xml -> get_jetpack_error ();
}
$responses = $xml -> getResponse ();
$r = array ();
foreach ( ( array ) $responses as $response ) {
if ( isset ( $response [ 'faultCode' ] ) || isset ( $response [ 'faultString' ] ) ) {
$r [] = $xml -> get_jetpack_error ( $response [ 'faultCode' ], $response [ 'faultString' ] );
continue ;
}
2022-04-02 10:26:41 +02:00
if ( ! is_array ( $response [ 0 ] ) || empty ( $response [ 0 ][ 'status' ] ) ) {
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'unknown' );
2019-11-15 23:26:29 +01:00
continue ;
}
switch ( $response [ 0 ][ 'status' ] ) {
2020-03-03 18:49:45 +01:00
case 'error' :
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'not_subscribed' );
2020-03-03 18:49:45 +01:00
continue 2 ;
case 'disabled' :
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'disabled' );
2020-03-03 18:49:45 +01:00
continue 2 ;
case 'active' :
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'active' );
2020-03-03 18:49:45 +01:00
continue 2 ;
case 'confirming' :
$r [] = true ;
continue 2 ;
case 'pending' :
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'pending' );
2020-03-03 18:49:45 +01:00
continue 2 ;
default :
2020-09-15 14:30:05 +02:00
$r [] = new WP_Error ( 'unknown_status' , ( string ) $response [ 0 ][ 'status' ] );
2020-03-03 18:49:45 +01:00
continue 2 ;
2019-11-15 23:26:29 +01:00
}
}
return $r ;
}
/**
* Jetpack_Subscriptions :: widget_submit ()
*
* When a user submits their email via the blog subscription widget , check the details and call the subsribe () method .
*/
2022-04-02 10:26:41 +02:00
public function widget_submit () {
2019-11-15 23:26:29 +01:00
// Check the nonce.
2023-12-07 09:44:11 +01:00
if ( ! wp_verify_nonce ( isset ( $_REQUEST [ '_wpnonce' ] ) ? sanitize_key ( $_REQUEST [ '_wpnonce' ] ) : '' , 'blogsub_subscribe_' . \Jetpack_Options :: get_option ( 'id' ) ) ) {
return false ;
2019-11-15 23:26:29 +01:00
}
2022-04-02 10:26:41 +02:00
if ( empty ( $_REQUEST [ 'email' ] ) || ! is_string ( $_REQUEST [ 'email' ] ) ) {
2019-11-15 23:26:29 +01:00
return false ;
2022-04-02 10:26:41 +02:00
}
2019-11-15 23:26:29 +01:00
$redirect_fragment = false ;
if ( isset ( $_REQUEST [ 'redirect_fragment' ] ) ) {
2022-06-16 14:01:47 +02:00
$redirect_fragment = preg_replace ( '/[^a-z0-9_-]/i' , '' , $_REQUEST [ 'redirect_fragment' ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is manually unslashing and sanitizing.
2019-11-15 23:26:29 +01:00
}
2022-04-02 10:26:41 +02:00
if ( ! $redirect_fragment || ! is_string ( $redirect_fragment ) ) {
2019-11-15 23:26:29 +01:00
$redirect_fragment = 'subscribe-blog' ;
}
2022-04-02 10:26:41 +02:00
$subscribe = self :: subscribe (
2022-06-16 14:01:47 +02:00
isset ( $_REQUEST [ 'email' ] ) ? wp_unslash ( $_REQUEST [ 'email' ] ) : null , // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated inside self::subscribe().
2022-04-02 10:26:41 +02:00
0 ,
false ,
array (
'source' => 'widget' ,
'widget-in-use' => is_active_widget ( false , false , 'blog_subscription' , true ) ? 'yes' : 'no' ,
'comment_status' => '' ,
'server_data' => jetpack_subscriptions_cherry_pick_server_data (),
)
2019-11-15 23:26:29 +01:00
);
if ( is_wp_error ( $subscribe ) ) {
$error = $subscribe -> get_error_code ();
} else {
$error = false ;
foreach ( $subscribe as $response ) {
if ( is_wp_error ( $response ) ) {
$error = $response -> get_error_code ();
break ;
}
}
}
switch ( $error ) {
case false :
$result = 'success' ;
break ;
case 'invalid_email' :
$result = $error ;
break ;
case 'blocked_email' :
$result = 'opted_out' ;
break ;
case 'active' :
$result = 'already' ;
break ;
2020-03-03 18:49:45 +01:00
case 'flooded_email' :
$result = 'many_pending_subs' ;
break ;
case 'pending' :
$result = 'pending' ;
break ;
2019-11-15 23:26:29 +01:00
default :
$result = 'error' ;
break ;
}
$redirect = add_query_arg ( 'subscribe' , $result );
/**
* Fires on each subscription form submission .
*
* @ module subscriptions
*
* @ since 3.7 . 0
*
* @ param string $result Result of form submission : success , invalid_email , already , error .
*/
do_action ( 'jetpack_subscriptions_form_submission' , $result );
wp_safe_redirect ( " $redirect # $redirect_fragment " );
2025-02-28 08:42:11 +01:00
exit ( 0 );
2019-11-15 23:26:29 +01:00
}
/**
* Jetpack_Subscriptions :: comment_subscribe_init ()
*
* Set up and add the comment subscription checkbox to the comment form .
*
* @ param string $submit_button HTML markup for the submit field .
*/
2022-04-02 10:26:41 +02:00
public function comment_subscribe_init ( $submit_button ) {
2019-11-15 23:26:29 +01:00
global $post ;
2024-02-16 11:03:01 +01:00
// Subscriptions are only available for posts so far.
if ( ! $post || 'post' !== $post -> post_type ) {
return $submit_button ;
}
2019-11-15 23:26:29 +01:00
$comments_checked = '' ;
$blog_checked = '' ;
// Check for a comment / blog submission and set a cookie to retain the setting and check the boxes.
if ( isset ( $_COOKIE [ 'jetpack_comments_subscribe_' . self :: $hash . '_' . $post -> ID ] ) ) {
$comments_checked = ' checked="checked"' ;
}
if ( isset ( $_COOKIE [ 'jetpack_blog_subscribe_' . self :: $hash ] ) ) {
$blog_checked = ' checked="checked"' ;
}
2022-04-02 10:26:41 +02:00
// Some themes call this function, don't show the checkbox again.
2019-11-15 23:26:29 +01:00
remove_action ( 'comment_form' , 'subscription_comment_form' );
2022-04-02 10:26:41 +02:00
// Check if Mark Jaquith's Subscribe to Comments plugin is active - if so, suppress Jetpack checkbox.
2019-11-15 23:26:29 +01:00
$str = '' ;
2022-04-02 10:26:41 +02:00
if ( false === has_filter ( 'comment_form' , 'show_subscription_checkbox' ) && 1 === ( int ) get_option ( 'stc_enabled' , 1 ) && empty ( $post -> post_password ) && 'post' === get_post_type () ) {
// Subscribe to comments checkbox.
$str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_comments" id="subscribe_comments" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $comments_checked . ' /> ' ;
2019-11-15 23:26:29 +01:00
$comment_sub_text = __ ( 'Notify me of follow-up comments by email.' , 'jetpack' );
2022-04-02 10:26:41 +02:00
$str .= '<label class="subscribe-label" id="subscribe-label" for="subscribe_comments">' . esc_html (
2019-11-15 23:26:29 +01:00
/**
* Filter the Subscribe to comments text appearing below the comment form .
*
* @ module subscriptions
*
* @ since 3.4 . 0
*
* @ param string $comment_sub_text Subscribe to comments text .
*/
apply_filters ( 'jetpack_subscribe_comment_label' , $comment_sub_text )
) . '</label>' ;
$str .= '</p>' ;
}
2022-04-02 10:26:41 +02:00
if ( 1 === ( int ) get_option ( 'stb_enabled' , 1 ) ) {
// Subscribe to blog checkbox.
$str .= '<p class="comment-subscription-form"><input type="checkbox" name="subscribe_blog" id="subscribe_blog" value="subscribe" style="width: auto; -moz-appearance: checkbox; -webkit-appearance: checkbox;"' . $blog_checked . ' /> ' ;
2019-11-15 23:26:29 +01:00
$blog_sub_text = __ ( 'Notify me of new posts by email.' , 'jetpack' );
2022-04-02 10:26:41 +02:00
$str .= '<label class="subscribe-label" id="subscribe-blog-label" for="subscribe_blog">' . esc_html (
2019-11-15 23:26:29 +01:00
/**
* Filter the Subscribe to blog text appearing below the comment form .
*
* @ module subscriptions
*
* @ since 3.4 . 0
*
* @ param string $comment_sub_text Subscribe to blog text .
*/
apply_filters ( 'jetpack_subscribe_blog_label' , $blog_sub_text )
) . '</label>' ;
$str .= '</p>' ;
}
/**
* Filter the output of the subscription options appearing below the comment form .
*
* @ module subscriptions
*
* @ since 1.2 . 0
*
* @ param string $str Comment Subscription form HTML output .
*/
$str = apply_filters ( 'jetpack_comment_subscription_form' , $str );
return $str . $submit_button ;
}
/**
* Jetpack_Subscriptions :: comment_subscribe_init ()
*
* When a user checks the comment subscribe box and submits a comment , subscribe them to the comment thread .
2022-04-02 10:26:41 +02:00
*
* @ param int | string $comment_id Comment thread being subscribed to .
* @ param string $approved Comment status .
2019-11-15 23:26:29 +01:00
*/
2022-04-02 10:26:41 +02:00
public function comment_subscribe_submit ( $comment_id , $approved ) {
2019-11-15 23:26:29 +01:00
if ( 'spam' === $approved ) {
return ;
}
$comment = get_comment ( $comment_id );
2022-04-02 10:26:41 +02:00
if ( ! $comment ) {
return ;
}
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
// Set cookies for this post/comment.
$this -> set_cookies ( isset ( $_REQUEST [ 'subscribe_comments' ] ), $comment -> comment_post_ID , isset ( $_REQUEST [ 'subscribe_blog' ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
if ( ! isset ( $_REQUEST [ 'subscribe_comments' ] ) && ! isset ( $_REQUEST [ 'subscribe_blog' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2019-11-15 23:26:29 +01:00
return ;
2022-04-02 10:26:41 +02:00
}
2019-11-15 23:26:29 +01:00
$post_ids = array ();
2022-04-02 10:26:41 +02:00
if ( isset ( $_REQUEST [ 'subscribe_comments' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2019-11-15 23:26:29 +01:00
$post_ids [] = $comment -> comment_post_ID ;
2022-04-02 10:26:41 +02:00
}
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
if ( isset ( $_REQUEST [ 'subscribe_blog' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2019-11-15 23:26:29 +01:00
$post_ids [] = 0 ;
2022-04-02 10:26:41 +02:00
}
2019-11-15 23:26:29 +01:00
2022-04-02 10:26:41 +02:00
$result = self :: subscribe (
$comment -> comment_author_email ,
$post_ids ,
true ,
array (
'source' => 'comment-form' ,
'widget-in-use' => is_active_widget ( false , false , 'blog_subscription' , true ) ? 'yes' : 'no' ,
'comment_status' => $approved ,
'server_data' => jetpack_subscriptions_cherry_pick_server_data (),
)
2019-11-15 23:26:29 +01:00
);
/**
* Fires on each comment subscription form submission .
*
* @ module subscriptions
*
* @ since 5.5 . 0
*
* @ param NULL | WP_Error $result Result of form submission : NULL on success , WP_Error otherwise .
2020-05-06 17:20:49 +02:00
* @ param array $post_ids An array of post IDs that the user subscribed to , 0 means blog subscription .
2019-11-15 23:26:29 +01:00
*/
do_action ( 'jetpack_subscriptions_comment_form_submission' , $result , $post_ids );
}
/**
* Jetpack_Subscriptions :: set_cookies ()
*
* Set a cookie to save state on the comment and post subscription checkboxes .
*
* @ param bool $subscribe_to_post Whether the user chose to subscribe to subsequent comments on this post .
2022-04-02 10:26:41 +02:00
* @ param int $post_id If $subscribe_to_post is true , the post ID they ' ve subscribed to .
2019-11-15 23:26:29 +01:00
* @ param bool $subscribe_to_blog Whether the user chose to subscribe to all new posts on the blog .
*/
2022-04-02 10:26:41 +02:00
public function set_cookies ( $subscribe_to_post = false , $post_id = null , $subscribe_to_blog = false ) {
2020-11-18 09:10:44 +01:00
$post_id = ( int ) $post_id ;
2019-11-15 23:26:29 +01:00
/** This filter is already documented in core/wp-includes/comment-functions.php */
2022-04-02 10:26:41 +02:00
$cookie_lifetime = apply_filters ( 'comment_cookie_lifetime' , 30000000 );
2019-11-15 23:26:29 +01:00
/**
* Filter the Jetpack Comment cookie path .
*
* @ module subscriptions
*
* @ since 2.5 . 0
*
* @ param string COOKIEPATH Cookie path .
*/
2022-04-02 10:26:41 +02:00
$cookie_path = apply_filters ( 'jetpack_comment_cookie_path' , COOKIEPATH );
2019-11-15 23:26:29 +01:00
/**
* Filter the Jetpack Comment cookie domain .
*
* @ module subscriptions
*
* @ since 2.5 . 0
*
* @ param string COOKIE_DOMAIN Cookie domain .
*/
2022-04-02 10:26:41 +02:00
$cookie_domain = apply_filters ( 'jetpack_comment_cookie_domain' , COOKIE_DOMAIN );
2019-11-15 23:26:29 +01:00
if ( $subscribe_to_post && $post_id >= 0 ) {
2022-06-16 14:01:47 +02:00
setcookie ( 'jetpack_comments_subscribe_' . self :: $hash . '_' . $post_id , 1 , time () + $cookie_lifetime , $cookie_path , $cookie_domain , is_ssl (), true );
2019-11-15 23:26:29 +01:00
} else {
2022-06-16 14:01:47 +02:00
setcookie ( 'jetpack_comments_subscribe_' . self :: $hash . '_' . $post_id , '' , time () - 3600 , $cookie_path , $cookie_domain , is_ssl (), true );
2019-11-15 23:26:29 +01:00
}
if ( $subscribe_to_blog ) {
2022-06-16 14:01:47 +02:00
setcookie ( 'jetpack_blog_subscribe_' . self :: $hash , 1 , time () + $cookie_lifetime , $cookie_path , $cookie_domain , is_ssl (), true );
2019-11-15 23:26:29 +01:00
} else {
2022-06-16 14:01:47 +02:00
setcookie ( 'jetpack_blog_subscribe_' . self :: $hash , '' , time () - 3600 , $cookie_path , $cookie_domain , is_ssl (), true );
2019-11-15 23:26:29 +01:00
}
}
2020-01-26 12:53:20 +01:00
/**
2020-03-03 18:49:45 +01:00
* Set the social_notifications_subscribe option to `off` when the Subscriptions module is activated in the first time .
2020-01-26 12:53:20 +01:00
*
* @ since 8.1
*
2022-04-02 10:26:41 +02:00
* @ return void
2020-01-26 12:53:20 +01:00
*/
2022-04-02 10:26:41 +02:00
public function set_social_notifications_subscribe () {
2020-03-03 18:49:45 +01:00
if ( false === get_option ( 'social_notifications_subscribe' ) ) {
add_option ( 'social_notifications_subscribe' , 'off' );
}
2020-01-26 12:53:20 +01:00
}
2022-12-15 17:41:53 +01:00
/**
* Save a flag when a post was ever published .
*
* It saves the post meta when the post was published and becomes a draft .
* Then this meta is used to hide subscription messaging in Publish panel .
*
* @ param string $new_status Tthe " new " post status of the transition when saved .
* @ param string $old_status The " old " post status of the transition when saved .
* @ param object $post obj The post object .
*/
public function maybe_set_first_published_status ( $new_status , $old_status , $post ) {
$was_post_ever_published = get_post_meta ( $post -> ID , '_jetpack_post_was_ever_published' , true );
if ( ! $was_post_ever_published && 'publish' === $old_status && 'draft' === $new_status ) {
update_post_meta ( $post -> ID , '_jetpack_post_was_ever_published' , true );
}
}
/**
* Checks if the current user can publish posts .
*
* @ return bool
*/
public function first_published_status_meta_auth_callback () {
2025-02-28 08:42:11 +01:00
/**
* Filter the capability to view if a post was ever published in the Subscription Module .
*
* @ module subscriptions
*
* @ since 13.4
*
* @ param string $capability User capability needed to view if a post was ever published . Default to publish_posts .
*/
$capability = apply_filters ( 'jetpack_subscriptions_post_was_ever_published_capability' , 'publish_posts' );
if ( current_user_can ( $capability ) ) {
2022-12-15 17:41:53 +01:00
return true ;
}
return false ;
}
/**
* Registers the 'post_was_ever_published' post meta for use in the REST API .
*/
public function register_post_meta () {
$jetpack_post_was_ever_published = array (
'type' => 'boolean' ,
'description' => __ ( 'Whether the post was ever published.' , 'jetpack' ),
'single' => true ,
'default' => false ,
'show_in_rest' => array (
'name' => 'jetpack_post_was_ever_published' ,
),
'auth_callback' => array ( $this , 'first_published_status_meta_auth_callback' ),
);
register_meta ( 'post' , '_jetpack_post_was_ever_published' , $jetpack_post_was_ever_published );
}
2023-09-26 10:24:36 +02:00
/**
* Create a Subscribers menu displayed on self - hosted sites .
*
* - It is not displayed on WordPress . com sites .
* - It directs you to Calypso to the existing Subscribers page .
*
* @ return void
*/
public function add_subscribers_menu () {
2025-04-25 12:30:07 +02:00
/**
* Enables the new in development subscribers in wp - admin dashboard .
*
* @ since 9.5 . 0
*
* @ param bool If the new dashboard is enabled . Default false .
*/
if ( apply_filters ( 'jetpack_wp_admin_subscriber_management_enabled' , false ) ) {
return ;
}
2023-09-26 10:24:36 +02:00
/*
2024-04-17 11:32:24 +02:00
* Do not display any menu on WoA and WordPress . com Simple sites ( unless Classic wp - admin is enabled ) .
2023-09-26 10:24:36 +02:00
* They already get a menu item under Users via nav - unification .
*/
2024-04-17 11:32:24 +02:00
if ( ( new Host () ) -> is_wpcom_platform () && get_option ( 'wpcom_admin_interface' ) !== 'wp-admin' ) {
2023-09-26 10:24:36 +02:00
return ;
}
$status = new Status ();
/*
* Do not display if we ' re in Offline mode ,
* or if the user is not connected .
*/
if (
$status -> is_offline_mode ()
|| ! ( new Connection_Manager ( 'jetpack' ) ) -> is_user_connected ()
) {
return ;
}
2024-02-16 11:03:01 +01:00
$blog_id = Connection_Manager :: get_site_id ( true );
2023-09-26 10:24:36 +02:00
$link = Redirect :: get_url (
2025-02-28 08:42:11 +01:00
'jetpack-menu-jetpack-manage-subscribers' ,
2024-02-16 11:03:01 +01:00
array ( 'site' => $blog_id ? $blog_id : $status -> get_site_suffix () )
2023-09-26 10:24:36 +02:00
);
2025-02-28 08:42:11 +01:00
Admin_Menu :: add_menu (
__ ( 'Subscribers' , 'jetpack' ),
2023-09-26 10:24:36 +02:00
__ ( 'Subscribers' , 'jetpack' ) . ' <span class="dashicons dashicons-external"></span>' ,
'manage_options' ,
esc_url ( $link ),
2025-02-28 08:42:11 +01:00
null ,
11
2023-09-26 10:24:36 +02:00
);
}
2024-04-17 11:32:24 +02:00
/**
* Record tracks event if categories is created when user enters
* the edit category page through the newsletter settings page .
*
* @ return void
*/
public function track_newsletter_category_creation () {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( empty ( $_POST [ '_wp_http_referer' ] ) ) {
return ;
}
if ( strpos ( sanitize_url ( wp_unslash ( $_POST [ '_wp_http_referer' ] ) ), 'referer=newsletter-categories' ) > - 1 ) {
$parent = filter_var ( empty ( $_POST [ 'parent' ] ) ? 0 : wp_unslash ( $_POST [ 'parent' ] ), FILTER_SANITIZE_NUMBER_INT );
$is_child_category = $parent > 0 ;
$tracking = new Automattic\Jetpack\Tracking ();
$tracking -> tracks_record_event (
wp_get_current_user (),
'jetpack_newsletter_add_category' ,
array (
'is_child_category' => $is_child_category ,
)
);
}
}
2019-11-15 23:26:29 +01:00
}
Jetpack_Subscriptions :: init ();
2022-04-02 10:26:41 +02:00
require __DIR__ . '/subscriptions/views.php' ;
2023-09-26 10:24:36 +02:00
require __DIR__ . '/subscriptions/subscribe-modal/class-jetpack-subscribe-modal.php' ;
2025-02-28 08:42:11 +01:00
require __DIR__ . '/subscriptions/subscribe-overlay/class-jetpack-subscribe-overlay.php' ;
require __DIR__ . '/subscriptions/subscribe-floating-button/class-jetpack-subscribe-floating-button.php' ;