PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3 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 All 508 releases
← All changes | _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-agent-guidelines-ai.php +190 -0 16.2-beta → 16.3-beta View file →
@@ -1,0 +1,190 @@
1 +<?php
2 +/**
3 + * REST API proxy endpoint for AI-powered content guidelines suggestions.
4 + *
5 + * Proxies requests to the wpcom endpoint that generates guidelines
6 + * using site content analysis.
7 + *
8 + * @package automattic/jetpack
9 + */
10 +
11 +use Automattic\Jetpack\Connection\Client;
12 +use Automattic\Jetpack\Connection\Manager;
13 +use Automattic\Jetpack\Status\Host;
14 +
15 +if ( ! defined( 'ABSPATH' ) ) {
16 + exit( 0 );
17 +}
18 +
19 +/**
20 + * Class WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI
21 + */
22 +class WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI extends WP_REST_Controller {
23 + /**
24 + * Namespace prefix.
25 + *
26 + * @var string
27 + */
28 + public $namespace = 'wpcom/v2';
29 +
30 + /**
31 + * Endpoint base route.
32 + *
33 + * @var string
34 + */
35 + public $rest_base = 'jetpack-ai/suggest-guidelines';
36 +
37 + /**
38 + * Constructor.
39 + */
40 + public function __construct() {
41 + $this->is_wpcom = true;
42 + $this->wpcom_is_wpcom_only_endpoint = true;
43 +
44 + add_action( 'rest_api_init', array( $this, 'maybe_register_routes' ) );
45 + }
46 +
47 + /**
48 + * Register routes on `rest_api_init`, gating on the AI feature state.
49 + *
50 + * The Jetpack_AI_Helper check (which loads the helper and instantiates
51 + * Status/Host classes) runs here rather than in the constructor so that code
52 + * is only loaded when the REST API is actually in use, not on every
53 + * front-end, cron, or login request.
54 + */
55 + public function maybe_register_routes() {
56 + if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
57 + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
58 + }
59 +
60 + // Intentionally gated to Simple, Atomic, and WordPress VIP sites only.
61 + // Broader self-hosted Jetpack support is deferred — we want to roll out
62 + // on these platforms first before opening to all connected sites.
63 + if ( ! \Jetpack_AI_Helper::is_enabled_for_content_guidelines() ) {
64 + return;
65 + }
66 +
67 + $this->register_routes();
68 + }
69 +
70 + /**
71 + * Register routes.
72 + */
73 + public function register_routes() {
74 + register_rest_route(
75 + $this->namespace,
76 + '/' . $this->rest_base,
77 + array(
78 + 'methods' => WP_REST_Server::CREATABLE,
79 + 'callback' => array( $this, 'suggest_guidelines' ),
80 + 'permission_callback' => array( $this, 'permission_callback' ),
81 + 'args' => array(
82 + 'categories' => array(
83 + 'description' => __( 'Categories to generate guidelines for.', 'jetpack' ),
84 + 'type' => 'object',
85 + 'required' => true,
86 + ),
87 + ),
88 + )
89 + );
90 + }
91 +
92 + /**
93 + * Permission check — require manage_options, plus a connected user account
94 + * on sites that proxy the request to WordPress.com over the connection.
95 + *
96 + * @return bool|WP_Error
97 + */
98 + public function permission_callback() {
99 + if ( ! current_user_can( 'manage_options' ) ) {
100 + return false;
101 + }
102 +
103 + // suggest_guidelines() signs with the user token off Simple, so without a
104 + // user connection there is no identity to present and WordPress.com rejects
105 + // the request on private sites.
106 + if ( ! ( new Host() )->is_wpcom_simple() && ! ( new Manager() )->is_user_connected() ) {
107 + return new WP_Error(
108 + 'rest_cannot_suggest_guidelines',
109 + __( 'Please connect your user account to WordPress.com', 'jetpack' ),
110 + array( 'status' => rest_authorization_required_code() )
111 + );
112 + }
113 +
114 + return true;
115 + }
116 +
117 + /**
118 + * Proxy the suggest-guidelines request to wpcom.
119 + *
120 + * @param WP_REST_Request $request The request object.
121 + * @return mixed|WP_Error
122 + */
123 + public function suggest_guidelines( $request ) {
124 + $blog_id = \Jetpack_Options::get_option( 'id' );
125 +
126 + $body = array(
127 + 'categories' => $request->get_param( 'categories' ),
128 + );
129 +
130 + $path = sprintf( '/sites/%d/jetpack-ai/suggest-guidelines', $blog_id ) . '?force=wpcom';
131 + $args = array(
132 + 'method' => 'POST',
133 + 'headers' => array( 'content-type' => 'application/json' ),
134 + 'timeout' => 90,
135 + );
136 +
137 + // On a private site WordPress.com resolves access from the requesting user,
138 + // and a blog token carries no user_id — so the request is rejected before
139 + // the endpoint runs, and again on the internal AI proxy dispatch. Signing
140 + // as the user clears both, because the identity holds for the whole request.
141 + // permission_callback() has already established that a user token exists.
142 + //
143 + // Simple sites keep the blog-token call: it short-circuits to an in-process
144 + // WPCOM_API_Direct dispatch that never leaves the request, so the logged-in
145 + // user is already present and there is no connection to authenticate against.
146 + if ( ( new Host() )->is_wpcom_simple() ) {
147 + $response = Client::wpcom_json_api_request_as_blog(
148 + $path,
149 + '2',
150 + $args,
151 + wp_json_encode( $body, JSON_UNESCAPED_SLASHES ),
152 + 'wpcom'
153 + );
154 + } else {
155 + $response = Client::wpcom_json_api_request_as_user(
156 + $path,
157 + '2',
158 + $args,
159 + wp_json_encode( $body, JSON_UNESCAPED_SLASHES ),
160 + 'wpcom'
161 + );
162 + }
163 +
164 + if ( is_wp_error( $response ) ) {
165 + return $response;
166 + }
167 +
168 + $status_code = wp_remote_retrieve_response_code( $response );
169 + $body_str = wp_remote_retrieve_body( $response );
170 + $data = json_decode( $body_str, true );
171 +
172 + if ( $status_code !== 200 ) {
173 + $message = is_array( $data ) && isset( $data['message'] ) ? $data['message'] : __( 'Failed to generate guidelines.', 'jetpack' );
174 + $code = is_array( $data ) && isset( $data['code'] ) ? $data['code'] : 'upstream_error';
175 + return new WP_Error( $code, $message, array( 'status' => $status_code ) );
176 + }
177 +
178 + if ( JSON_ERROR_NONE !== json_last_error() ) {
179 + return new WP_Error(
180 + 'invalid_response',
181 + __( 'The guidelines service returned a malformed response.', 'jetpack' ),
182 + array( 'status' => 502 )
183 + );
184 + }
185 +
186 + return $data;
187 + }
188 +}
189 +
190 +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI' );