PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 13.8.3 All 506 releases
jetpack / _inc / lib / core-api / wpcom-endpoints / class-wpcom-rest-api-v2-endpoint-send-email-preview.php

class-wpcom-rest-api-v2-endpoint-send-email-preview.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-send-email-preview.php

207 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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' );
207