From 5fcfb9ae54a74f4887e400eef6321517cf3bc0f9 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 24 Nov 2025 14:33:29 -0500 Subject: [PATCH 01/19] add subscribers list on publish --- .../plugins/jetpack/modules/subscriptions.php | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 3a1876a62696..26cdc58dc6ae 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -16,6 +16,7 @@ // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files. use Automattic\Jetpack\Admin_UI\Admin_Menu; +use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Connection\XMLRPC_Async_Call; use Automattic\Jetpack\Redirect; @@ -132,6 +133,8 @@ public function __construct() { add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 ); + add_action( 'jetpack_published_post', array( $this, 'store_subscribers_when_sent' ), 10, 3 ); + add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 18, 1 ); // Set "social_notifications_subscribe" option during the first-time activation. @@ -987,6 +990,188 @@ public function register_post_meta() { ); register_meta( 'post', '_jetpack_post_was_ever_published', $jetpack_post_was_ever_published ); + + $jetpack_newsletter_subscribers_when_sent = array( + 'type' => 'array', + 'description' => __( 'List of subscribers when the post was first emailed. Used for debugging purposes.', 'jetpack' ), + 'single' => true, + 'default' => array(), + 'show_in_rest' => array( + 'name' => 'jetpack_newsletter_subscribers_when_sent', + ), + 'auth_callback' => array( $this, 'first_published_status_meta_auth_callback' ), + ); + + register_meta( 'post', '_jetpack_newsletter_subscribers_when_sent', $jetpack_newsletter_subscribers_when_sent ); + } + + /** + * Store the list of subscribers when a post is first emailed. + * + * This method is called when a post is published and emails are sent to subscribers. + * It stores the subscriber count and metadata in post meta for debugging purposes. + * + * @since $$next-version$$ + * + * @param int $post_ID Post ID. + * @param array $flags Post flags including send_subscription. + * @param WP_Post $post Post object. + * + * @return void + */ + public function store_subscribers_when_sent( $post_ID, $flags, $post ) { + // Only store if emails are being sent. + if ( ! isset( $flags['send_subscription'] ) || ! $flags['send_subscription'] ) { + return; + } + + // Only store once - check if we've already stored subscribers for this post. + $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', true ); + if ( ! empty( $existing_subscribers ) ) { + return; + } + + // Only store for posts. + if ( 'post' !== $post->post_type ) { + return; + } + + // Fetch subscriber data from WordPress.com API. + $subscriber_data = $this->get_subscriber_data(); + + // Store subscriber data with timestamp. + $data_to_store = array( + 'timestamp' => current_time( 'mysql' ), + 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, + 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, + 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, + 'subscriber_list' => isset( $subscriber_data['subscriber_list'] ) && is_array( $subscriber_data['subscriber_list'] ) ? $subscriber_data['subscriber_list'] : array(), + ); + + update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); + } + + /** + * Get subscriber data from WordPress.com API. + * + * @since $$next-version$$ + * + * @return array Subscriber data including counts and email list. + */ + private function get_subscriber_data() { + $subscriber_data = array( + 'email_subscribers' => 0, + 'paid_subscribers' => 0, + 'all_subscribers' => 0, + 'subscriber_list' => array(), + ); + + // Only fetch if Jetpack is connected. + if ( ! Jetpack::is_connection_ready() ) { + return $subscriber_data; + } + + $site_id = Jetpack_Options::get_option( 'id' ); + + // First, get subscriber counts from stats endpoint. + $stats_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); + $stats_response = Client::wpcom_json_api_request_as_blog( + $stats_path, + '2', + array(), + null, + 'wpcom' + ); + + if ( ! is_wp_error( $stats_response ) ) { + $stats_code = wp_remote_retrieve_response_code( $stats_response ); + if ( 200 === $stats_code ) { + $subscriber_counts = json_decode( wp_remote_retrieve_body( $stats_response ), true ); + if ( is_array( $subscriber_counts ) ) { + if ( isset( $subscriber_counts['counts']['email_subscribers'] ) ) { + $subscriber_data['email_subscribers'] = (int) $subscriber_counts['counts']['email_subscribers']; + } + if ( isset( $subscriber_counts['counts']['paid_subscribers'] ) ) { + $subscriber_data['paid_subscribers'] = (int) $subscriber_counts['counts']['paid_subscribers']; + } + if ( isset( $subscriber_counts['counts']['all_subscribers'] ) ) { + $subscriber_data['all_subscribers'] = (int) $subscriber_counts['counts']['all_subscribers']; + } + } + } + } + + // Fetch the actual subscriber list with emails. + $subscriber_emails = $this->fetch_all_subscribers( $site_id ); + $subscriber_data['subscriber_list'] = $subscriber_emails; + + return $subscriber_data; + } + + /** + * Fetch all subscribers from WordPress.com API with pagination. + * + * @since $$next-version$$ + * + * @param int $site_id Site ID. + * @return array Array of subscriber email addresses. + */ + private function fetch_all_subscribers( $site_id ) { + $subscriber_emails = array(); + $page = 1; + $per_page = 100; // Maximum per page to minimize requests. + + while ( true ) { + $api_path = sprintf( + '/sites/%d/subscribers/?page=%d&per_page=%d&filter=email_subscriber', + $site_id, + $page, + $per_page + ); + + $response = Client::wpcom_json_api_request_as_blog( + $api_path, + '2', + array(), + null, + 'wpcom' + ); + + if ( is_wp_error( $response ) ) { + break; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + if ( 200 !== $response_code ) { + break; + } + + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $response_body ) ) { + break; + } + + // Extract emails from subscribers array. + if ( isset( $response_body['subscribers'] ) && is_array( $response_body['subscribers'] ) ) { + foreach ( $response_body['subscribers'] as $subscriber ) { + if ( isset( $subscriber['email'] ) && is_email( $subscriber['email'] ) ) { + $subscriber_emails[] = sanitize_email( $subscriber['email'] ); + } + } + } + + // Check if there are more pages. + $total = isset( $response_body['total'] ) ? (int) $response_body['total'] : 0; + $total_pages = isset( $response_body['total_pages'] ) ? (int) $response_body['total_pages'] : 1; + + if ( $page >= $total_pages || count( $subscriber_emails ) >= $total ) { + break; + } + + $page++; + } + + return $subscriber_emails; } /** From 88513651ef6f20adfac222280f2befcf81ac68e9 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 25 Nov 2025 14:06:43 -0500 Subject: [PATCH 02/19] add emails and if paid subscriber --- .../plugins/jetpack/modules/subscriptions.php | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 26cdc58dc6ae..faf28e3dce6c 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1013,9 +1013,9 @@ public function register_post_meta() { * * @since $$next-version$$ * - * @param int $post_ID Post ID. - * @param array $flags Post flags including send_subscription. - * @param WP_Post $post Post object. + * @param int $post_ID Post ID. + * @param array $flags Post flags including send_subscription. + * @param WP_Post $post Post object. * * @return void */ @@ -1041,11 +1041,11 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { // Store subscriber data with timestamp. $data_to_store = array( - 'timestamp' => current_time( 'mysql' ), - 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, - 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, - 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, - 'subscriber_list' => isset( $subscriber_data['subscriber_list'] ) && is_array( $subscriber_data['subscriber_list'] ) ? $subscriber_data['subscriber_list'] : array(), + 'timestamp' => current_time( 'mysql' ), + 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, + 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, + 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, + 'subscriber_list' => isset( $subscriber_data['subscriber_list'] ) && is_array( $subscriber_data['subscriber_list'] ) ? $subscriber_data['subscriber_list'] : array(), ); update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); @@ -1074,7 +1074,7 @@ private function get_subscriber_data() { $site_id = Jetpack_Options::get_option( 'id' ); // First, get subscriber counts from stats endpoint. - $stats_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); + $stats_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); $stats_response = Client::wpcom_json_api_request_as_blog( $stats_path, '2', @@ -1102,7 +1102,7 @@ private function get_subscriber_data() { } // Fetch the actual subscriber list with emails. - $subscriber_emails = $this->fetch_all_subscribers( $site_id ); + $subscriber_emails = $this->fetch_all_subscribers( $site_id ); $subscriber_data['subscriber_list'] = $subscriber_emails; return $subscriber_data; @@ -1114,12 +1114,12 @@ private function get_subscriber_data() { * @since $$next-version$$ * * @param int $site_id Site ID. - * @return array Array of subscriber email addresses. + * @return array Array of subscriber data, each containing 'email' and 'is_paid' keys. */ private function fetch_all_subscribers( $site_id ) { $subscriber_emails = array(); - $page = 1; - $per_page = 100; // Maximum per page to minimize requests. + $page = 1; + $per_page = 100; // Maximum per page to minimize requests. while ( true ) { $api_path = sprintf( @@ -1151,24 +1151,38 @@ private function fetch_all_subscribers( $site_id ) { break; } - // Extract emails from subscribers array. + // Extract subscriber data from subscribers array. if ( isset( $response_body['subscribers'] ) && is_array( $response_body['subscribers'] ) ) { foreach ( $response_body['subscribers'] as $subscriber ) { - if ( isset( $subscriber['email'] ) && is_email( $subscriber['email'] ) ) { - $subscriber_emails[] = sanitize_email( $subscriber['email'] ); + if ( isset( $subscriber['email_address'] ) && is_email( $subscriber['email_address'] ) ) { + // Determine if subscriber has an active paid plan. + $is_paid = false; + if ( isset( $subscriber['plans'] ) && is_array( $subscriber['plans'] ) ) { + foreach ( $subscriber['plans'] as $plan ) { + if ( isset( $plan['status'] ) && 'active' === $plan['status'] ) { + $is_paid = true; + break; + } + } + } + + $subscriber_emails[] = array( + 'email' => sanitize_email( $subscriber['email_address'] ), + 'is_paid' => $is_paid, + ); } } } // Check if there are more pages. - $total = isset( $response_body['total'] ) ? (int) $response_body['total'] : 0; + $total = isset( $response_body['total'] ) ? (int) $response_body['total'] : 0; $total_pages = isset( $response_body['total_pages'] ) ? (int) $response_body['total_pages'] : 1; if ( $page >= $total_pages || count( $subscriber_emails ) >= $total ) { break; } - $page++; + ++$page; } return $subscriber_emails; From 7f3f5a4b5b34a475e47ee855235fc52152355472 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 25 Nov 2025 14:08:08 -0500 Subject: [PATCH 03/19] changelog --- .../jetpack/changelog/add-post-meta-subscribers-list-at-send | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send diff --git a/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send new file mode 100644 index 000000000000..b9814fdff6ee --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Adds subscribers list to post meta for debugging purposes From 1ad17b70721598178d2e3981fb18a5972b920a9f Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 25 Nov 2025 14:20:15 -0500 Subject: [PATCH 04/19] remove rest registration --- projects/plugins/jetpack/modules/subscriptions.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index faf28e3dce6c..96bed1d84967 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -990,19 +990,6 @@ public function register_post_meta() { ); register_meta( 'post', '_jetpack_post_was_ever_published', $jetpack_post_was_ever_published ); - - $jetpack_newsletter_subscribers_when_sent = array( - 'type' => 'array', - 'description' => __( 'List of subscribers when the post was first emailed. Used for debugging purposes.', 'jetpack' ), - 'single' => true, - 'default' => array(), - 'show_in_rest' => array( - 'name' => 'jetpack_newsletter_subscribers_when_sent', - ), - 'auth_callback' => array( $this, 'first_published_status_meta_auth_callback' ), - ); - - register_meta( 'post', '_jetpack_newsletter_subscribers_when_sent', $jetpack_newsletter_subscribers_when_sent ); } /** From a9032744ef54130cb1517a99e5e29f8da9f6d668 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 11:20:07 -0500 Subject: [PATCH 05/19] add helper and share stats requests --- .../plugins/jetpack/modules/subscriptions.php | 35 +++----- .../class-jetpack-subscriptions-helper.php | 86 +++++++++++++++++++ ...ss-jetpack-newsletter-dashboard-widget.php | 32 +++---- 3 files changed, 108 insertions(+), 45 deletions(-) create mode 100644 projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 96bed1d84967..a4ee18061886 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1061,30 +1061,16 @@ private function get_subscriber_data() { $site_id = Jetpack_Options::get_option( 'id' ); // First, get subscriber counts from stats endpoint. - $stats_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); - $stats_response = Client::wpcom_json_api_request_as_blog( - $stats_path, - '2', - array(), - null, - 'wpcom' - ); - - if ( ! is_wp_error( $stats_response ) ) { - $stats_code = wp_remote_retrieve_response_code( $stats_response ); - if ( 200 === $stats_code ) { - $subscriber_counts = json_decode( wp_remote_retrieve_body( $stats_response ), true ); - if ( is_array( $subscriber_counts ) ) { - if ( isset( $subscriber_counts['counts']['email_subscribers'] ) ) { - $subscriber_data['email_subscribers'] = (int) $subscriber_counts['counts']['email_subscribers']; - } - if ( isset( $subscriber_counts['counts']['paid_subscribers'] ) ) { - $subscriber_data['paid_subscribers'] = (int) $subscriber_counts['counts']['paid_subscribers']; - } - if ( isset( $subscriber_counts['counts']['all_subscribers'] ) ) { - $subscriber_data['all_subscribers'] = (int) $subscriber_counts['counts']['all_subscribers']; - } - } + $stats = Jetpack_Subscriptions_Helper::fetch_subscriber_stats( $site_id ); + if ( ! is_wp_error( $stats ) ) { + if ( isset( $stats['email_subscribers'] ) ) { + $subscriber_data['email_subscribers'] = $stats['email_subscribers']; + } + if ( isset( $stats['paid_subscribers'] ) ) { + $subscriber_data['paid_subscribers'] = $stats['paid_subscribers']; + } + if ( isset( $stats['all_subscribers'] ) ) { + $subscriber_data['all_subscribers'] = $stats['all_subscribers']; } } @@ -1267,6 +1253,7 @@ public function track_newsletter_category_creation() { Jetpack_Subscriptions::init(); require __DIR__ . '/subscriptions/views.php'; +require __DIR__ . '/subscriptions/class-jetpack-subscriptions-helper.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'; diff --git a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php new file mode 100644 index 000000000000..b3dafd8c6762 --- /dev/null +++ b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php @@ -0,0 +1,86 @@ + $stats_code ) + ); + } + + $subscriber_counts = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $subscriber_counts ) ) { + return new WP_Error( 'invalid_response', __( 'Invalid response format from API.', 'jetpack' ) ); + } + + $stats = array( + 'email_subscribers' => 0, + 'paid_subscribers' => 0, + 'all_subscribers' => 0, + 'aggregate' => array(), + ); + + if ( isset( $subscriber_counts['counts']['email_subscribers'] ) ) { + $stats['email_subscribers'] = (int) $subscriber_counts['counts']['email_subscribers']; + } + + if ( isset( $subscriber_counts['counts']['paid_subscribers'] ) ) { + $stats['paid_subscribers'] = (int) $subscriber_counts['counts']['paid_subscribers']; + } + + if ( isset( $subscriber_counts['counts']['all_subscribers'] ) ) { + $stats['all_subscribers'] = (int) $subscriber_counts['counts']['all_subscribers']; + } + + if ( isset( $subscriber_counts['aggregate'] ) && is_array( $subscriber_counts['aggregate'] ) ) { + $stats['aggregate'] = $subscriber_counts['aggregate']; + } + + return $stats; + } +} diff --git a/projects/plugins/jetpack/modules/subscriptions/newsletter-widget/class-jetpack-newsletter-dashboard-widget.php b/projects/plugins/jetpack/modules/subscriptions/newsletter-widget/class-jetpack-newsletter-dashboard-widget.php index a25b469a53ac..c24508cb1453 100644 --- a/projects/plugins/jetpack/modules/subscriptions/newsletter-widget/class-jetpack-newsletter-dashboard-widget.php +++ b/projects/plugins/jetpack/modules/subscriptions/newsletter-widget/class-jetpack-newsletter-dashboard-widget.php @@ -5,7 +5,6 @@ * @package jetpack */ -use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Modules; /** @@ -56,32 +55,23 @@ public static function get_config_data() { ); if ( Jetpack::is_connection_ready() ) { - $site_id = Jetpack_Options::get_option( 'id' ); - $api_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); - $response = Client::wpcom_json_api_request_as_blog( - $api_path, - '2', - array(), - null, - 'wpcom' - ); - - if ( 200 === wp_remote_retrieve_response_code( $response ) ) { - $subscriber_counts = json_decode( wp_remote_retrieve_body( $response ), true ); - if ( isset( $subscriber_counts['counts']['email_subscribers'] ) ) { - $config_data['emailSubscribers'] = (int) $subscriber_counts['counts']['email_subscribers']; + $site_id = Jetpack_Options::get_option( 'id' ); + $stats = Jetpack_Subscriptions_Helper::fetch_subscriber_stats( $site_id ); + if ( ! is_wp_error( $stats ) ) { + if ( isset( $stats['email_subscribers'] ) ) { + $config_data['emailSubscribers'] = $stats['email_subscribers']; } - if ( isset( $subscriber_counts['counts']['paid_subscribers'] ) ) { - $config_data['paidSubscribers'] = (int) $subscriber_counts['counts']['paid_subscribers']; + if ( isset( $stats['paid_subscribers'] ) ) { + $config_data['paidSubscribers'] = $stats['paid_subscribers']; } - if ( isset( $subscriber_counts['counts']['all_subscribers'] ) ) { - $config_data['allSubscribers'] = (int) $subscriber_counts['counts']['all_subscribers']; + if ( isset( $stats['all_subscribers'] ) ) { + $config_data['allSubscribers'] = $stats['all_subscribers']; } - if ( isset( $subscriber_counts['aggregate'] ) ) { - $config_data['subscriberTotalsByDate'] = $subscriber_counts['aggregate']; + if ( isset( $stats['aggregate'] ) ) { + $config_data['subscriberTotalsByDate'] = $stats['aggregate']; } } From 15ec8b17e247803b008e06f85b57298b4aca6fbd Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 11:42:17 -0500 Subject: [PATCH 06/19] use helper for fetching subscribers as well --- .../plugins/jetpack/modules/subscriptions.php | 39 ++++--------- .../class-jetpack-subscriptions-helper.php | 55 +++++++++++++++++++ 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index a4ee18061886..06f6b22a37bb 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -16,7 +16,6 @@ // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files. use Automattic\Jetpack\Admin_UI\Admin_Menu; -use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Connection\XMLRPC_Async_Call; use Automattic\Jetpack\Redirect; @@ -1075,52 +1074,36 @@ private function get_subscriber_data() { } // Fetch the actual subscriber list with emails. - $subscriber_emails = $this->fetch_all_subscribers( $site_id ); - $subscriber_data['subscriber_list'] = $subscriber_emails; + $subscriber_emails = $this->fetch_email_subscribers( $site_id ); + $subscriber_data['email_subscriber_list'] = $subscriber_emails; return $subscriber_data; } /** - * Fetch all subscribers from WordPress.com API with pagination. + * Fetch all email subscribers from WordPress.com API with pagination. * * @since $$next-version$$ * * @param int $site_id Site ID. * @return array Array of subscriber data, each containing 'email' and 'is_paid' keys. */ - private function fetch_all_subscribers( $site_id ) { + private function fetch_email_subscribers( $site_id ) { $subscriber_emails = array(); $page = 1; $per_page = 100; // Maximum per page to minimize requests. while ( true ) { - $api_path = sprintf( - '/sites/%d/subscribers/?page=%d&per_page=%d&filter=email_subscriber', + $response_body = Jetpack_Subscriptions_Helper::fetch_subscribers( $site_id, - $page, - $per_page - ); - - $response = Client::wpcom_json_api_request_as_blog( - $api_path, - '2', - array(), - null, - 'wpcom' + array( + 'page' => $page, + 'per_page' => $per_page, + 'filter' => 'email_subscriber', + ) ); - if ( is_wp_error( $response ) ) { - break; - } - - $response_code = wp_remote_retrieve_response_code( $response ); - if ( 200 !== $response_code ) { - break; - } - - $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); - if ( ! is_array( $response_body ) ) { + if ( is_wp_error( $response_body ) ) { break; } diff --git a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php index b3dafd8c6762..c119b1eadce2 100644 --- a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php +++ b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php @@ -83,4 +83,59 @@ public static function fetch_subscriber_stats( $site_id ) { return $stats; } + + /** + * Fetch subscribers from WordPress.com API. + * + * @since $$next-version$$ + * + * @param int $site_id The site ID to fetch subscribers for. + * @param array $query_params Optional. Array of query parameters to add to the request. + * Common params: 'page', 'per_page', 'filter'. + * @return array|WP_Error Associative array with subscriber data on success, WP_Error on failure. + */ + public static function fetch_subscribers( $site_id, $query_params = array() ) { + if ( ! $site_id ) { + return new WP_Error( 'invalid_site_id', __( 'Invalid site ID provided.', 'jetpack' ) ); + } + + $api_path = sprintf( '/sites/%d/subscribers/', $site_id ); + + // Build query string from parameters. + if ( ! empty( $query_params ) ) { + $api_path = add_query_arg( $query_params, $api_path ); + } + + $response = Client::wpcom_json_api_request_as_blog( + $api_path, + '2', + array(), + null, + 'wpcom' + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + if ( 200 !== $response_code ) { + return new WP_Error( + 'http_error', + sprintf( + /* translators: %d is the HTTP response code */ + __( 'HTTP error %d when fetching subscribers.', 'jetpack' ), + $response_code + ), + array( 'status' => $response_code ) + ); + } + + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! is_array( $response_body ) ) { + return new WP_Error( 'invalid_response', __( 'Invalid response format from API.', 'jetpack' ) ); + } + + return $response_body; + } } From 5062c156a88bd64fc8f30250dc2c5029d4ee9c6f Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 12:00:16 -0500 Subject: [PATCH 07/19] remove unnecessary sanitize email --- projects/plugins/jetpack/modules/subscriptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 06f6b22a37bb..1253e0e838ed 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1123,7 +1123,7 @@ private function fetch_email_subscribers( $site_id ) { } $subscriber_emails[] = array( - 'email' => sanitize_email( $subscriber['email_address'] ), + 'email' => $subscriber['email_address'], 'is_paid' => $is_paid, ); } From 97b4c9e868262c77d5964cc564220006747e51c5 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 12:32:22 -0500 Subject: [PATCH 08/19] limit support for subscribers requests --- .../plugins/jetpack/modules/subscriptions.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 1253e0e838ed..e338b3ebb553 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1027,11 +1027,11 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { // Store subscriber data with timestamp. $data_to_store = array( - 'timestamp' => current_time( 'mysql' ), - 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, - 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, - 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, - 'subscriber_list' => isset( $subscriber_data['subscriber_list'] ) && is_array( $subscriber_data['subscriber_list'] ) ? $subscriber_data['subscriber_list'] : array(), + 'timestamp' => current_time( 'mysql' ), + 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, + 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, + 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, + 'email_subscribers_list' => isset( $subscriber_data['email_subscribers_list'] ) && is_array( $subscriber_data['email_subscribers_list'] ) ? $subscriber_data['email_subscribers_list'] : array(), ); update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); @@ -1046,10 +1046,10 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { */ private function get_subscriber_data() { $subscriber_data = array( - 'email_subscribers' => 0, - 'paid_subscribers' => 0, - 'all_subscribers' => 0, - 'subscriber_list' => array(), + 'email_subscribers' => 0, + 'paid_subscribers' => 0, + 'all_subscribers' => 0, + 'email_subscribers_list' => array(), ); // Only fetch if Jetpack is connected. @@ -1073,9 +1073,12 @@ private function get_subscriber_data() { } } - // Fetch the actual subscriber list with emails. - $subscriber_emails = $this->fetch_email_subscribers( $site_id ); - $subscriber_data['email_subscriber_list'] = $subscriber_emails; + // Set a maximum to support here to limit number of requests and meta size. + if ( (int) $subscriber_data['email_subscribers'] <= 2000 ) { + // Fetch the actual subscriber list with emails. + $subscriber_emails = $this->fetch_email_subscribers( $site_id ); + $subscriber_data['email_subscriber_list'] = $subscriber_emails; + } return $subscriber_data; } From a176b96b725bf81f6c77696c4c2fc09b99651d3d Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 13:47:02 -0500 Subject: [PATCH 09/19] set transients for request responses --- .../class-jetpack-subscriptions-helper.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php index c119b1eadce2..e91bad304547 100644 --- a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php +++ b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php @@ -15,6 +15,7 @@ class Jetpack_Subscriptions_Helper { /** * Fetch subscriber statistics from WordPress.com API. + * Results are cached for 5 minutes to reduce API requests. * * @since $$next-version$$ * @@ -27,6 +28,13 @@ public static function fetch_subscriber_stats( $site_id ) { return new WP_Error( 'invalid_site_id', __( 'Invalid site ID provided.', 'jetpack' ) ); } + // Check cache first. + $cache_key = 'jetpack_subscriber_stats_' . $site_id; + $cached = get_transient( $cache_key ); + if ( false !== $cached ) { + return $cached; + } + $stats_path = sprintf( '/sites/%d/subscribers/stats', $site_id ); $response = Client::wpcom_json_api_request_as_blog( $stats_path, @@ -81,11 +89,15 @@ public static function fetch_subscriber_stats( $site_id ) { $stats['aggregate'] = $subscriber_counts['aggregate']; } + // Cache successful responses for 5 minutes. + set_transient( $cache_key, $stats, 5 * MINUTE_IN_SECONDS ); + return $stats; } /** * Fetch subscribers from WordPress.com API. + * Results are cached for 5 minutes to reduce API requests. * * @since $$next-version$$ * @@ -99,6 +111,18 @@ public static function fetch_subscribers( $site_id, $query_params = array() ) { return new WP_Error( 'invalid_site_id', __( 'Invalid site ID provided.', 'jetpack' ) ); } + // Build cache key based on site_id and query parameters. + // Sort keys to ensure deterministic cache keys regardless of parameter order. + $sorted_params = $query_params; + ksort( $sorted_params ); + $cache_key = 'jetpack_subscribers_' . $site_id . '_' . md5( wp_json_encode( $sorted_params ) ); + + // Check cache first. + $cached = get_transient( $cache_key ); + if ( false !== $cached ) { + return $cached; + } + $api_path = sprintf( '/sites/%d/subscribers/', $site_id ); // Build query string from parameters. @@ -136,6 +160,9 @@ public static function fetch_subscribers( $site_id, $query_params = array() ) { return new WP_Error( 'invalid_response', __( 'Invalid response format from API.', 'jetpack' ) ); } + // Cache successful responses for 5 minutes. + set_transient( $cache_key, $response_body, 5 * MINUTE_IN_SECONDS ); + return $response_body; } } From c5e782f86fc39fa2fbad5113cebe99bc2ea5beca Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 13:51:50 -0500 Subject: [PATCH 10/19] add new meta to sync --- projects/packages/sync/src/class-defaults.php | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/packages/sync/src/class-defaults.php b/projects/packages/sync/src/class-defaults.php index 5bdaf3883ed7..978abbd7062c 100644 --- a/projects/packages/sync/src/class-defaults.php +++ b/projects/packages/sync/src/class-defaults.php @@ -790,6 +790,7 @@ public static function get_multisite_callable_whitelist() { 'videopress_guid', 'vimeo_poster_image', '_jetpack_blogging_prompt_key', + '_jetpack_newsletter_subscribers_when_sent', 'footnotes', // Core footnotes block ); From 500986ba41e3750af76bd063c6b61a052f90e86c Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 13:53:07 -0500 Subject: [PATCH 11/19] changelog --- .../sync/changelog/add-post-meta-subscribers-list-at-send | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send diff --git a/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send b/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send new file mode 100644 index 000000000000..2385b8f505c0 --- /dev/null +++ b/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +adds new post meta for newsletter subscribers to post sync From 031efedf0194d8b5514342ae88ab98adfe8b8766 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Tue, 2 Dec 2025 13:58:01 -0500 Subject: [PATCH 12/19] fix key name bug --- .../plugins/jetpack/modules/subscriptions.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index e338b3ebb553..1263605c42ea 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1027,11 +1027,11 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { // Store subscriber data with timestamp. $data_to_store = array( - 'timestamp' => current_time( 'mysql' ), - 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, - 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, - 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, - 'email_subscribers_list' => isset( $subscriber_data['email_subscribers_list'] ) && is_array( $subscriber_data['email_subscribers_list'] ) ? $subscriber_data['email_subscribers_list'] : array(), + 'timestamp' => current_time( 'mysql' ), + 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, + 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, + 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, + 'email_subscriber_list' => isset( $subscriber_data['email_subscriber_list'] ) && is_array( $subscriber_data['email_subscriber_list'] ) ? $subscriber_data['email_subscriber_list'] : array(), ); update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); @@ -1046,10 +1046,10 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { */ private function get_subscriber_data() { $subscriber_data = array( - 'email_subscribers' => 0, - 'paid_subscribers' => 0, - 'all_subscribers' => 0, - 'email_subscribers_list' => array(), + 'email_subscribers' => 0, + 'paid_subscribers' => 0, + 'all_subscribers' => 0, + 'email_subscriber_list' => array(), ); // Only fetch if Jetpack is connected. From 0da375559295956c447ee61e8932bf769cdfb314 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 11:07:14 -0500 Subject: [PATCH 13/19] remove individual subscriber info additions --- .../plugins/jetpack/modules/subscriptions.php | 71 ------------------- .../class-jetpack-subscriptions-helper.php | 71 ------------------- 2 files changed, 142 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 1263605c42ea..14044527ca15 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1073,80 +1073,9 @@ private function get_subscriber_data() { } } - // Set a maximum to support here to limit number of requests and meta size. - if ( (int) $subscriber_data['email_subscribers'] <= 2000 ) { - // Fetch the actual subscriber list with emails. - $subscriber_emails = $this->fetch_email_subscribers( $site_id ); - $subscriber_data['email_subscriber_list'] = $subscriber_emails; - } - return $subscriber_data; } - /** - * Fetch all email subscribers from WordPress.com API with pagination. - * - * @since $$next-version$$ - * - * @param int $site_id Site ID. - * @return array Array of subscriber data, each containing 'email' and 'is_paid' keys. - */ - private function fetch_email_subscribers( $site_id ) { - $subscriber_emails = array(); - $page = 1; - $per_page = 100; // Maximum per page to minimize requests. - - while ( true ) { - $response_body = Jetpack_Subscriptions_Helper::fetch_subscribers( - $site_id, - array( - 'page' => $page, - 'per_page' => $per_page, - 'filter' => 'email_subscriber', - ) - ); - - if ( is_wp_error( $response_body ) ) { - break; - } - - // Extract subscriber data from subscribers array. - if ( isset( $response_body['subscribers'] ) && is_array( $response_body['subscribers'] ) ) { - foreach ( $response_body['subscribers'] as $subscriber ) { - if ( isset( $subscriber['email_address'] ) && is_email( $subscriber['email_address'] ) ) { - // Determine if subscriber has an active paid plan. - $is_paid = false; - if ( isset( $subscriber['plans'] ) && is_array( $subscriber['plans'] ) ) { - foreach ( $subscriber['plans'] as $plan ) { - if ( isset( $plan['status'] ) && 'active' === $plan['status'] ) { - $is_paid = true; - break; - } - } - } - - $subscriber_emails[] = array( - 'email' => $subscriber['email_address'], - 'is_paid' => $is_paid, - ); - } - } - } - - // Check if there are more pages. - $total = isset( $response_body['total'] ) ? (int) $response_body['total'] : 0; - $total_pages = isset( $response_body['total_pages'] ) ? (int) $response_body['total_pages'] : 1; - - if ( $page >= $total_pages || count( $subscriber_emails ) >= $total ) { - break; - } - - ++$page; - } - - return $subscriber_emails; - } - /** * Create a Subscribers menu displayed on self-hosted sites. * diff --git a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php index e91bad304547..dbf78e11c1bf 100644 --- a/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php +++ b/projects/plugins/jetpack/modules/subscriptions/class-jetpack-subscriptions-helper.php @@ -94,75 +94,4 @@ public static function fetch_subscriber_stats( $site_id ) { return $stats; } - - /** - * Fetch subscribers from WordPress.com API. - * Results are cached for 5 minutes to reduce API requests. - * - * @since $$next-version$$ - * - * @param int $site_id The site ID to fetch subscribers for. - * @param array $query_params Optional. Array of query parameters to add to the request. - * Common params: 'page', 'per_page', 'filter'. - * @return array|WP_Error Associative array with subscriber data on success, WP_Error on failure. - */ - public static function fetch_subscribers( $site_id, $query_params = array() ) { - if ( ! $site_id ) { - return new WP_Error( 'invalid_site_id', __( 'Invalid site ID provided.', 'jetpack' ) ); - } - - // Build cache key based on site_id and query parameters. - // Sort keys to ensure deterministic cache keys regardless of parameter order. - $sorted_params = $query_params; - ksort( $sorted_params ); - $cache_key = 'jetpack_subscribers_' . $site_id . '_' . md5( wp_json_encode( $sorted_params ) ); - - // Check cache first. - $cached = get_transient( $cache_key ); - if ( false !== $cached ) { - return $cached; - } - - $api_path = sprintf( '/sites/%d/subscribers/', $site_id ); - - // Build query string from parameters. - if ( ! empty( $query_params ) ) { - $api_path = add_query_arg( $query_params, $api_path ); - } - - $response = Client::wpcom_json_api_request_as_blog( - $api_path, - '2', - array(), - null, - 'wpcom' - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $response_code = wp_remote_retrieve_response_code( $response ); - if ( 200 !== $response_code ) { - return new WP_Error( - 'http_error', - sprintf( - /* translators: %d is the HTTP response code */ - __( 'HTTP error %d when fetching subscribers.', 'jetpack' ), - $response_code - ), - array( 'status' => $response_code ) - ); - } - - $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); - if ( ! is_array( $response_body ) ) { - return new WP_Error( 'invalid_response', __( 'Invalid response format from API.', 'jetpack' ) ); - } - - // Cache successful responses for 5 minutes. - set_transient( $cache_key, $response_body, 5 * MINUTE_IN_SECONDS ); - - return $response_body; - } } From cf1ea77e1dbb64a13bbf8c1fdae9700827908c2f Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 11:29:18 -0500 Subject: [PATCH 14/19] add info for sending preference and categories --- .../plugins/jetpack/modules/subscriptions.php | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 14044527ca15..290e367b14b9 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1025,13 +1025,43 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { // Fetch subscriber data from WordPress.com API. $subscriber_data = $this->get_subscriber_data(); + // Get email subscription setting for this post. + // If _jetpack_dont_email_post_to_subs is 1, post is "post only" (not emailed). + // If 0 or not set, post is "email and post". + $dont_email = get_post_meta( $post_ID, '_jetpack_dont_email_post_to_subs', true ); + $email_to_subs_disabled = ! empty( $dont_email ); + + // Get newsletter categories information. + $newsletter_categories_enabled = (bool) get_option( 'wpcom_newsletter_categories_enabled', false ); + $post_categories = wp_get_post_categories( $post_ID ); + $newsletter_category_ids = Jetpack_Newsletter_Category_Helper::get_category_ids(); + $post_newsletter_categories = array(); + $post_non_newsletter_categories = array(); + + if ( $newsletter_categories_enabled && ! empty( $newsletter_category_ids ) && ! empty( $post_categories ) ) { + // Find which of the post's categories are newsletter categories. + $post_newsletter_categories = array_intersect( $post_categories, $newsletter_category_ids ); + $post_newsletter_categories = array_values( array_map( 'intval', $post_newsletter_categories ) ); + + // Find which of the post's categories are NOT newsletter categories. + $post_non_newsletter_categories = array_diff( $post_categories, $newsletter_category_ids ); + $post_non_newsletter_categories = array_values( array_map( 'intval', $post_non_newsletter_categories ) ); + } elseif ( ! empty( $post_categories ) ) { + // If newsletter categories are not enabled, all post categories are non-newsletter categories. + $post_non_newsletter_categories = array_values( array_map( 'intval', $post_categories ) ); + } + // Store subscriber data with timestamp. $data_to_store = array( - 'timestamp' => current_time( 'mysql' ), - 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, - 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, - 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, - 'email_subscriber_list' => isset( $subscriber_data['email_subscriber_list'] ) && is_array( $subscriber_data['email_subscriber_list'] ) ? $subscriber_data['email_subscriber_list'] : array(), + 'timestamp' => current_time( 'mysql' ), + 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, + 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, + 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, + 'email_subscriber_list' => isset( $subscriber_data['email_subscriber_list'] ) && is_array( $subscriber_data['email_subscriber_list'] ) ? $subscriber_data['email_subscriber_list'] : array(), + 'email_to_subs_disabled' => $email_to_subs_disabled, + 'newsletter_categories_enabled' => $newsletter_categories_enabled, + 'newsletter_category_ids' => $post_newsletter_categories, + 'non_newsletter_category_ids' => $post_non_newsletter_categories, ); update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); From 79952bcdbc894a7048685165d97d4b3ea3c4763d Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 11:36:38 -0500 Subject: [PATCH 15/19] renaming and changelog updates --- .../changelog/add-post-meta-subscribers-list-at-send | 2 +- projects/packages/sync/src/class-defaults.php | 2 +- .../changelog/add-post-meta-subscribers-list-at-send | 2 +- projects/plugins/jetpack/modules/subscriptions.php | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send b/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send index 2385b8f505c0..03e93b0cba33 100644 --- a/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send +++ b/projects/packages/sync/changelog/add-post-meta-subscribers-list-at-send @@ -1,4 +1,4 @@ Significance: minor Type: changed -adds new post meta for newsletter subscribers to post sync +adds new post meta for newsletter debug info to post sync diff --git a/projects/packages/sync/src/class-defaults.php b/projects/packages/sync/src/class-defaults.php index 978abbd7062c..3e066bddb761 100644 --- a/projects/packages/sync/src/class-defaults.php +++ b/projects/packages/sync/src/class-defaults.php @@ -790,7 +790,7 @@ public static function get_multisite_callable_whitelist() { 'videopress_guid', 'vimeo_poster_image', '_jetpack_blogging_prompt_key', - '_jetpack_newsletter_subscribers_when_sent', + '_jetpack_newsletter_initial_debug_info', 'footnotes', // Core footnotes block ); diff --git a/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send index b9814fdff6ee..0e5416f3ee3e 100644 --- a/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send +++ b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send @@ -1,4 +1,4 @@ Significance: patch Type: other -Adds subscribers list to post meta for debugging purposes +Adds newsletter debug info to post meta on initial publish diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 290e367b14b9..4ccdb12e427b 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -132,7 +132,7 @@ public function __construct() { add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 ); - add_action( 'jetpack_published_post', array( $this, 'store_subscribers_when_sent' ), 10, 3 ); + add_action( 'jetpack_published_post', array( $this, 'store_initial_debug_info' ), 10, 3 ); add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 18, 1 ); @@ -992,7 +992,7 @@ public function register_post_meta() { } /** - * Store the list of subscribers when a post is first emailed. + * Store the initial debug info when a post is first published. * * This method is called when a post is published and emails are sent to subscribers. * It stores the subscriber count and metadata in post meta for debugging purposes. @@ -1005,14 +1005,14 @@ public function register_post_meta() { * * @return void */ - public function store_subscribers_when_sent( $post_ID, $flags, $post ) { + public function store_initial_debug_info( $post_ID, $flags, $post ) { // Only store if emails are being sent. if ( ! isset( $flags['send_subscription'] ) || ! $flags['send_subscription'] ) { return; } // Only store once - check if we've already stored subscribers for this post. - $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', true ); + $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_initial_debug_info', true ); if ( ! empty( $existing_subscribers ) ) { return; } @@ -1064,7 +1064,7 @@ public function store_subscribers_when_sent( $post_ID, $flags, $post ) { 'non_newsletter_category_ids' => $post_non_newsletter_categories, ); - update_post_meta( $post_ID, '_jetpack_newsletter_subscribers_when_sent', $data_to_store ); + update_post_meta( $post_ID, '_jetpack_newsletter_initial_debug_info', $data_to_store ); } /** From ff85c2213e1589ac39fc687adf09556777587818 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 11:39:08 -0500 Subject: [PATCH 16/19] changelog update --- .../jetpack/changelog/add-post-meta-subscribers-list-at-send | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send index 0e5416f3ee3e..97ab63f5128a 100644 --- a/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send +++ b/projects/plugins/jetpack/changelog/add-post-meta-subscribers-list-at-send @@ -1,4 +1,5 @@ Significance: patch Type: other -Adds newsletter debug info to post meta on initial publish +Adds newsletter debug info to post meta on initial publish. +Adds helper class for requesting stats, and updates the newsletter dashboard widgetp to use this as well. From 0444d749e7cd20ac39825ed8039e7697f0498285 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 12:26:56 -0500 Subject: [PATCH 17/19] fix broken issues --- .../plugins/jetpack/modules/subscriptions.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 4ccdb12e427b..d109c6de3b37 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1006,12 +1006,7 @@ public function register_post_meta() { * @return void */ public function store_initial_debug_info( $post_ID, $flags, $post ) { - // Only store if emails are being sent. - if ( ! isset( $flags['send_subscription'] ) || ! $flags['send_subscription'] ) { - return; - } - - // Only store once - check if we've already stored subscribers for this post. + // Only store once - check if we've already stored debug info for this post. $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_initial_debug_info', true ); if ( ! empty( $existing_subscribers ) ) { return; @@ -1022,15 +1017,21 @@ public function store_initial_debug_info( $post_ID, $flags, $post ) { return; } + // Load the newsletter category helper class if not already loaded. + if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) { + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php'; + } + // Fetch subscriber data from WordPress.com API. $subscriber_data = $this->get_subscriber_data(); // Get email subscription setting for this post. - // If _jetpack_dont_email_post_to_subs is 1, post is "post only" (not emailed). - // If 0 or not set, post is "email and post". $dont_email = get_post_meta( $post_ID, '_jetpack_dont_email_post_to_subs', true ); $email_to_subs_disabled = ! empty( $dont_email ); + // Also store the final determination from the flags (includes more checks than post_meta). + $will_send_to_subscribers = isset( $flags['send_subscription'] ) && $flags['send_subscription']; + // Get newsletter categories information. $newsletter_categories_enabled = (bool) get_option( 'wpcom_newsletter_categories_enabled', false ); $post_categories = wp_get_post_categories( $post_ID ); @@ -1057,8 +1058,8 @@ public function store_initial_debug_info( $post_ID, $flags, $post ) { 'email_subscribers' => isset( $subscriber_data['email_subscribers'] ) ? (int) $subscriber_data['email_subscribers'] : 0, 'paid_subscribers' => isset( $subscriber_data['paid_subscribers'] ) ? (int) $subscriber_data['paid_subscribers'] : 0, 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, - 'email_subscriber_list' => isset( $subscriber_data['email_subscriber_list'] ) && is_array( $subscriber_data['email_subscriber_list'] ) ? $subscriber_data['email_subscriber_list'] : array(), 'email_to_subs_disabled' => $email_to_subs_disabled, + 'will_send_to_subscribers' => $will_send_to_subscribers, 'newsletter_categories_enabled' => $newsletter_categories_enabled, 'newsletter_category_ids' => $post_newsletter_categories, 'non_newsletter_category_ids' => $post_non_newsletter_categories, From a521a4378d6ed4e5406519b593b0a0a1ff7c6d22 Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 13:39:29 -0500 Subject: [PATCH 18/19] add access level setting info --- .../plugins/jetpack/modules/subscriptions.php | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index d109c6de3b37..2f279ded6648 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -18,15 +18,22 @@ use Automattic\Jetpack\Admin_UI\Admin_Menu; use Automattic\Jetpack\Connection\Manager as Connection_Manager; use Automattic\Jetpack\Connection\XMLRPC_Async_Call; +use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service; use Automattic\Jetpack\Redirect; use Automattic\Jetpack\Status; use Automattic\Jetpack\Status\Host; use Automattic\Jetpack\Subscribers_Dashboard\Dashboard as Subscribers_Dashboard; +use const Automattic\Jetpack\Extensions\Subscriptions\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS; if ( ! defined( 'ABSPATH' ) ) { exit( 0 ); } +// Load required classes and constants. +require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/subscriptions/constants.php'; +require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php'; +require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php'; + add_action( 'jetpack_modules_loaded', 'jetpack_subscriptions_load' ); // Loads the User Content Link Redirection feature. @@ -1006,20 +1013,15 @@ public function register_post_meta() { * @return void */ public function store_initial_debug_info( $post_ID, $flags, $post ) { - // Only store once - check if we've already stored debug info for this post. - $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_initial_debug_info', true ); - if ( ! empty( $existing_subscribers ) ) { - return; - } - // Only store for posts. if ( 'post' !== $post->post_type ) { return; } - // Load the newsletter category helper class if not already loaded. - if ( ! class_exists( 'Jetpack_Newsletter_Category_Helper' ) ) { - require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-newsletter-category-helper.php'; + // Only store once - check if we've already stored debug info for this post. + $existing_subscribers = get_post_meta( $post_ID, '_jetpack_newsletter_initial_debug_info', true ); + if ( ! empty( $existing_subscribers ) ) { + return; } // Fetch subscriber data from WordPress.com API. @@ -1032,6 +1034,18 @@ public function store_initial_debug_info( $post_ID, $flags, $post ) { // Also store the final determination from the flags (includes more checks than post_meta). $will_send_to_subscribers = isset( $flags['send_subscription'] ) && $flags['send_subscription']; + // Get newsletter access level for this post. + // Use constant for meta key if available, fallback to string. + $access_level_meta_key = defined( 'Automattic\\Jetpack\\Extensions\\Subscriptions\\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS' ) + ? META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS + : '_jetpack_newsletter_access'; + + $newsletter_access_level = get_post_meta( $post_ID, $access_level_meta_key, true ); + if ( empty( $newsletter_access_level ) ) { + // Use constant for default value. + $newsletter_access_level = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY ?? 'everybody'; + } + // Get newsletter categories information. $newsletter_categories_enabled = (bool) get_option( 'wpcom_newsletter_categories_enabled', false ); $post_categories = wp_get_post_categories( $post_ID ); @@ -1060,6 +1074,7 @@ public function store_initial_debug_info( $post_ID, $flags, $post ) { 'all_subscribers' => isset( $subscriber_data['all_subscribers'] ) ? (int) $subscriber_data['all_subscribers'] : 0, 'email_to_subs_disabled' => $email_to_subs_disabled, 'will_send_to_subscribers' => $will_send_to_subscribers, + 'newsletter_access_level' => $newsletter_access_level, 'newsletter_categories_enabled' => $newsletter_categories_enabled, 'newsletter_category_ids' => $post_newsletter_categories, 'non_newsletter_category_ids' => $post_non_newsletter_categories, From 4fd30874bf17011f7b936d9cebb4298f8be8730e Mon Sep 17 00:00:00 2001 From: Addison Stavlo Date: Mon, 8 Dec 2025 14:05:29 -0500 Subject: [PATCH 19/19] remove safety because linter typecheck doesn't like unnecessary safety --- projects/plugins/jetpack/modules/subscriptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/modules/subscriptions.php b/projects/plugins/jetpack/modules/subscriptions.php index 034aacbee10a..5435998a4263 100644 --- a/projects/plugins/jetpack/modules/subscriptions.php +++ b/projects/plugins/jetpack/modules/subscriptions.php @@ -1047,7 +1047,7 @@ public function store_initial_debug_info( $post_ID, $flags, $post ) { $newsletter_access_level = get_post_meta( $post_ID, $access_level_meta_key, true ); if ( empty( $newsletter_access_level ) ) { // Use constant for default value. - $newsletter_access_level = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY ?? 'everybody'; + $newsletter_access_level = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY; } // Get newsletter categories information.