PluginProbe
Code Snippets / 3.10.0-beta.1
Code Snippets v3.10.0-beta.1
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
code-snippets / php / REST_API / Cloud / Cloud_Snippets_REST_Controller.php

Cloud_Snippets_REST_Controller.php in Code Snippets 3.10.0-beta.1, at php/REST_API/Cloud/Cloud_Snippets_REST_Controller.php

374 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets\REST_API\Cloud;
4
5 use Code_Snippets\Admin\Menus\Manage\Manage_Menu;
6 use Code_Snippets\Controller\Cloud_Search_Controller;
7 use Code_Snippets\Model\Cloud_Snippets;
8 use Code_Snippets\REST_API\REST_Collection_Controller;
9 use WP_Error;
10 use WP_REST_Request;
11 use WP_REST_Response;
12 use WP_REST_Server;
13 use function Code_Snippets\code_snippets;
14 use function Code_Snippets\get_snippets;
15
16 /**
17 * Allows fetching cloud snippets through the WordPress REST API.
18 *
19 * @package Code_Snippets
20 */
21 final class Cloud_Snippets_REST_Controller extends REST_Collection_Controller {
22
23 /**
24 * Current API version.
25 */
26 public const VERSION = 1;
27
28 /**
29 * The base of this controller's route.
30 */
31 public const BASE_ROUTE = 'cloud/snippets';
32
33 /**
34 * Search controller instance.
35 *
36 * @var Cloud_Search_Controller
37 */
38 private Cloud_Search_Controller $search_controller;
39
40 /**
41 * Class constructor.
42 *
43 * @param Cloud_Search_Controller $search_controller Cloud search controller.
44 */
45 public function __construct( Cloud_Search_Controller $search_controller ) {
46 parent::__construct();
47 $this->search_controller = $search_controller;
48 }
49
50 /**
51 * Check the request from Cloud API is valid
52 *
53 * @param WP_REST_Request $request Full data about the request.
54 *
55 * @return bool
56 */
57 public function permission_callback( WP_REST_Request $request ): bool {
58 return code_snippets()->current_user_can() && $this->search_controller->verify_rest_request( $request );
59 }
60
61 /**
62 * Common filter args shared across search and featured endpoints.
63 *
64 * Each filter accepts a single numeric ID (e.g. category=12). The cloud API
65 * resolves IDs to the underlying name/slug.
66 *
67 * @return array<string, array<string, mixed>>
68 */
69 private function get_filter_args(): array {
70 return [
71 'category' => [
72 'description' => esc_html__( 'Filter by category ID.', 'code-snippets' ),
73 'type' => 'string',
74 'default' => '',
75 ],
76 'type' => [
77 'description' => esc_html__( 'Filter by language/type ID.', 'code-snippets' ),
78 'type' => 'string',
79 'default' => '',
80 ],
81 'status' => [
82 'description' => esc_html__( 'Filter by status ID.', 'code-snippets' ),
83 'type' => 'string',
84 'default' => '',
85 ],
86 ];
87 }
88
89 /**
90 * Extract filter values from a request.
91 *
92 * @param WP_REST_Request $request Request object.
93 *
94 * @return array<string, string>
95 */
96 private function extract_filters( WP_REST_Request $request ): array {
97 $filters = [];
98
99 foreach ( [ 'category', 'type', 'status' ] as $filter ) {
100 if ( $request->has_param( $filter ) ) {
101 $filters[ $filter ] = $request->get_param( $filter ) ?? '';
102 }
103 }
104
105 return $filters;
106 }
107
108 /**
109 * Register REST routes.
110 */
111 public function register_routes() {
112 $collection_args = $this->get_collection_params();
113 $filter_args = $this->get_filter_args();
114
115 register_rest_route(
116 $this->namespace,
117 $this->rest_base,
118 [
119 [
120 'methods' => WP_REST_Server::READABLE,
121 'callback' => [ $this, 'get_items' ],
122 'permission_callback' => [ $this, 'get_items_permissions_check' ],
123 'args' => array_merge(
124 [
125 'query' => [
126 'description' => esc_html__( 'Search query.', 'code-snippets' ),
127 'type' => 'string',
128 'required' => true,
129 ],
130 'searchByCodevault' => [
131 'description' => esc_html__( 'Treat the search query as the name of a CodeVault instead of a search term.', 'code-snippets' ),
132 'type' => 'boolean',
133 'default' => false,
134 ],
135 'page' => $collection_args['page'],
136 'per_page' => $collection_args['per_page'],
137 ],
138 $filter_args
139 ),
140 ],
141 'schema' => [ $this, 'get_item_schema' ],
142 ]
143 );
144
145 register_rest_route(
146 $this->namespace,
147 $this->rest_base . '/codevault',
148 [
149 [
150 'methods' => WP_REST_Server::READABLE,
151 'callback' => [ $this, 'get_codevault_items' ],
152 'permission_callback' => [ $this, 'get_items_permissions_check' ],
153 'args' => [ 'page' => $collection_args['page'] ],
154 'schema' => [ $this, 'get_item_schema' ],
155 ],
156 ]
157 );
158
159 register_rest_route(
160 $this->namespace,
161 $this->rest_base . '/featured',
162 [
163 [
164 'methods' => WP_REST_Server::READABLE,
165 'callback' => [ $this, 'get_featured_items' ],
166 'permission_callback' => [ $this, 'get_items_permissions_check' ],
167 'args' => array_merge(
168 $filter_args,
169 [
170 'page' => $collection_args['page'],
171 'per_page' => $collection_args['per_page'],
172 ]
173 ),
174 ],
175 'schema' => [ $this, 'get_item_schema' ],
176 ]
177 );
178
179 register_rest_route(
180 $this->namespace,
181 $this->rest_base . '/(?P<id>\d+)/download',
182 [
183 [
184 'methods' => WP_REST_Server::CREATABLE,
185 'callback' => [ $this, 'create_item' ],
186 'permission_callback' => [ $this, 'create_item_permissions_check' ],
187 'args' => [
188 'id' => [
189 'description' => esc_html__( 'Cloud snippet ID.', 'code-snippets' ),
190 'type' => 'number',
191 'required' => true,
192 ],
193 ],
194 ],
195 ]
196 );
197 }
198
199 /**
200 * Augment the standard controller collection query params for cloud searches.
201 *
202 * The per_page default is intentionally removed rather than set here: routes are
203 * registered once, so a schema default would capture a single user's Screen
204 * Options value. Callbacks resolve the per-user default at request time instead.
205 *
206 * @return array Query parameters for the collection.
207 */
208 public function get_collection_params(): array {
209 $params = parent::get_collection_params();
210 unset( $params['per_page']['default'] );
211 return $params;
212 }
213
214 /**
215 * Record which of the given cloud snippets have already been downloaded to this site.
216 *
217 * Downloading stores the remote identifier on the local snippet, so the local
218 * snippets are the only record of the link once the browser has been reloaded.
219 *
220 * @param Cloud_Snippets $snippets Cloud snippets as retrieved from the cloud API.
221 *
222 * @return Cloud_Snippets The same collection, with local identifiers attached.
223 */
224 private function attach_local_ids( Cloud_Snippets $snippets ): Cloud_Snippets {
225 $local_ids = [];
226
227 foreach ( get_snippets() as $local_snippet ) {
228 if ( $local_snippet->cloud_id && ! $local_snippet->trashed ) {
229 $local_ids[ $local_snippet->cloud_id ] = $local_snippet->id;
230 }
231 }
232
233 foreach ( $snippets->snippets as $cloud_snippet ) {
234 $cloud_snippet->local_id = $local_ids[ $cloud_snippet->id ] ?? null;
235 }
236
237 return $snippets;
238 }
239
240 /**
241 * Retrieve cloud snippets using a search query.
242 *
243 * @param WP_REST_Request $request The request object containing the search parameters.
244 *
245 * @return WP_REST_Response|WP_Error
246 */
247 public function get_items( $request ) {
248 $method = $request->get_param( 'searchByCodevault' ) ? 'codevault' : 'term';
249 $query = $request->get_param( 'query' ) ?? '';
250
251 $page = max( 1, intval( $request->get_param( 'page' ) ) );
252 $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
253 $filters = $this->extract_filters( $request );
254
255 $snippets = $this->search_controller
256 ->fetch_search_results( $method, $query, $page, $per_page, $filters );
257
258 return $snippets
259 ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
260 : new WP_Error(
261 'code_snippets_get_snippets_failure',
262 esc_html__( 'Could not fetch snippets.', 'code-snippets' ),
263 [ 'status' => 500 ]
264 );
265 }
266
267 /**
268 * Retrieve featured snippets from the cloud API.
269 *
270 * @param WP_REST_Request $request The request object.
271 *
272 * @return WP_REST_Response|WP_Error
273 */
274 public function get_featured_items( WP_REST_Request $request ) {
275 $page = max( 1, intval( $request->get_param( 'page' ) ) );
276 $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
277 $filters = $this->extract_filters( $request );
278
279 $snippets = $this->search_controller->get_featured_snippets( $page, $per_page, $filters );
280
281 return $snippets
282 ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
283 : new WP_Error(
284 'code_snippets_featured_snippets_failure',
285 esc_html__( 'Could not fetch featured snippets.', 'code-snippets' ),
286 [ 'status' => 500 ]
287 );
288 }
289
290 /**
291 * Download a single cloud snippet.
292 *
293 * @param WP_REST_Request $request The request object containing the search parameters.
294 *
295 * @return WP_REST_Response|WP_Error
296 */
297 public function create_item( $request ) {
298 $id = intval( $request->get_param( 'id' ) );
299 $cloud_snippet = $this->search_controller->get_cloud_snippet( $id );
300
301 if ( ! $cloud_snippet ) {
302 return new WP_Error(
303 'code_snippets_cloud_snippet_not_found',
304 esc_html__( 'Cloud snippet not found.', 'code-snippets' ),
305 [ 'status' => 404 ]
306 );
307 }
308
309 $local_snippet = $this->search_controller->download_snippet_from_cloud( $cloud_snippet );
310
311 return $local_snippet
312 ? rest_ensure_response(
313 [
314 'success' => true,
315 'snippet_id' => $local_snippet->id,
316 ]
317 )
318 : new WP_Error(
319 'code_snippets_cloud_snippet_download_failed',
320 esc_html__( 'Failed to create new snippet.', 'code-snippets' ),
321 [ 'status' => 500 ]
322 );
323 }
324
325 /**
326 * Retrieves the item's schema, conforming to JSON Schema.
327 *
328 * @return array
329 */
330 public function get_item_schema(): array {
331 if ( $this->schema ) {
332 return $this->schema;
333 }
334
335 $this->schema = [
336 '$schema' => 'http://json-schema.org/draft-04/schema#',
337 'title' => 'cloud snippet',
338 'type' => 'object',
339 'properties' => [
340 'id' => [
341 'description' => esc_html__( 'Cloud snippet identifier.', 'code-snippets' ),
342 'type' => 'string',
343 ],
344 'name' => [
345 'description' => esc_html__( 'Title of cloud snippet.', 'code-snippets' ),
346 'type' => 'string',
347 ],
348 'description' => [
349 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
350 'type' => 'string',
351 ],
352 'code' => [
353 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
354 'type' => 'string',
355 ],
356 'scope' => [
357 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
358 'type' => 'string',
359 ],
360 'created' => [
361 'description' => esc_html__( 'Date and time when the snippet was last created, in ISO format.', 'code-snippets' ),
362 'type' => 'string',
363 ],
364 'revision' => [
365 'description' => esc_html__( 'Snippet revision number.', 'code-snippets' ),
366 'type' => 'integer',
367 ],
368 ],
369 ];
370
371 return $this->schema;
372 }
373 }
374