PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
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 13.9.2 14.0.1 14.1.1 14.2.2 All 502 releases
jetpack / _inc / lib / core-api / wpcom-endpoints / class-wpcom-rest-api-v2-endpoint-mcp-settings.php

class-wpcom-rest-api-v2-endpoint-mcp-settings.php in Jetpack – WP Security, Backup, Speed, & Growth 16.2, at _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mcp-settings.php

307 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API endpoint for Jetpack AI MCP settings.
4 *
5 * GET — proxies to WPCOM wpcom/v2/sites/{blog_id}/mcp-abilities (wpcom#205108) using
6 * user token auth so that WPCOM can resolve the requesting user's account-level
7 * MCP state. Reshapes the response into the { account, site, sites,
8 * site_level_enabled_default } structure the client utilities expect.
9 * POST — proxies to WPCOM POST /sites/{blog_id}/mcp-abilities (user token auth) which
10 * persists { site_level_enabled, abilities } to user settings via SettingsHelper,
11 * keeping Jetpack and Calypso in sync.
12 *
13 * @package automattic/jetpack
14 */
15
16 use Automattic\Jetpack\Connection\Client;
17 use Automattic\Jetpack\Connection\Manager;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit( 0 );
21 }
22
23 /**
24 * Class WPCOM_REST_API_V2_Endpoint_MCP_Settings
25 */
26 class WPCOM_REST_API_V2_Endpoint_MCP_Settings extends WP_REST_Controller {
27 /**
28 * Namespace prefix.
29 *
30 * @var string
31 */
32 public $namespace = 'wpcom/v2';
33
34 /**
35 * Endpoint base route.
36 *
37 * @var string
38 */
39 public $rest_base = 'jetpack-ai/mcp-settings';
40
41 /**
42 * Constructor.
43 */
44 public function __construct() {
45 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
46 }
47
48 /**
49 * Register routes.
50 */
51 public function register_routes() {
52 register_rest_route(
53 $this->namespace,
54 '/' . $this->rest_base,
55 array(
56 array(
57 'methods' => WP_REST_Server::READABLE,
58 'callback' => array( $this, 'get_mcp_settings' ),
59 'permission_callback' => array( $this, 'permissions_check' ),
60 ),
61 array(
62 'methods' => WP_REST_Server::EDITABLE,
63 'callback' => array( $this, 'update_mcp_settings' ),
64 'permission_callback' => array( $this, 'permissions_check' ),
65 'args' => array(
66 'mcp_abilities' => array(
67 'type' => 'object',
68 'required' => true,
69 ),
70 ),
71 ),
72 )
73 );
74 }
75
76 /**
77 * Check permissions.
78 *
79 * @return bool|WP_Error
80 */
81 public function permissions_check() {
82 if ( ! current_user_can( 'manage_options' ) ) {
83 return new WP_Error(
84 'rest_forbidden',
85 __( 'You do not have permission to manage MCP settings.', 'jetpack' ),
86 array( 'status' => rest_authorization_required_code() )
87 );
88 }
89
90 return true;
91 }
92
93 /**
94 * Fetch MCP abilities from the WPCOM wpcom/v2/sites/{blog_id}/mcp-abilities endpoint.
95 *
96 * The WPCOM endpoint accepts user token auth and returns an array of abilities.
97 * The response is shaped to match the format the client utilities expect:
98 * { account: { tool-id: {...} }, site: { tool-id: {...} },
99 * sites: [{ blog_id, site_level_enabled, abilities }],
100 * site_level_enabled_default: bool }
101 *
102 * All state is owned by WPCOM and persisted via POST /sites/{blog_id}/mcp-abilities.
103 *
104 * @return WP_REST_Response|WP_Error
105 */
106 public function get_mcp_settings() {
107 $blog_id = Manager::get_site_id();
108
109 if ( is_wp_error( $blog_id ) ) {
110 return rest_ensure_response( $blog_id );
111 }
112
113 $response = Client::wpcom_json_api_request_as_user(
114 sprintf( '/sites/%d/mcp-abilities', (int) $blog_id ),
115 '2',
116 array( 'method' => 'GET' ),
117 null,
118 'wpcom'
119 );
120
121 if ( is_wp_error( $response ) ) {
122 return $response;
123 }
124
125 $body = json_decode( wp_remote_retrieve_body( $response ), true );
126
127 // has_mcp_plan is set explicitly by the WPCOM endpoint using PaidPlanMiddleware.
128 // Fall back to 402/403 status handling for older WPCOM builds that predate the field.
129 $http_status = wp_remote_retrieve_response_code( $response );
130 $has_mcp_plan = isset( $body['has_mcp_plan'] )
131 ? (bool) $body['has_mcp_plan']
132 : ! in_array( $http_status, array( 402, 403 ), true );
133
134 if ( ! $has_mcp_plan ) {
135 return rest_ensure_response(
136 array(
137 'has_mcp_access' => false,
138 'mcp_abilities' => array(),
139 )
140 );
141 }
142
143 // Transform [ {name, title, readonly, site_context, enabled, …}, … ] → { name: {…}, … }.
144 // readonly and site_context are now provided by the WPCOM endpoint (wpcom#205108).
145 // Fall back to name-suffix heuristic for readonly only if the field is absent, so
146 // this endpoint remains functional against older WPCOM builds.
147 $account_abilities = new stdClass();
148 $site_abilities = array();
149 if ( ! empty( $body['abilities'] ) && is_array( $body['abilities'] ) ) {
150 $account_abilities = array();
151 foreach ( $body['abilities'] as $ability ) {
152 if ( empty( $ability['name'] ) ) {
153 continue;
154 }
155 if ( ! array_key_exists( 'readonly', $ability ) ) {
156 $ability['readonly'] = ! (bool) preg_match( '/-(create|update|delete)$/i', $ability['name'] );
157 }
158 $account_abilities[ (string) $ability['name'] ] = $ability;
159
160 // `site` subset: tools marked site_context=true by WPCOM are the only ones
161 // relevant to the site-level settings UI. getSiteContextToolIds() in JS uses
162 // this to filter out account/notifications/billing/domains tools.
163 if ( ! empty( $ability['site_context'] ) ) {
164 $site_abilities[ $ability['name'] ] = $ability;
165 }
166 }
167 }
168
169 // site_level_enabled comes directly from WPCOM — it is the authoritative effective
170 // state for this site (account default applied, site exceptions factored in).
171 // Fall back to the enabled-abilities heuristic only if the field is absent.
172 $site_level_enabled = isset( $body['site_level_enabled'] )
173 ? (bool) $body['site_level_enabled']
174 : ! empty(
175 array_filter(
176 (array) $account_abilities,
177 function ( $a ) {
178 return ! empty( $a['enabled'] );
179 }
180 )
181 );
182
183 // site_level_enabled_default mirrors Calypso: same value as site_level_enabled
184 // when derived from WPCOM (no per-site override concept at this layer).
185 $site_level_enabled_default = $site_level_enabled;
186
187 // Group descriptors (AIINT-469) — pass through if the WPCOM endpoint returns them.
188 $groups = array();
189 if ( isset( $body['groups'] ) && is_array( $body['groups'] ) ) {
190 $groups = $body['groups'];
191 }
192
193 // Use only the explicit per-site user overrides returned by WPCOM in user_overrides.abilities.
194 // These are the raw values stored via SettingsHelper — not the computed effective states
195 // (account defaults merged with overrides). Keeping only explicit overrides here lets the
196 // JS fall back to site_level_enabled as the default for any tool not yet overridden,
197 // matching Calypso's display behaviour (all tools on when site_level_enabled:true and no
198 // per-tool overrides exist).
199 $site_tool_abilities = array();
200 if ( isset( $body['user_overrides']['abilities'] ) && is_array( $body['user_overrides']['abilities'] ) ) {
201 foreach ( $body['user_overrides']['abilities'] as $name => $value ) {
202 $site_tool_abilities[ (string) $name ] = (bool) $value;
203 }
204 }
205
206 return rest_ensure_response(
207 array(
208 'has_mcp_access' => true,
209 'mcp_abilities' => array(
210 'account' => $account_abilities,
211 'site' => $site_abilities,
212 'sites' => array(
213 array(
214 'blog_id' => (int) $blog_id,
215 'site_level_enabled' => $site_level_enabled,
216 'abilities' => (object) $site_tool_abilities,
217 ),
218 ),
219 'site_level_enabled_default' => $site_level_enabled_default,
220 'groups' => $groups,
221 ),
222 )
223 );
224 }
225
226 /**
227 * Proxy mcp_abilities update to WPCOM POST /sites/{blog_id}/mcp-abilities.
228 *
229 * Accepts the sites[] format used by the client:
230 * { sites: [{ blog_id, site_level_enabled?, abilities?: { tool_id: bool } }] }
231 *
232 * Extracts site_level_enabled and abilities from sites[0] and forwards them
233 * to the WPCOM endpoint which persists them to user settings via SettingsHelper.
234 * This keeps Jetpack and Calypso in sync — both read/write the same store.
235 *
236 * @param WP_REST_Request $request Full details about the request.
237 * @return WP_REST_Response|WP_Error
238 */
239 public function update_mcp_settings( $request ) {
240 $blog_id = Manager::get_site_id();
241
242 if ( is_wp_error( $blog_id ) ) {
243 return rest_ensure_response( $blog_id );
244 }
245
246 $incoming = $request->get_param( 'mcp_abilities' );
247
248 if ( is_object( $incoming ) ) {
249 $incoming = get_object_vars( $incoming );
250 } elseif ( ! is_array( $incoming ) ) {
251 $incoming = array();
252 }
253
254 // Unpack sites[0] into the flat format WPCOM POST /sites/{id}/mcp-abilities expects.
255 $wpcom_body = array();
256
257 if ( ! empty( $incoming['sites'] ) && is_array( $incoming['sites'] ) ) {
258 $site_update = $incoming['sites'][0];
259 if ( is_object( $site_update ) ) {
260 $site_update = get_object_vars( $site_update );
261 }
262
263 if ( isset( $site_update['site_level_enabled'] ) ) {
264 $wpcom_body['site_level_enabled'] = (bool) $site_update['site_level_enabled'];
265 }
266
267 if ( isset( $site_update['abilities'] ) ) {
268 $abilities = is_object( $site_update['abilities'] )
269 ? get_object_vars( $site_update['abilities'] )
270 : (array) $site_update['abilities'];
271 $normalised = array();
272 foreach ( $abilities as $name => $value ) {
273 $normalised[ $name ] = (bool) $value;
274 }
275 $wpcom_body['abilities'] = $normalised;
276 }
277 }
278
279 if ( ! empty( $wpcom_body ) ) {
280 $response = Client::wpcom_json_api_request_as_user(
281 sprintf( '/sites/%d/mcp-abilities', (int) $blog_id ),
282 '2',
283 array( 'method' => 'POST' ),
284 $wpcom_body,
285 'wpcom'
286 );
287
288 if ( is_wp_error( $response ) ) {
289 return $response;
290 }
291
292 $status = wp_remote_retrieve_response_code( $response );
293 if ( $status < 200 || $status >= 300 ) {
294 return new WP_Error(
295 'wpcom_update_failed',
296 __( 'Failed to save MCP settings on WordPress.com.', 'jetpack' ),
297 array( 'status' => 502 )
298 );
299 }
300 }
301
302 return $this->get_mcp_settings();
303 }
304 }
305
306 wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_MCP_Settings' );
307