Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.90% covered (danger)
33.90%
20 / 59
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI
35.71% covered (danger)
35.71%
20 / 56
20.00% covered (danger)
20.00%
1 / 5
66.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_register_routes
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 register_routes
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 permission_callback
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 suggest_guidelines
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
72
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
11use Automattic\Jetpack\Connection\Client;
12
13if ( ! defined( 'ABSPATH' ) ) {
14    exit( 0 );
15}
16
17/**
18 * Class WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI
19 */
20class WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI extends WP_REST_Controller {
21    /**
22     * Namespace prefix.
23     *
24     * @var string
25     */
26    public $namespace = 'wpcom/v2';
27
28    /**
29     * Endpoint base route.
30     *
31     * @var string
32     */
33    public $rest_base = 'jetpack-ai/suggest-guidelines';
34
35    /**
36     * Constructor.
37     */
38    public function __construct() {
39        $this->is_wpcom                     = true;
40        $this->wpcom_is_wpcom_only_endpoint = true;
41
42        add_action( 'rest_api_init', array( $this, 'maybe_register_routes' ) );
43    }
44
45    /**
46     * Register routes on `rest_api_init`, gating on the AI feature state.
47     *
48     * The Jetpack_AI_Helper check (which loads the helper and instantiates
49     * Status/Host classes) runs here rather than in the constructor so that code
50     * is only loaded when the REST API is actually in use, not on every
51     * front-end, cron, or login request.
52     */
53    public function maybe_register_routes() {
54        if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
55            require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
56        }
57
58        // Intentionally gated to Simple and Atomic sites only.
59        // Self-hosted Jetpack support is deferred — we want to roll out
60        // on WordPress.com platforms first before opening to all connected sites.
61        if ( ! \Jetpack_AI_Helper::is_enabled() ) {
62            return;
63        }
64
65        $this->register_routes();
66    }
67
68    /**
69     * Register routes.
70     */
71    public function register_routes() {
72        register_rest_route(
73            $this->namespace,
74            '/' . $this->rest_base,
75            array(
76                'methods'             => WP_REST_Server::CREATABLE,
77                'callback'            => array( $this, 'suggest_guidelines' ),
78                'permission_callback' => array( $this, 'permission_callback' ),
79                'args'                => array(
80                    'categories' => array(
81                        'description' => __( 'Categories to generate guidelines for.', 'jetpack' ),
82                        'type'        => 'object',
83                        'required'    => true,
84                    ),
85                ),
86            )
87        );
88    }
89
90    /**
91     * Permission check — require manage_options.
92     *
93     * @return bool
94     */
95    public function permission_callback() {
96        return current_user_can( 'manage_options' );
97    }
98
99    /**
100     * Proxy the suggest-guidelines request to wpcom.
101     *
102     * @param WP_REST_Request $request The request object.
103     * @return mixed|WP_Error
104     */
105    public function suggest_guidelines( $request ) {
106        $blog_id = \Jetpack_Options::get_option( 'id' );
107
108        $body = array(
109            'categories' => $request->get_param( 'categories' ),
110        );
111
112        $response = Client::wpcom_json_api_request_as_blog(
113            sprintf( '/sites/%d/jetpack-ai/suggest-guidelines', $blog_id ) . '?force=wpcom',
114            '2',
115            array(
116                'method'  => 'POST',
117                'headers' => array( 'content-type' => 'application/json' ),
118                'timeout' => 90,
119            ),
120            wp_json_encode( $body, JSON_UNESCAPED_SLASHES ),
121            'wpcom'
122        );
123
124        if ( is_wp_error( $response ) ) {
125            return $response;
126        }
127
128        $status_code = wp_remote_retrieve_response_code( $response );
129        $body_str    = wp_remote_retrieve_body( $response );
130        $data        = json_decode( $body_str, true );
131
132        if ( $status_code !== 200 ) {
133            $message = is_array( $data ) && isset( $data['message'] ) ? $data['message'] : __( 'Failed to generate guidelines.', 'jetpack' );
134            $code    = is_array( $data ) && isset( $data['code'] ) ? $data['code'] : 'upstream_error';
135            return new WP_Error( $code, $message, array( 'status' => $status_code ) );
136        }
137
138        if ( JSON_ERROR_NONE !== json_last_error() ) {
139            return new WP_Error(
140                'invalid_response',
141                __( 'The guidelines service returned a malformed response.', 'jetpack' ),
142                array( 'status' => 502 )
143            );
144        }
145
146        return $data;
147    }
148}
149
150wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Agent_Guidelines_AI' );