PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
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
← All changes | php/REST_API/Cloud/Cloud_Snippets_REST_Controller.php +182 -159 4.0.0-beta.1 → trunk View file →
@@ -1,17 +1,18 @@
1 1 <?php
2 2
3 3 namespace Code_Snippets\REST_API\Cloud;
4 4
5 -use Code_Snippets\Client\Cloud_API;
6 -use Code_Snippets\Model\Cloud_Link;
7 -use Code_Snippets\Model\Snippet;
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 8 use Code_Snippets\REST_API\REST_Collection_Controller;
9 9 use WP_Error;
10 10 use WP_REST_Request;
11 11 use WP_REST_Response;
12 12 use WP_REST_Server;
13 -use function Code_Snippets\save_snippet;
13 +use function Code_Snippets\code_snippets;
14 +use function Code_Snippets\get_snippets;
14 15
15 16 /**
16 17 * Allows fetching cloud snippets through the WordPress REST API.
17 18 *
@@ -16,9 +17,9 @@
16 17 * Allows fetching cloud snippets through the WordPress REST API.
17 18 *
18 19 * @package Code_Snippets
19 20 */
20 -final class Cloud_Snippets_REST_Controller extends Cloud_Collection_REST_Controller {
21 +final class Cloud_Snippets_REST_Controller extends REST_Collection_Controller {
21 22
22 23 /**
23 24 * Current API version.
24 25 */
@@ -29,8 +30,41 @@
29 30 */
30 31 public const BASE_ROUTE = 'cloud/snippets';
31 32
32 33 /**
34 + * Search methods supported by the cloud API.
35 + */
36 + private const SEARCH_METHODS = [ 'term', 'codevault', 'ai' ];
37 +
38 + /**
39 + * Search controller instance.
40 + *
41 + * @var Cloud_Search_Controller
42 + */
43 + private Cloud_Search_Controller $search_controller;
44 +
45 + /**
46 + * Class constructor.
47 + *
48 + * @param Cloud_Search_Controller $search_controller Cloud search controller.
49 + */
50 + public function __construct( Cloud_Search_Controller $search_controller ) {
51 + parent::__construct();
52 + $this->search_controller = $search_controller;
53 + }
54 +
55 + /**
56 + * Check the request from Cloud API is valid
57 + *
58 + * @param WP_REST_Request $request Full data about the request.
59 + *
60 + * @return bool
61 + */
62 + public function permission_callback( WP_REST_Request $request ): bool {
63 + return code_snippets()->current_user_can() && $this->search_controller->verify_rest_request( $request );
64 + }
65 +
66 + /**
33 67 * Common filter args shared across search and featured endpoints.
34 68 *
35 69 * Each filter accepts a single numeric ID (e.g. category=12). The cloud API
36 70 * resolves IDs to the underlying name/slug.
@@ -64,15 +98,17 @@
64 98 *
65 99 * @return array<string, string>
66 100 */
67 101 private function extract_filters( WP_REST_Request $request ): array {
68 - return array_filter(
69 - [
70 - 'category' => $request->get_param( 'category' ) ?? '',
71 - 'type' => $request->get_param( 'type' ) ?? '',
72 - 'status' => $request->get_param( 'status' ) ?? '',
73 - ]
74 - );
102 + $filters = [];
103 +
104 + foreach ( [ 'category', 'type', 'status' ] as $filter ) {
105 + if ( $request->has_param( $filter ) ) {
106 + $filters[ $filter ] = $request->get_param( $filter ) ?? '';
107 + }
108 + }
109 +
110 + return $filters;
75 111 }
76 112
77 113 /**
78 114 * Register REST routes.
@@ -78,9 +114,8 @@
78 114 * Register REST routes.
79 115 */
80 116 public function register_routes() {
81 117 $collection_args = $this->get_collection_params();
82 - $collection_args['per_page']['default'] = $this->get_snippets_per_page();
83 118 $filter_args = $this->get_filter_args();
84 119
85 120 register_rest_route(
86 121 $this->namespace,
@@ -90,10 +125,8 @@
90 125 'methods' => WP_REST_Server::READABLE,
91 126 'callback' => [ $this, 'get_items' ],
92 127 'permission_callback' => [ $this, 'get_items_permissions_check' ],
93 128 'args' => array_merge(
94 - $collection_args,
95 - $filter_args,
96 129 [
97 130 'query' => [
98 131 'description' => esc_html__( 'Search query.', 'code-snippets' ),
99 132 'type' => 'string',
@@ -103,14 +136,18 @@
103 136 'description' => esc_html__( 'Treat the search query as the name of a CodeVault instead of a search term.', 'code-snippets' ),
104 137 'type' => 'boolean',
105 138 'default' => false,
106 139 ],
107 - 'page' => [
108 - 'description' => esc_html__( 'Page number.', 'code-snippets' ),
109 - 'type' => 'integer',
110 - 'default' => 1,
140 + 'searchMethod' => [
141 + 'description' => esc_html__( 'Optional search method. Set to "ai" to match snippets by natural-language intent.', 'code-snippets' ),
142 + 'type' => 'string',
143 + 'enum' => self::SEARCH_METHODS,
144 + 'default' => 'term',
111 145 ],
112 - ]
146 + 'page' => $collection_args['page'],
147 + 'per_page' => $collection_args['per_page'],
148 + ],
149 + $filter_args
113 150 ),
114 151 ],
115 152 'schema' => [ $this, 'get_item_schema' ],
116 153 ]
@@ -117,22 +154,8 @@
117 154 );
118 155
119 156 register_rest_route(
120 157 $this->namespace,
121 - $this->rest_base,
122 - [
123 - [
124 - 'methods' => WP_REST_Server::CREATABLE,
125 - 'callback' => [ $this, 'create_item_from_cloud' ],
126 - 'permission_callback' => [ $this, 'create_item_permissions_check' ],
127 - 'args' => rest_get_endpoint_args_for_schema( $this->get_cloud_snippet_schema() ),
128 - ],
129 - 'schema' => [ $this, 'get_cloud_snippet_schema' ],
130 - ]
131 - );
132 -
133 - register_rest_route(
134 - $this->namespace,
135 158 $this->rest_base . '/featured',
136 159 [
137 160 [
138 161 'methods' => WP_REST_Server::READABLE,
@@ -140,18 +163,10 @@
140 163 'permission_callback' => [ $this, 'get_items_permissions_check' ],
141 164 'args' => array_merge(
142 165 $filter_args,
143 166 [
144 - 'page' => [
145 - 'description' => esc_html__( 'Page number.', 'code-snippets' ),
146 - 'type' => 'integer',
147 - 'default' => 1,
148 - ],
149 - 'per_page' => [
150 - 'description' => esc_html__( 'Results per page.', 'code-snippets' ),
151 - 'type' => 'integer',
152 - 'default' => $this->get_snippets_per_page(),
153 - ],
167 + 'page' => $collection_args['page'],
168 + 'per_page' => $collection_args['per_page'],
154 169 ]
155 170 ),
156 171 ],
157 172 'schema' => [ $this, 'get_item_schema' ],
@@ -163,9 +178,9 @@
163 178 $this->rest_base . '/(?P<id>\d+)/download',
164 179 [
165 180 [
166 181 'methods' => WP_REST_Server::CREATABLE,
167 - 'callback' => [ $this, 'download_item' ],
182 + 'callback' => [ $this, 'create_item' ],
168 183 'permission_callback' => [ $this, 'create_item_permissions_check' ],
169 184 'args' => [
170 185 'id' => [
171 186 'description' => esc_html__( 'Cloud snippet ID.', 'code-snippets' ),
@@ -178,90 +193,159 @@
178 193 );
179 194 }
180 195
181 196 /**
182 - * Retrieve cloud snippets using a search query.
197 + * Augment the standard controller collection query params for cloud searches.
183 198 *
184 - * @param WP_REST_Request $request The request object containing the search parameters.
199 + * The per_page default is intentionally removed rather than set here: routes are
200 + * registered once, so a schema default would capture a single user's Screen
201 + * Options value. Callbacks resolve the per-user default at request time instead.
185 202 *
186 - * @return WP_REST_Response
203 + * @return array Query parameters for the collection.
187 204 */
188 - public function get_items( $request ): WP_REST_Response {
189 - $method = $request->get_param( 'searchByCodevault' ) ? 'codevault' : 'term';
190 - $query = $request->get_param( 'query' );
191 - $query_params = $request->get_query_params();
192 - $page = max( 1, (int) $request->get_param( 'page' ) );
193 - $per_page = isset( $query_params['per_page'] )
194 - ? min( Cloud_API::MAX_RESULTS_PER_PAGE, max( 1, (int) $request->get_param( 'per_page' ) ) )
195 - : $this->get_snippets_per_page();
205 + public function get_collection_params(): array {
206 + $params = parent::get_collection_params();
207 + unset( $params['per_page']['default'] );
208 + return $params;
209 + }
196 210
197 - $filters = $this->extract_filters( $request );
198 - $cloud_snippets = $this->cloud_api->fetch_search_results( $method, $query, $page, $per_page, $filters );
199 - return $cloud_snippets->to_rest_response();
211 + /**
212 + * Record which of the given cloud snippets have already been downloaded to this site.
213 + *
214 + * Downloading stores the remote identifier on the local snippet, so the local
215 + * snippets are the only record of the link once the browser has been reloaded.
216 + *
217 + * @param Cloud_Snippets $snippets Cloud snippets as retrieved from the cloud API.
218 + *
219 + * @return Cloud_Snippets The same collection, with local identifiers attached.
220 + */
221 + private function attach_local_ids( Cloud_Snippets $snippets ): Cloud_Snippets {
222 + $local_ids = [];
223 +
224 + foreach ( get_snippets() as $local_snippet ) {
225 + if ( $local_snippet->cloud_id && ! $local_snippet->trashed ) {
226 + $local_ids[ $local_snippet->cloud_id ] = $local_snippet->id;
227 + }
228 + }
229 +
230 + foreach ( $snippets->snippets as $cloud_snippet ) {
231 + $cloud_snippet->local_id = $local_ids[ $cloud_snippet->id ] ?? null;
232 + }
233 +
234 + return $snippets;
200 235 }
201 236
202 237 /**
203 - * Retrieve featured snippets from the cloud API.
238 + * Determine the search method to use based on request parameters.
204 239 *
205 - * @param WP_REST_Request $request The request object.
240 + * @param WP_REST_Request $request The request object containing the search parameters.
206 241 *
207 - * @return WP_REST_Response
242 + * @return string The search method to use.
208 243 */
209 - public function get_featured_items( WP_REST_Request $request ): WP_REST_Response {
210 - $page = max( 1, (int) $request->get_param( 'page' ) );
211 - $query_params = $request->get_query_params();
244 + private function get_search_method( WP_REST_Request $request ): string {
245 + $search_by_codevault = $request->get_param( 'searchByCodevault' );
246 + if ( $search_by_codevault ) {
247 + return 'codevault';
248 + }
212 249
213 - $per_page = isset( $query_params['per_page'] )
214 - ? min( Cloud_API::MAX_RESULTS_PER_PAGE, max( 1, (int) $request->get_param( 'per_page' ) ) )
215 - : $this->get_snippets_per_page();
216 -
217 - $filters = $this->extract_filters( $request );
218 - $cloud_snippets = $this->cloud_api->get_featured_snippets( $page, $per_page, $filters );
219 - return $cloud_snippets->to_rest_response();
250 + $method_param = $request->get_param( 'searchMethod' );
251 + return in_array( $method_param, self::SEARCH_METHODS, true ) ? $method_param : 'term';
220 252 }
221 253
222 254 /**
223 - * Retrieve available snippet types (languages) from the cloud API.
255 + * Retrieve cloud snippets using a search query.
224 256 *
225 - * @return WP_REST_Response
257 + * @param WP_REST_Request $request The request object containing the search parameters.
258 + *
259 + * @return WP_REST_Response|WP_Error
226 260 */
227 - public function get_types(): WP_REST_Response {
228 - return rest_ensure_response( $this->cloud_api->get_cloud_types() );
261 + public function get_items( $request ) {
262 + $query = $request->get_param( 'query' ) ?? '';
263 + $page = max( 1, intval( $request->get_param( 'page' ) ) );
264 + $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
265 + $filters = $this->extract_filters( $request );
266 +
267 + $snippets = $this->search_controller
268 + ->fetch_search_results( $this->get_search_method( $request ), $query, $page, $per_page, $filters );
269 +
270 + return $snippets
271 + ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
272 + : new WP_Error(
273 + 'code_snippets_get_snippets_failure',
274 + esc_html__( 'Could not fetch snippets.', 'code-snippets' ),
275 + [ 'status' => 500 ]
276 + );
229 277 }
230 278
231 279 /**
232 - * Retrieve available snippet categories from the cloud API.
280 + * Retrieve featured snippets from the cloud API.
233 281 *
234 - * @return WP_REST_Response
282 + * @param WP_REST_Request $request The request object.
283 + *
284 + * @return WP_REST_Response|WP_Error
235 285 */
236 - public function get_categories(): WP_REST_Response {
237 - return rest_ensure_response( $this->cloud_api->get_cloud_categories() );
286 + public function get_featured_items( WP_REST_Request $request ) {
287 + $page = max( 1, intval( $request->get_param( 'page' ) ) );
288 + $per_page = intval( $request->get_param( 'per_page' ) ?? Manage_Menu::get_cloud_search_per_page() );
289 + $filters = $this->extract_filters( $request );
290 +
291 + $snippets = $this->search_controller->get_featured_snippets( $page, $per_page, $filters );
292 +
293 + return $snippets
294 + ? rest_ensure_response( $this->attach_local_ids( $snippets )->to_rest_response() )
295 + : new WP_Error(
296 + 'code_snippets_featured_snippets_failure',
297 + esc_html__( 'Could not fetch featured snippets.', 'code-snippets' ),
298 + [ 'status' => 500 ]
299 + );
238 300 }
239 301
240 302 /**
241 - * Get the user's snippets per-page preference for Screen Options pagination.
303 + * Download a single cloud snippet.
242 304 *
243 - * @return int
305 + * @param WP_REST_Request $request The request object containing the search parameters.
306 + *
307 + * @return WP_REST_Response|WP_Error
244 308 */
245 - private function get_snippets_per_page(): int {
246 - $per_page = (int) get_user_option( 'snippets_per_page' );
309 + public function create_item( $request ) {
310 + $id = intval( $request->get_param( 'id' ) );
311 + $cloud_snippet = $this->search_controller->get_cloud_snippet( $id );
247 312
248 - return $per_page > 0 ? min( Cloud_API::MAX_RESULTS_PER_PAGE, $per_page ) : 10;
313 + if ( ! $cloud_snippet ) {
314 + return new WP_Error(
315 + 'code_snippets_cloud_snippet_not_found',
316 + esc_html__( 'Cloud snippet not found.', 'code-snippets' ),
317 + [ 'status' => 404 ]
318 + );
319 + }
320 +
321 + $local_snippet = $this->search_controller->download_snippet_from_cloud( $cloud_snippet );
322 +
323 + return $local_snippet
324 + ? rest_ensure_response(
325 + [
326 + 'success' => true,
327 + 'snippet_id' => $local_snippet->id,
328 + ]
329 + )
330 + : new WP_Error(
331 + 'code_snippets_cloud_snippet_download_failed',
332 + esc_html__( 'Failed to create new snippet.', 'code-snippets' ),
333 + [ 'status' => 500 ]
334 + );
249 335 }
250 336
251 337 /**
252 - * Get the schema for a cloud snippet request body.
338 + * Retrieves the item's schema, conforming to JSON Schema.
253 339 *
254 340 * @return array
255 341 */
256 - public function get_cloud_snippet_schema(): array {
257 - static $schema = null;
258 -
259 - if ( ! is_null( $schema ) ) {
260 - return $schema;
342 + public function get_item_schema(): array {
343 + if ( $this->schema ) {
344 + return $this->schema;
261 345 }
262 346
263 - $schema = [
347 + $this->schema = [
264 348 '$schema' => 'http://json-schema.org/draft-04/schema#',
265 349 'title' => 'cloud snippet',
266 350 'type' => 'object',
267 351 'properties' => [
@@ -292,76 +376,15 @@
292 376 'revision' => [
293 377 'description' => esc_html__( 'Snippet revision number.', 'code-snippets' ),
294 378 'type' => 'integer',
295 379 ],
380 + 'active' => [
381 + 'description' => esc_html__( 'Whether the snippet should be active on the receiving site. Defaults to false to preserve install-as-inactive behaviour; set true for the self-heal redeploy path.', 'code-snippets' ),
382 + 'type' => 'boolean',
383 + 'default' => false,
384 + ],
296 385 ],
297 386 ];
298 387
299 - return $schema;
300 - }
301 -
302 - /**
303 - * Create one item from the collection
304 - *
305 - * @param WP_REST_Request $request Full data about the request.
306 - *
307 - * @return WP_REST_Response|WP_Error
308 - */
309 - public function create_item( $request ) {
310 - $body = json_decode( $request->get_body() );
311 - $snippet_data = json_decode( $body[0], true );
312 -
313 - $snippet = new Snippet();
314 -
315 - $snippet->name = $snippet_data['name'];
316 - $snippet->desc = $snippet_data['description'];
317 - $snippet->code = $snippet_data['code'];
318 - $snippet->scope = $snippet_data['scope'];
319 - $snippet->modified = $snippet_data['created'];
320 - $snippet->revision = $snippet_data['revision'] ?? 1;
321 - $snippet->cloud_id = $snippet_data['id'] . '_0'; // Set to not owner.
322 - $snippet->shared_network = false;
323 - $snippet->network = false;
324 - $snippet->active = false;
325 -
326 - $result = save_snippet( $snippet );
327 -
328 - if ( ! $result ) {
329 - return new WP_Error(
330 - 'rest_cannot_create',
331 - __( 'The snippet could not be created.', 'code-snippets' ),
332 - [ 'status' => 500 ]
333 - );
334 - }
335 -
336 - $link = new Cloud_Link();
337 - $link->local_id = $snippet_data['id'];
338 - $link->cloud_id = $snippet->cloud_id;
339 - $link->is_owner = false;
340 - $link->in_codevault = false;
341 - $link->update_available = false;
342 -
343 - $this->cloud_api->add_cloud_link( $link );
344 -
345 - $response = [
346 - 'status' => 'success',
347 - 'message' => __( 'Snippet created', 'code-snippets' ),
348 - ];
349 -
350 - return rest_ensure_response( $response );
351 - }
352 -
353 - /**
354 - * Download a single cloud snippet.
355 - *
356 - * @param WP_REST_Request $request The request object containing the search parameters.
357 - *
358 - * @return WP_REST_Response
359 - */
360 - public function download_item( WP_REST_Request $request ): WP_REST_Response {
361 - $id = $request->get_param( 'id' );
362 - $in_codevault = $request->get_param( 'in_codevault' );
363 -
364 - $cloud_snippet = $this->cloud_api->get_single_snippet_from_cloud( $id );
365 - return rest_ensure_response( $this->cloud_api->download_snippet_from_cloud( $cloud_snippet, $in_codevault ) );
388 + return $this->schema;
366 389 }
367 390 }