$value ) { if ( ! is_string( $value ) || str_starts_with( $key, 'HTTP_COOKIE' ) ) { continue; } if ( str_starts_with( $key, 'HTTP_' ) || in_array( $key, array( 'REMOTE_ADDR', 'REQUEST_URI', 'DOCUMENT_URI' ), true ) ) { $data[ $key ] = $value; } } return $data; } /** * Main class file for the Subscriptions module. */ class Jetpack_Subscriptions { /** * Whether Jetpack has been instantiated or not. * * @var bool */ public $jetpack = false; /** * Hash of the siteurl option. * * @var string */ public static $hash; /** * Singleton * * @static */ public static function init() { static $instance = false; if ( ! $instance ) { $instance = new Jetpack_Subscriptions(); } return $instance; } /** * Jetpack_Subscriptions constructor. */ public function __construct() { $this->jetpack = Jetpack::init(); // Don't use COOKIEHASH as it could be shared across installs && is non-unique in multisite. // @see: https://twitter.com/nacin/status/378246957451333632 . self::$hash = md5( get_option( 'siteurl' ) ); add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) ); // @todo remove sync from subscriptions and move elsewhere... // Add Configuration Page. add_action( 'admin_init', array( $this, 'configure' ) ); // 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. add_action( 'template_redirect', array( $this, 'widget_submit' ) ); } // Set up the comment subscription checkboxes. 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 ); // Adds post meta checkbox in the post submit metabox. 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 ); // Set "social_notifications_subscribe" option during the first-time activation. add_action( 'jetpack_activate_module_subscriptions', array( $this, 'set_social_notifications_subscribe' ) ); // 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 ); // Add Subscribers menu to Jetpack navigation. add_action( 'jetpack_admin_menu', array( $this, 'add_subscribers_menu' ) ); // 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 ); $subscribers_dashboard = new Subscribers_Dashboard(); $subscribers_dashboard::init(); } /** * Jetpack_Subscriptions::xmlrpc_methods() * * Register subscriptions methods with the Jetpack XML-RPC server. * * @param array $methods Methods being registered. */ public function xmlrpc_methods( $methods ) { return array_merge( $methods, array( 'jetpack.subscriptions.subscribe' => array( $this, 'subscribe' ), ) ); } /** * Disable Subscribe on Single Post * Register post meta */ public function subscription_post_page_metabox() { 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. */ ! apply_filters( 'jetpack_allow_per_post_subscriptions', false ) ) { 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. if ( get_post_status( $post->ID ) !== 'publish' && get_post_type( $post->ID ) === 'post' ) : // Nonce it. wp_nonce_field( 'disable_subscribe', 'disable_subscribe_nonce' ); ?>
/> 'follow blog' option in the comment form", 'jetpack' ), array( 'em' => array() ) ); ?>
/> 'follow comments' option in the comment form", 'jetpack' ), array( 'em' => array() ) ); ?>
addCall( 'jetpack.subscribeToSite', $email, $post_id, serialize( $extra_data ) ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize } } if ( $async ) { return; } // Call. $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; } if ( ! is_array( $response[0] ) || empty( $response[0]['status'] ) ) { $r[] = new WP_Error( 'unknown' ); continue; } switch ( $response[0]['status'] ) { case 'error': $r[] = new WP_Error( 'not_subscribed' ); continue 2; case 'disabled': $r[] = new WP_Error( 'disabled' ); continue 2; case 'active': $r[] = new WP_Error( 'active' ); continue 2; case 'confirming': $r[] = true; continue 2; case 'pending': $r[] = new WP_Error( 'pending' ); continue 2; default: $r[] = new WP_Error( 'unknown_status', (string) $response[0]['status'] ); continue 2; } } 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. */ public function widget_submit() { // Check the nonce. if ( ! wp_verify_nonce( isset( $_REQUEST['_wpnonce'] ) ? sanitize_key( $_REQUEST['_wpnonce'] ) : '', 'blogsub_subscribe_' . \Jetpack_Options::get_option( 'id' ) ) ) { return false; } if ( empty( $_REQUEST['email'] ) || ! is_string( $_REQUEST['email'] ) ) { return false; } $redirect_fragment = false; if ( isset( $_REQUEST['redirect_fragment'] ) ) { $redirect_fragment = preg_replace( '/[^a-z0-9_-]/i', '', $_REQUEST['redirect_fragment'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is manually unslashing and sanitizing. } if ( ! $redirect_fragment || ! is_string( $redirect_fragment ) ) { $redirect_fragment = 'subscribe-blog'; } $subscribe = self::subscribe( isset( $_REQUEST['email'] ) ? wp_unslash( $_REQUEST['email'] ) : null, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated inside self::subscribe(). 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(), ) ); 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; case 'flooded_email': $result = 'many_pending_subs'; break; case 'pending': $result = 'pending'; break; 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" ); exit( 0 ); } /** * 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. */ public function comment_subscribe_init( $submit_button ) { global $post; // Subscriptions are only available for posts so far. if ( ! $post || 'post' !== $post->post_type ) { return $submit_button; } $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"'; } // Some themes call this function, don't show the checkbox again. remove_action( 'comment_form', 'subscription_comment_form' ); // Check if Mark Jaquith's Subscribe to Comments plugin is active - if so, suppress Jetpack checkbox. $str = ''; 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 .= ''; $comment_sub_text = __( 'Notify me of follow-up comments by email.', 'jetpack' ); $str .= ''; $str .= '
'; } if ( 1 === (int) get_option( 'stb_enabled', 1 ) ) { // Subscribe to blog checkbox. $str .= ''; $blog_sub_text = __( 'Notify me of new posts by email.', 'jetpack' ); $str .= ''; $str .= '
'; } /** * 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. * * @param int|string $comment_id Comment thread being subscribed to. * @param string $approved Comment status. */ public function comment_subscribe_submit( $comment_id, $approved ) { if ( 'spam' === $approved ) { return; } $comment = get_comment( $comment_id ); if ( ! $comment ) { return; } // 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 if ( ! isset( $_REQUEST['subscribe_comments'] ) && ! isset( $_REQUEST['subscribe_blog'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $post_ids = array(); if ( isset( $_REQUEST['subscribe_comments'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_ids[] = $comment->comment_post_ID; } if ( isset( $_REQUEST['subscribe_blog'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_ids[] = 0; } $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(), ) ); /** * 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. * @param array $post_ids An array of post IDs that the user subscribed to, 0 means blog subscription. */ 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. * @param int $post_id If $subscribe_to_post is true, the post ID they've subscribed to. * @param bool $subscribe_to_blog Whether the user chose to subscribe to all new posts on the blog. */ public function set_cookies( $subscribe_to_post = false, $post_id = null, $subscribe_to_blog = false ) { $post_id = (int) $post_id; /** This filter is already documented in core/wp-includes/comment-functions.php */ $cookie_lifetime = apply_filters( 'comment_cookie_lifetime', 30000000 ); /** * Filter the Jetpack Comment cookie path. * * @module subscriptions * * @since 2.5.0 * * @param string COOKIEPATH Cookie path. */ $cookie_path = apply_filters( 'jetpack_comment_cookie_path', COOKIEPATH ); /** * Filter the Jetpack Comment cookie domain. * * @module subscriptions * * @since 2.5.0 * * @param string COOKIE_DOMAIN Cookie domain. */ $cookie_domain = apply_filters( 'jetpack_comment_cookie_domain', COOKIE_DOMAIN ); if ( $subscribe_to_post && $post_id >= 0 ) { setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain, is_ssl(), true ); } else { setcookie( 'jetpack_comments_subscribe_' . self::$hash . '_' . $post_id, '', time() - 3600, $cookie_path, $cookie_domain, is_ssl(), true ); } if ( $subscribe_to_blog ) { setcookie( 'jetpack_blog_subscribe_' . self::$hash, 1, time() + $cookie_lifetime, $cookie_path, $cookie_domain, is_ssl(), true ); } else { setcookie( 'jetpack_blog_subscribe_' . self::$hash, '', time() - 3600, $cookie_path, $cookie_domain, is_ssl(), true ); } } /** * Set the social_notifications_subscribe option to `off` when the Subscriptions module is activated in the first time. * * @since 8.1 * * @return void */ public function set_social_notifications_subscribe() { if ( false === get_option( 'social_notifications_subscribe' ) ) { add_option( 'social_notifications_subscribe', 'off' ); } } /** * 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() { /** * 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 ) ) { 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 ); } /** * 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() { /** * 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; } /* * Do not display any menu on WoA and WordPress.com Simple sites (unless Classic wp-admin is enabled). * They already get a menu item under Users via nav-unification. */ if ( ( new Host() )->is_wpcom_platform() && get_option( 'wpcom_admin_interface' ) !== 'wp-admin' ) { 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; } $blog_id = Connection_Manager::get_site_id( true ); $link = Redirect::get_url( 'jetpack-menu-jetpack-manage-subscribers', array( 'site' => $blog_id ? $blog_id : $status->get_site_suffix() ) ); Admin_Menu::add_menu( __( 'Subscribers', 'jetpack' ), __( 'Subscribers', 'jetpack' ) . ' ', 'manage_options', esc_url( $link ), null, 11 ); } /** * 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, ) ); } } } Jetpack_Subscriptions::init(); require __DIR__ . '/subscriptions/views.php'; require __DIR__ . '/subscriptions/subscribe-modal/class-jetpack-subscribe-modal.php'; require __DIR__ . '/subscriptions/subscribe-overlay/class-jetpack-subscribe-overlay.php'; require __DIR__ . '/subscriptions/subscribe-floating-button/class-jetpack-subscribe-floating-button.php';