PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-send-email-preview.php +206 -0 16.2-beta → 16.3-a.5 View file →
@@ -1,0 +1,206 @@
1 +<?php
2 +/**
3 + * Handles the sending of email previews via the WordPress.com REST API.
4 + *
5 + * @package automattic/jetpack
6 + */
7 +
8 +use Automattic\Jetpack\Connection\Manager;
9 +use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
10 +use Automattic\Jetpack\Status\Host;
11 +
12 +if ( ! defined( 'ABSPATH' ) ) {
13 + exit( 0 );
14 +}
15 +
16 +/**
17 + * Class WPCOM_REST_API_V2_Endpoint_Send_Email_Preview
18 + * Handles the sending of email previews via the WordPress.com REST API
19 + */
20 +class WPCOM_REST_API_V2_Endpoint_Send_Email_Preview extends WP_REST_Controller {
21 +
22 + use WPCOM_REST_API_Proxy_Request;
23 +
24 + /**
25 + * Constructor.
26 + */
27 + public function __construct() {
28 + $this->base_api_path = 'wpcom';
29 + $this->version = 'v2';
30 + $this->namespace = $this->base_api_path . '/' . $this->version;
31 + $this->rest_base = '/send-email-preview';
32 + $this->wpcom_is_wpcom_only_endpoint = true;
33 + $this->wpcom_is_site_specific_endpoint = true;
34 +
35 + add_action( 'rest_api_init', array( $this, 'register_routes' ) );
36 + }
37 +
38 + /**
39 + * Registers the routes for blogging prompts.
40 + *
41 + * @see register_rest_route()
42 + */
43 + public function register_routes() {
44 + $options = array(
45 + 'show_in_index' => true,
46 + 'methods' => 'POST',
47 + // if this is not a wpcom site, we need to proxy the request to wpcom
48 + 'callback' => ( ( new Host() )->is_wpcom_simple() ) ? array(
49 + $this,
50 + 'send_email_preview',
51 + ) : array( $this, 'proxy_request_to_wpcom_as_user' ),
52 + 'permission_callback' => array( $this, 'permissions_check' ),
53 + 'args' => array(
54 + 'id' => array(
55 + 'description' => __( 'Unique identifier for the post.', 'jetpack' ),
56 + 'type' => 'integer',
57 + ),
58 + 'email' => array(
59 + 'description' => __( 'Optional recipient address. Defaults to the current user. A different address is only accepted from users who may add subscribers, and is subject to the same abuse checks.', 'jetpack' ),
60 + 'type' => 'string',
61 + ),
62 + ),
63 + );
64 +
65 + register_rest_route(
66 + $this->namespace,
67 + $this->rest_base,
68 + $options
69 + );
70 + }
71 +
72 + /**
73 + * Checks if the user is connected and has access to edit the post
74 + *
75 + * @param WP_REST_Request $request Full data about the request.
76 + *
77 + * @return true|WP_Error True if the request has edit access, WP_Error object otherwise.
78 + */
79 + public function permissions_check( $request ) {
80 + if ( ! ( new Host() )->is_wpcom_simple() ) {
81 + if ( ! ( new Manager() )->is_user_connected() ) {
82 + return new WP_Error(
83 + 'rest_cannot_send_email_preview',
84 + __( 'Please connect your user account to WordPress.com', 'jetpack' ),
85 + array( 'status' => rest_authorization_required_code() )
86 + );
87 + }
88 + }
89 +
90 + $post = get_post( $request->get_param( 'id' ) );
91 +
92 + if ( is_wp_error( $post ) ) {
93 + return $post;
94 + }
95 +
96 + if ( $post && ! current_user_can( 'edit_post', $post->ID ) ) {
97 + return new WP_Error(
98 + 'rest_forbidden_context',
99 + __( 'Please connect your user account to WordPress.com', 'jetpack' ),
100 + array( 'status' => rest_authorization_required_code() )
101 + );
102 + }
103 +
104 + return true;
105 + }
106 +
107 + /**
108 + * Sends an email preview of a post to the current user.
109 + *
110 + * @param WP_REST_Request $request Full data about the request.
111 + *
112 + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
113 + */
114 + public function send_email_preview( $request ) {
115 + $post_id = $request['id'];
116 + $post = get_post( $post_id );
117 +
118 + // Return error if the post cannot be retrieved
119 + if ( is_wp_error( $post ) ) {
120 + return $post;
121 + }
122 +
123 + // Check if the user's email is verified
124 + if ( Email_Verification::is_email_unverified() ) {
125 + return new WP_Error( 'unverified', __( 'Your email address must be verified.', 'jetpack' ), array( 'status' => rest_authorization_required_code() ) );
126 + }
127 +
128 + $current_user = wp_get_current_user();
129 + $self_email = $current_user->user_email;
130 + $email = $self_email;
131 +
132 + // Resolve the recipient. The address defaults to the caller's own verified
133 + // email; a caller-supplied address is only honored after it clears the same
134 + // gates as adding that person as a subscriber. Self-sends keep their
135 + // historical behavior and skip the guard entirely.
136 + //
137 + // The self-send fast path relies on the caller's own address matching
138 + // $current_user->user_email. That holds because this callback only runs on
139 + // wpcom (is_wpcom_simple(); Atomic/Jetpack requests are proxied to run as the
140 + // wpcom user) — revisit this comparison if it ever runs in another context.
141 + $requested = $request->get_param( 'email' );
142 + if ( is_string( $requested ) && '' !== trim( $requested ) ) {
143 + $requested = sanitize_email( $requested );
144 +
145 + if ( ! is_email( $requested ) ) {
146 + return new WP_Error( 'invalid_email', __( 'Please enter a valid email address.', 'jetpack' ), array( 'status' => 400 ) );
147 + }
148 +
149 + // Normalize both sides: comparing a sanitized address against the raw
150 + // stored email could route a genuine self-send through the guard.
151 + if ( 0 !== strcasecmp( $requested, sanitize_email( $self_email ) ) ) {
152 + $guard = ABSPATH . 'wp-content/mu-plugins/email-subscriptions/email-preview-guard.php';
153 + if ( ! class_exists( 'Email_Preview_Guard' ) && file_exists( $guard ) ) {
154 + require_once $guard;
155 + }
156 +
157 + if ( ! class_exists( 'Email_Preview_Guard' ) ) {
158 + return new WP_Error( 'send_email_preview_guard_unavailable', __( 'Test emails to another address are temporarily unavailable.', 'jetpack' ), array( 'status' => 503 ) );
159 + }
160 +
161 + $guarded = Email_Preview_Guard::check( $requested );
162 + if ( is_wp_error( $guarded ) ) {
163 + return $guarded;
164 + }
165 +
166 + $email = $requested;
167 + }
168 + }
169 +
170 + // Try to create a new subscriber with the resolved email
171 + $subscriber = Blog_Subscriber::create( $email );
172 + if ( ! $subscriber ) {
173 + return new WP_Error( 'unverified', __( 'Could not create subscriber.', 'jetpack' ), array( 'status' => rest_authorization_required_code() ) );
174 + }
175 +
176 + // Send the post to the subscriber
177 + require_once ABSPATH . 'wp-content/mu-plugins/email-subscriptions/subscription-mailer.php';
178 + $mailer = new Subscription_Mailer( $subscriber );
179 + $subscription = $subscriber->get_subscription( get_current_blog_id() );
180 +
181 + /**
182 + * Fires immediately before an email preview is dispatched to the current user.
183 + *
184 + * Useful for inspecting the post content with an external classifier (e.g. an
185 + * LLM-based content moderator) or for logging outbound previews. Fires after
186 + * the subscriber has been resolved, so handlers receive a post that is about
187 + * to be sent.
188 + *
189 + * @module subscriptions
190 + *
191 + * @since 15.8
192 + *
193 + * @param WP_Post $post The post being previewed.
194 + * @param Blog_Subscriber $subscriber The subscriber receiving the preview.
195 + * @param Blog_Subscription|false $subscription The subscriber's subscription for the current blog, or false if none exists.
196 + */
197 + do_action( 'jetpack_before_send_email_preview', $post, $subscriber, $subscription );
198 +
199 + $mailer->send_post( $post, $subscription );
200 +
201 + // Return a response
202 + return new WP_REST_Response( 'Email preview sent successfully.', 200 );
203 + }
204 +}
205 +
206 +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Send_Email_Preview' );