PluginProbe
Code Snippets / 3.10.1
Code Snippets v3.10.1
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 3.0.1 All 64 releases
code-snippets / php / REST_API / Cloud / Cloud_Snippets_REST_Controller.php

Cloud_Snippets_REST_Controller.php in Code Snippets 3.10.1, at php/REST_API/Cloud/Cloud_Snippets_REST_Controller.php

360 lines 10.3 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 . '/featured',
148 [
149 [
150 'methods' => WP_REST_Server::READABLE,
151 'callback' => [ $this, 'get_featured_items' ],
152 'permission_callback' => [ $this, 'get_items_permissions_check' ],
153 'args' => array_merge(
154 $filter_args,
155 [
156 'page' => $collection_args['page'],
157 'per_page' => $collection_args['per_page'],
158 ]
159 ),
160 ],
161 'schema' => [ $this, 'get_item_schema' ],
162 ]
163 );
164
165 register_rest_route(
166 $this->namespace,
167 $this->rest_base . '/(?P<id>\d+)/download',
168 [
169 [
170 'methods' => WP_REST_Server::CREATABLE,
171 'callback' => [ $this, 'create_item' ],
172 'permission_callback' => [ $this, 'create_item_permissions_check' ],
173 'args' => [
174 'id' => [
175 'description' => esc_html__( 'Cloud snippet ID.', 'code-snippets' ),
176 'type' => 'number',
177 'required' => true,
178 ],
179 ],
180 ],
181 ]
182 );
183 }
184
185 /**
186 * Augment the standard controller collection query params for cloud searches.
187 *
188 * The per_page default is intentionally removed rather than set here: routes are
189 * registered once, so a schema default would capture a single user's Screen
190 * Options value. Callbacks resolve the per-user default at request time instead.
191 *
192 * @return array Query parameters for the collection.
193 */
194 public function get_collection_params(): array {
195 $params = parent::get_collection_params();
196 unset( $params['per_page']['default'] );
197 return $params;
198 }
199
200 /**
201 * Record which of the given cloud snippets have already been downloaded to this site.
202 *
203 * Downloading stores the remote identifier on the local snippet, so the local
204 * snippets are the only record of the link once the browser has been reloaded.
205 *
206 * @param Cloud_Snippets $snippets Cloud snippets as retrieved from the cloud API.
207 *
208 * @return Cloud_Snippets The same collection, with local identifiers attached.
209 */
210 private function attach_local_ids( Cloud_Snippets $snippets ): Cloud_Snippets {
211 $local_ids = [];
212
213 foreach ( get_snippets() as $local_snippet ) {
214 if ( $local_snippet->cloud_id && ! $local_snippet->trashed ) {
215 $local_ids[ $local_snippet->cloud_id ] = $local_snippet->id;
216 }
217 }
218
219 foreach ( $snippets->snippets as $cloud_snippet ) {
220 $cloud_snippet->local_id = $local_ids[ $cloud_snippet->id ] ?? null;
221 }
222
223 return $snippets;
224 }
225
226 /**
227 * Retrieve cloud snippets using a search query.
228 *
229 * @param WP_REST_Request $request The request object containing the search parameters.
230 *
231 * @return WP_REST_Response|WP_Error
232 */
233 public function get_items( $request ) {
234 $method = $request->get_param( 'searchByCodevault' ) ? 'codevault' : 'term';
235 $query = $request->get_param( 'query' ) ?? '';
236
237 $page = max( 1, intval( $request->get_param( 'page' ) ) );
238 $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
239 $filters = $this->extract_filters( $request );
240
241 $snippets = $this->search_controller
242 ->fetch_search_results( $method, $query, $page, $per_page, $filters );
243
244 return $snippets
245 ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
246 : new WP_Error(
247 'code_snippets_get_snippets_failure',
248 esc_html__( 'Could not fetch snippets.', 'code-snippets' ),
249 [ 'status' => 500 ]
250 );
251 }
252
253 /**
254 * Retrieve featured snippets from the cloud API.
255 *
256 * @param WP_REST_Request $request The request object.
257 *
258 * @return WP_REST_Response|WP_Error
259 */
260 public function get_featured_items( WP_REST_Request $request ) {
261 $page = max( 1, intval( $request->get_param( 'page' ) ) );
262 $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
263 $filters = $this->extract_filters( $request );
264
265 $snippets = $this->search_controller->get_featured_snippets( $page, $per_page, $filters );
266
267 return $snippets
268 ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
269 : new WP_Error(
270 'code_snippets_featured_snippets_failure',
271 esc_html__( 'Could not fetch featured snippets.', 'code-snippets' ),
272 [ 'status' => 500 ]
273 );
274 }
275
276 /**
277 * Download a single cloud snippet.
278 *
279 * @param WP_REST_Request $request The request object containing the search parameters.
280 *
281 * @return WP_REST_Response|WP_Error
282 */
283 public function create_item( $request ) {
284 $id = intval( $request->get_param( 'id' ) );
285 $cloud_snippet = $this->search_controller->get_cloud_snippet( $id );
286
287 if ( ! $cloud_snippet ) {
288 return new WP_Error(
289 'code_snippets_cloud_snippet_not_found',
290 esc_html__( 'Cloud snippet not found.', 'code-snippets' ),
291 [ 'status' => 404 ]
292 );
293 }
294
295 $local_snippet = $this->search_controller->download_snippet_from_cloud( $cloud_snippet );
296
297 return $local_snippet
298 ? rest_ensure_response(
299 [
300 'success' => true,
301 'snippet_id' => $local_snippet->id,
302 ]
303 )
304 : new WP_Error(
305 'code_snippets_cloud_snippet_download_failed',
306 esc_html__( 'Failed to create new snippet.', 'code-snippets' ),
307 [ 'status' => 500 ]
308 );
309 }
310
311 /**
312 * Retrieves the item's schema, conforming to JSON Schema.
313 *
314 * @return array
315 */
316 public function get_item_schema(): array {
317 if ( $this->schema ) {
318 return $this->schema;
319 }
320
321 $this->schema = [
322 '$schema' => 'http://json-schema.org/draft-04/schema#',
323 'title' => 'cloud snippet',
324 'type' => 'object',
325 'properties' => [
326 'id' => [
327 'description' => esc_html__( 'Cloud snippet identifier.', 'code-snippets' ),
328 'type' => 'string',
329 ],
330 'name' => [
331 'description' => esc_html__( 'Title of cloud snippet.', 'code-snippets' ),
332 'type' => 'string',
333 ],
334 'description' => [
335 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
336 'type' => 'string',
337 ],
338 'code' => [
339 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
340 'type' => 'string',
341 ],
342 'scope' => [
343 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
344 'type' => 'string',
345 ],
346 'created' => [
347 'description' => esc_html__( 'Date and time when the snippet was last created, in ISO format.', 'code-snippets' ),
348 'type' => 'string',
349 ],
350 'revision' => [
351 'description' => esc_html__( 'Snippet revision number.', 'code-snippets' ),
352 'type' => 'integer',
353 ],
354 ],
355 ];
356
357 return $this->schema;
358 }
359 }
360