Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Endpoint
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 11
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 register_rest_routes
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
20
 handle_get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handle_set
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handle_merge
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handle_delete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 response_error
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 response_success
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 handler
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 create_nonce
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 permissions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Register and handle REST API Endpoints for each data sync entry.
4 *
5 * @package automattic/jetpack-wp-js-data-sync
6 */
7
8namespace Automattic\Jetpack\WP_JS_Data_Sync\Endpoints;
9
10use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Data_Sync_Entry;
11use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Entry_Can_Delete;
12use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Entry_Can_Get;
13use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Entry_Can_Merge;
14use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Entry_Can_Set;
15use Automattic\Jetpack\WP_JS_Data_Sync\DS_Utils;
16
17class Endpoint {
18
19    /**
20     * @var Data_Sync_Entry $entry - The data sync entry to register the endpoint for.
21     */
22    private $entry;
23
24    /**
25     * @var string $rest_namespace - The namespace for the REST API endpoint.
26     */
27    private $rest_namespace;
28
29    /**
30     * @var string $route_base - The route for the REST API endpoint.
31     */
32    private $route_base;
33
34    /**
35     * @var Authenticated_Nonce $nonce - The nonce for the REST API endpoint.
36     */
37    private $nonce;
38
39    /**
40     * @param string          $namespace - The namespace for the REST API endpoint.
41     * @param string          $route     - The route for the REST API endpoint.
42     * @param Data_Sync_Entry $entry     The data sync entry to register the endpoint for.
43     */
44    public function __construct( $namespace, $route, $entry ) {
45        $this->entry          = $entry;
46        $this->rest_namespace = $namespace;
47        $this->route_base     = $route;
48        $this->nonce          = new Authenticated_Nonce( "{$namespace}_{$route}" );
49    }
50
51    public function register_rest_routes() {
52
53        register_rest_route(
54            $this->rest_namespace,
55            $this->route_base,
56            array(
57                'methods'             => 'GET, POST',
58                'callback'            => array( $this, 'handle_get' ),
59                'permission_callback' => array( $this, 'permissions' ),
60            )
61        );
62
63        if ( $this->entry->is( Entry_Can_Set::class ) ) {
64            register_rest_route(
65                $this->rest_namespace,
66                $this->route_base . '/set',
67                array(
68                    'methods'             => \WP_REST_Server::EDITABLE,
69                    'callback'            => array( $this, 'handle_set' ),
70                    'permission_callback' => array( $this, 'permissions' ),
71                )
72            );
73        }
74
75        if ( $this->entry->is( Entry_Can_Merge::class ) ) {
76            register_rest_route(
77                $this->rest_namespace,
78                $this->route_base . '/merge',
79                array(
80                    'methods'             => \WP_REST_Server::EDITABLE,
81                    'callback'            => array( $this, 'handle_merge' ),
82                    'permission_callback' => array( $this, 'permissions' ),
83                )
84            );
85        }
86
87        if ( $this->entry->is( Entry_Can_Delete::class ) ) {
88            register_rest_route(
89                $this->rest_namespace,
90                $this->route_base . '/delete',
91                array(
92                    'methods'             => 'POST, DELETE',
93                    'callback'            => array( $this, 'handle_delete' ),
94                    'permission_callback' => array( $this, 'permissions' ),
95                )
96            );
97        }
98    }
99
100    /**
101     * Handle GET Requests on /wp-json/<namespace>/<route>
102     *
103     * @param \WP_REST_Request $request - The request object.
104     */
105    public function handle_get( $request ) {
106        return $this->handler( $request, 'get' );
107    }
108
109    /**
110     * Handle POST, PUT, PATCH Requests on /wp-json/<namespace>/<route>/set
111     *
112     * @param \WP_REST_Request $request - The request object.
113     */
114    public function handle_set( $request ) {
115        return $this->handler( $request, 'set' );
116    }
117
118    /**
119     * Handle POST, PUT, PATCH Requests on /wp-json/<namespace>/<route>/merge
120     *
121     * @param \WP_REST_Request $request - The request object.
122     */
123    public function handle_merge( $request ) {
124        return $this->handler( $request, 'merge' );
125    }
126
127    /**
128     * Handle POST, DELETE Requests on /wp-json/<namespace>/<route>/delete
129     *
130     * @param \WP_REST_Request $request - The request object.
131     */
132    public function handle_delete( $request ) {
133        return $this->handler( $request, 'delete' );
134    }
135
136    private function response_error( $message, $code ) {
137        return rest_ensure_response(
138            array(
139                'status'  => 'error',
140                'message' => $message,
141                'code'    => $code,
142            )
143        );
144    }
145
146    private function response_success( $data ) {
147        $response = array(
148            'status' => 'success',
149            'JSON'   => $data,
150        );
151        if ( DS_Utils::is_debug() ) {
152            $response['log'] = $this->entry->get_parser()->get_log();
153        }
154        return rest_ensure_response( $response );
155    }
156
157    /**
158     * Route the request to the apropriate handler.
159     *
160     * @param \WP_REST_Request $request - The request object.
161     */
162    private function handler( $request, $entry_method = 'get' ) {
163
164        $available_methods = array(
165            'get'    => Entry_Can_Get::class,
166            'set'    => Entry_Can_Set::class,
167            'merge'  => Entry_Can_Merge::class,
168            'delete' => Entry_Can_Delete::class,
169        );
170        if ( ! isset( $available_methods[ $entry_method ] ) ) {
171            // Set status 400 because an unsupported method was used.
172            return rest_ensure_response( new \WP_Error( 'invalid_method', 'Invalid method.', array( 'status' => 400 ) ) );
173        }
174
175        if ( ! $this->entry->is( $available_methods[ $entry_method ] ) ) {
176            // Set Status 500 because the method is valid but is missing in Data_Sync_Entry.
177            return rest_ensure_response( new \WP_Error( 'invalid_method', 'Invalid method. "' . $entry_method . '" ' ) );
178        }
179
180        try {
181            $params = $request->get_json_params();
182            $data   = $params['JSON'] ?? null;
183            $result = $this->entry->$entry_method( $data );
184
185            if ( true === DS_Utils::debug_disable( $this->route_base ) ) {
186                // Return 418 I'm a teapot if this is a debug request to the endpoint.
187                return rest_ensure_response( new \WP_Error( 'teapot', "I'm a teapot.", array( 'status' => 418 ) ) );
188            }
189
190            if ( is_wp_error( $result ) ) {
191                return $this->response_error( $result->get_error_message(), $result->get_error_code() );
192            }
193
194            return $this->response_success( $result );
195        } catch ( \RuntimeException $e ) {
196            return $this->response_error( $e->getMessage(), 500 );
197        }
198    }
199
200    /**
201     * Create a nonce for this endpoint
202     *
203     * @return false|string
204     */
205    public function create_nonce() {
206        return $this->nonce->create();
207    }
208
209    /**
210     * @param \WP_REST_Request $request
211     */
212    public function permissions( $request ) {
213        $nonce = $request->get_header( 'X-Jetpack-WP-JS-Sync-Nonce' );
214        return $this->nonce->verify( $nonce ) && current_user_can( 'manage_options' );
215    }
216}