Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.49% covered (warning)
75.49%
77 / 102
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
REST_Recommendations_Evaluation
75.49% covered (warning)
75.49%
77 / 102
20.00% covered (danger)
20.00%
1 / 5
18.31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
60 / 60
100.00% covered (success)
100.00%
1 / 1
1
 permissions_callback
63.16% covered (warning)
63.16%
12 / 19
0.00% covered (danger)
0.00%
0 / 1
3.45
 evaluate_site_recommendations
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 save_evaluation_recommendations
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 dismiss_evaluation_recommendations
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Sets up the Evaluation Recommendations REST API endpoints.
4 *
5 * @package automattic/my-jetpack
6 */
7
8namespace Automattic\Jetpack\My_Jetpack;
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use WP_Error;
13
14/**
15 * Registers the REST routes for Evaluation Recommendations.
16 *
17 * @phan-constructor-used-for-side-effects
18 */
19class REST_Recommendations_Evaluation {
20    /**
21     * Constructor.
22     */
23    public function __construct() {
24        register_rest_route(
25            'my-jetpack/v1',
26            '/site/recommendations/evaluation/',
27            array(
28                array(
29                    'methods'             => \WP_REST_Server::READABLE,
30                    'callback'            => __CLASS__ . '::evaluate_site_recommendations',
31                    'permission_callback' => __CLASS__ . '::permissions_callback',
32                    'args'                => array(
33                        'goals' => array(
34                            'description' => __( 'List of goals selected by the user.', 'jetpack-my-jetpack' ),
35                            'type'        => 'array',
36                            'required'    => true,
37                            'items'       => array(
38                                'type' => 'string',
39                            ),
40                        ),
41                    ),
42                ),
43            )
44        );
45
46        register_rest_route(
47            'my-jetpack/v1',
48            '/site/recommendations/evaluation/result/',
49            array(
50                array(
51                    'methods'             => \WP_REST_Server::EDITABLE,
52                    'callback'            => __CLASS__ . '::save_evaluation_recommendations',
53                    'permission_callback' => __CLASS__ . '::permissions_callback',
54                    'args'                => array(
55                        'recommendations' => array(
56                            'description'          => __( 'Recommended module slugs mapped to their evaluation score.', 'jetpack-my-jetpack' ),
57                            'type'                 => 'object',
58                            'required'             => true,
59                            'additionalProperties' => array(
60                                'type' => 'number',
61                            ),
62                        ),
63                    ),
64                ),
65            )
66        );
67
68        register_rest_route(
69            'my-jetpack/v1',
70            '/site/recommendations/evaluation/result/',
71            array(
72                array(
73                    'methods'             => \WP_REST_Server::DELETABLE,
74                    'callback'            => __CLASS__ . '::dismiss_evaluation_recommendations',
75                    'permission_callback' => __CLASS__ . '::permissions_callback',
76                    'args'                => array(
77                        'showWelcomeBanner' => array(
78                            'description' => __( 'Whether the welcome banner should be shown again.', 'jetpack-my-jetpack' ),
79                            'type'        => 'string',
80                            'enum'        => array( 'true', 'false' ),
81                        ),
82                    ),
83                ),
84            )
85        );
86    }
87
88    /**
89     * Check user capability to access the endpoint.
90     *
91     * @access public
92     * @static
93     *
94     * @return true|WP_Error
95     */
96    public static function permissions_callback() {
97        $connection        = new Connection_Manager();
98        $is_site_connected = $connection->is_connected();
99
100        if ( ! $is_site_connected ) {
101            return new WP_Error(
102                'not_connected',
103                __( 'Your site is not connected to Jetpack.', 'jetpack-my-jetpack' ),
104                array(
105                    'status' => 400,
106                )
107            );
108        }
109
110        // These endpoints read and write site-wide options, so they are limited to administrators.
111        if ( ! current_user_can( 'manage_options' ) ) {
112            return new WP_Error(
113                'invalid_user_permission_manage_options',
114                __( 'You do not have the correct user permissions to perform this action.', 'jetpack-my-jetpack' ),
115                array(
116                    'status' => rest_authorization_required_code(),
117                )
118            );
119        }
120
121        return true;
122    }
123
124    /**
125     * Recommendations Evaluation endpoint.
126     *
127     * @param \WP_REST_Request $request Query request.
128     *
129     * @return \WP_REST_Response|WP_Error of 3 product slugs (recommendations).
130     */
131    public static function evaluate_site_recommendations( $request ) {
132        $goals = $request->get_param( 'goals' );
133
134        if ( ! isset( $goals ) ) {
135            return new WP_Error( 'missing_goals', 'Goals are required', array( 'status' => 400 ) );
136        }
137
138        $site_id        = \Jetpack_Options::get_option( 'id' );
139        $wpcom_endpoint = sprintf( '/sites/%1$d/jetpack-recommendations/evaluation?goals=%2$s', $site_id, implode( ',', array_map( 'rawurlencode', (array) $goals ) ) );
140        $response       = Client::wpcom_json_api_request_as_blog( $wpcom_endpoint, '2', array(), null, 'wpcom' );
141        $response_code  = wp_remote_retrieve_response_code( $response );
142        $body           = json_decode( wp_remote_retrieve_body( $response ) );
143
144        if ( is_wp_error( $response ) || empty( $body ) || 200 !== $response_code ) {
145            return new WP_Error( 'recommendations_evaluation_fetch_failed', 'Evaluation processing failed', array( 'status' => $response_code ? $response_code : 400 ) );
146        }
147
148        return rest_ensure_response( $body );
149    }
150
151    /**
152     * Endpoint to save recommendations results.
153     *
154     * @param \WP_REST_Request $request Query request.
155     *
156     * @return \WP_REST_Response|WP_Error success response.
157     */
158    public static function save_evaluation_recommendations( $request ) {
159        $json = $request->get_json_params();
160
161        if ( ! isset( $json['recommendations'] ) ) {
162            return new WP_Error( 'missing_recommendations', 'Recommendations are required', array( 'status' => 400 ) );
163        }
164
165        \Jetpack_Options::update_option( 'recommendations_evaluation', $json['recommendations'] );
166        \Jetpack_Options::delete_option( 'dismissed_recommendations' );
167
168        return rest_ensure_response( Initializer::get_recommended_modules() );
169    }
170
171    /**
172     * Endpoint to dismiss the recommendation section
173     *
174     * @param \WP_REST_Request $request Query request.
175     *
176     * @return \WP_REST_Response|WP_Error success response.
177     */
178    public static function dismiss_evaluation_recommendations( $request ) {
179        $show_welcome_banner = $request->get_param( 'showWelcomeBanner' );
180
181        \Jetpack_Options::update_option( 'dismissed_recommendations', true );
182
183        if ( isset( $show_welcome_banner ) && $show_welcome_banner === 'true' ) {
184            \Jetpack_Options::update_option( 'recommendations_first_run', false );
185            \Jetpack_Options::delete_option( 'dismissed_welcome_banner' );
186        }
187
188        return rest_ensure_response( array() );
189    }
190}