PluginProbe
Code Snippets / 3.8.1
Code Snippets v3.8.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 / class-snippets-rest-controller.php

class-snippets-rest-controller.php in Code Snippets 3.8.1, at php/rest-api/class-snippets-rest-controller.php

585 lines 16.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;
4
5 use Code_Snippets\Export;
6 use Code_Snippets\Snippet;
7 use WP_Error;
8 use WP_REST_Controller;
9 use WP_REST_Request;
10 use WP_REST_Response;
11 use WP_REST_Server;
12 use function Code_Snippets\activate_snippet;
13 use function Code_Snippets\code_snippets;
14 use function Code_Snippets\deactivate_snippet;
15 use function Code_Snippets\delete_snippet;
16 use function Code_Snippets\get_snippet;
17 use function Code_Snippets\get_snippets;
18 use function Code_Snippets\save_snippet;
19 use const Code_Snippets\REST_API_NAMESPACE;
20
21 /**
22 * Allows fetching snippet data through the WordPress REST API.
23 *
24 * @since 3.4.0
25 * @package Code_Snippets
26 */
27 final class Snippets_REST_Controller extends WP_REST_Controller {
28
29 /**
30 * Current API version.
31 */
32 public const VERSION = 1;
33
34 /**
35 * The base of this controller's route.
36 */
37 public const BASE_ROUTE = 'snippets';
38
39 /**
40 * The namespace of this controller's route.
41 *
42 * @var string
43 */
44 protected $namespace = REST_API_NAMESPACE . self::VERSION;
45
46 /**
47 * The base of this controller's route.
48 *
49 * @var string
50 */
51 protected $rest_base = self::BASE_ROUTE;
52
53 /**
54 * Retrieve this controller's REST API base path, including namespace.
55 *
56 * @return string
57 */
58 public static function get_base_route(): string {
59 return REST_API_NAMESPACE . self::VERSION . '/' . self::BASE_ROUTE;
60 }
61
62 /**
63 * Retrieve the full base route including the REST API prefix.
64 *
65 * @return string
66 */
67 public static function get_prefixed_base_route(): string {
68 return '/' . rtrim( rest_get_url_prefix(), '/\\' ) . '/' . self::get_base_route();
69 }
70
71 /**
72 * Register REST routes.
73 */
74 public function register_routes() {
75 $route = '/' . $this->rest_base;
76 $id_route = $route . '/(?P<id>[\d]+)';
77
78 $network_args = array_intersect_key(
79 $this->get_endpoint_args_for_item_schema(),
80 [ 'network' ]
81 );
82
83 // Allow standard collection parameters (page, per_page, etc.) on the collection route.
84 $collection_args = array_merge( $network_args, $this->get_collection_params() );
85
86 register_rest_route(
87 $this->namespace,
88 $route,
89 [
90 [
91 'methods' => WP_REST_Server::READABLE,
92 'callback' => [ $this, 'get_items' ],
93 'permission_callback' => [ $this, 'get_items_permissions_check' ],
94 'args' => $collection_args,
95 ],
96 [
97 'methods' => WP_REST_Server::CREATABLE,
98 'callback' => [ $this, 'create_item' ],
99 'permission_callback' => [ $this, 'create_item_permissions_check' ],
100 'args' => $this->get_endpoint_args_for_item_schema( true ),
101 ],
102 'schema' => [ $this, 'get_item_schema' ],
103 ]
104 );
105
106 register_rest_route(
107 $this->namespace,
108 $id_route,
109 [
110 [
111 'methods' => WP_REST_Server::READABLE,
112 'callback' => [ $this, 'get_item' ],
113 'permission_callback' => [ $this, 'get_item_permissions_check' ],
114 'args' => $network_args,
115 ],
116 [
117 'methods' => WP_REST_Server::EDITABLE,
118 'callback' => [ $this, 'update_item' ],
119 'permission_callback' => [ $this, 'update_item_permissions_check' ],
120 'args' => $this->get_endpoint_args_for_item_schema( false ),
121 ],
122 [
123 'methods' => WP_REST_Server::DELETABLE,
124 'callback' => [ $this, 'delete_item' ],
125 'permission_callback' => [ $this, 'delete_item_permissions_check' ],
126 'args' => $network_args,
127 ],
128 'schema' => [ $this, 'get_item_schema' ],
129 ]
130 );
131
132 register_rest_route(
133 $this->namespace,
134 $route . '/schema',
135 [
136 'methods' => WP_REST_Server::READABLE,
137 'callback' => [ $this, 'get_public_item_schema' ],
138 'permission_callback' => '__return_true',
139 ]
140 );
141
142 register_rest_route(
143 $this->namespace,
144 $id_route . '/activate',
145 [
146 'methods' => WP_REST_Server::EDITABLE,
147 'callback' => [ $this, 'activate_item' ],
148 'permission_callback' => [ $this, 'update_item_permissions_check' ],
149 'schema' => [ $this, 'get_item_schema' ],
150 'args' => $network_args,
151 ]
152 );
153
154 register_rest_route(
155 $this->namespace,
156 $id_route . '/deactivate',
157 [
158 'methods' => WP_REST_Server::EDITABLE,
159 'callback' => [ $this, 'deactivate_item' ],
160 'permission_callback' => [ $this, 'update_item_permissions_check' ],
161 'schema' => [ $this, 'get_item_schema' ],
162 'args' => $network_args,
163 ]
164 );
165
166 register_rest_route(
167 $this->namespace,
168 $id_route . '/export',
169 [
170 'methods' => WP_REST_Server::READABLE,
171 'callback' => [ $this, 'export_item' ],
172 'permission_callback' => [ $this, 'get_item_permissions_check' ],
173 'schema' => [ $this, 'get_item_schema' ],
174 'args' => $network_args,
175 ]
176 );
177
178 register_rest_route(
179 $this->namespace,
180 $id_route . '/export-code',
181 [
182 'methods' => WP_REST_Server::READABLE,
183 'callback' => [ $this, 'export_item_code' ],
184 'permission_callback' => [ $this, 'get_item_permissions_check' ],
185 'schema' => [ $this, 'get_item_schema' ],
186 'args' => $network_args,
187 ]
188 );
189 }
190
191 /**
192 * Retrieves a collection of snippets, with pagination.
193 *
194 * @param WP_REST_Request $request Full details about the request.
195 *
196 * @return WP_REST_Response Response object on success.
197 */
198 public function get_items( $request ): WP_REST_Response {
199 $network = $request->get_param( 'network' );
200 $all_snippets = get_snippets( [], $network );
201
202 // Get collection params (page, per_page).
203 $collection_params = $this->get_collection_params();
204 $per_page_request = (int) $request->get_param( 'per_page' );
205 $per_page = max( 1, $per_page_request ? $per_page_request : (int) $collection_params['per_page']['default'] );
206
207 $page_request = (int) $request->get_param( 'page' );
208 $page = max( 1, $page_request ? $page_request : (int) $collection_params['page']['default'] );
209
210 // Count total items
211 $total_items = count( $all_snippets );
212 $total_pages = (int) ceil( $total_items / $per_page );
213
214 // Slice the full list to the requested page.
215 $offset = ( $page - 1 ) * $per_page;
216 $snippets = array_slice( $all_snippets, $offset, $per_page );
217
218 $snippets_data = [];
219
220 foreach ( $snippets as $snippet ) {
221 $snippet_data = $this->prepare_item_for_response( $snippet, $request );
222 $snippets_data[] = $this->prepare_response_for_collection( $snippet_data );
223 }
224
225 $response = rest_ensure_response( $snippets_data );
226 $response->header( 'X-WP-Total', (string) $total_items );
227 $response->header( 'X-WP-TotalPages', (string) $total_pages );
228
229 return $response;
230 }
231
232 /**
233 * Retrieves one item from the collection.
234 *
235 * @param WP_REST_Request $request Full details about the request.
236 *
237 * @return WP_REST_Response|WP_Error Response object on success.
238 */
239 public function get_item( $request ) {
240 $snippet_id = $request->get_param( 'id' );
241 $item = get_snippet( $snippet_id, $request->get_param( 'network' ) );
242
243 if ( ! $item->id && 0 !== $snippet_id && '0' !== $snippet_id ) {
244 return new WP_Error(
245 'rest_cannot_get',
246 __( 'The snippet could not be found.', 'code-snippets' ),
247 [ 'status' => 500 ]
248 );
249 }
250
251 $data = $this->prepare_item_for_response( $item, $request );
252 return rest_ensure_response( $data );
253 }
254
255 /**
256 * Create one item from the collection
257 *
258 * @param WP_REST_Request|array $request Full data about the request.
259 *
260 * @return WP_REST_Response|WP_Error
261 */
262 public function create_item( $request ) {
263 $snippet = $this->prepare_item_for_database( $request );
264 $result = $snippet ? save_snippet( $snippet ) : null;
265
266 return $result ?
267 $this->prepare_item_for_response( $result, $request ) :
268 new WP_Error(
269 'rest_cannot_create',
270 __( 'The snippet could not be created.', 'code-snippets' ),
271 [ 'status' => 500 ]
272 );
273 }
274
275 /**
276 * Update one item from the collection
277 *
278 * @param WP_REST_Request $request Full data about the request.
279 *
280 * @return WP_Error|WP_REST_Response
281 */
282 public function update_item( $request ) {
283 $snippet_id = absint( $request->get_param( 'id' ) );
284 $snippet = $snippet_id ? get_snippet( $snippet_id, $request->get_param( 'network' ) ) : null;
285
286 if ( ! $snippet_id || ! $snippet || ! $snippet->id ) {
287 return new WP_Error(
288 'rest_cannot_update',
289 __( 'Cannot update a snippet without a valid ID.', 'code-snippets' ),
290 [ 'status' => 400 ]
291 );
292 }
293
294 $item = $this->prepare_item_for_database( $request, $snippet );
295 $result = save_snippet( $item );
296
297 if ( $result ) {
298 $request->set_param( 'id', $result->id );
299 return $this->get_item( $request );
300 }
301
302 return new WP_Error(
303 'rest_cannot_update',
304 __( 'The snippet could not be updated.', 'code-snippets' ),
305 [ 'status' => 500 ]
306 );
307 }
308
309 /**
310 * Delete one item from the collection
311 *
312 * @param WP_REST_Request $request Full data about the request.
313 *
314 * @return WP_Error|WP_REST_Response
315 */
316 public function delete_item( $request ) {
317 $item = $this->prepare_item_for_database( $request );
318 $result = delete_snippet( $item->id, $item->network );
319
320 return $result ?
321 new WP_REST_Response( null, 204 ) :
322 new WP_Error(
323 'rest_cannot_delete',
324 __( 'The snippet could not be deleted.', 'code-snippets' ),
325 [ 'status' => 500 ]
326 );
327 }
328
329 /**
330 * Activate one item in the collection.
331 *
332 * @param WP_REST_Request $request Full data about the request.
333 *
334 * @return WP_Error|WP_REST_Response
335 */
336 public function activate_item( WP_REST_Request $request ) {
337 $item = $this->prepare_item_for_database( $request );
338 $result = activate_snippet( $item->id, $item->network );
339
340 return $result instanceof Snippet ?
341 rest_ensure_response( $result ) :
342 new WP_Error(
343 'rest_cannot_activate',
344 $result,
345 [ 'status' => 500 ]
346 );
347 }
348
349 /**
350 * Deactivate one item in the collection.
351 *
352 * @param WP_REST_Request $request Full data about the request.
353 *
354 * @return WP_Error|WP_REST_Response
355 */
356 public function deactivate_item( WP_REST_Request $request ) {
357 $item = $this->prepare_item_for_database( $request );
358 $result = deactivate_snippet( $item->id, $item->network );
359
360 return $result instanceof Snippet ?
361 rest_ensure_response( $result ) :
362 new WP_Error(
363 'rest_cannot_activate',
364 __( 'The snippet could not be deactivated.', 'code-snippets' ),
365 [ 'status' => 500 ]
366 );
367 }
368
369 /**
370 * Prepare an instance of the Export class from a request.
371 *
372 * @param WP_REST_Request $request Full data about the request.
373 *
374 * @return Export
375 */
376 protected function build_export( WP_REST_Request $request ): Export {
377 $item = $this->prepare_item_for_database( $request );
378 return new Export( [ $item->id ], $item->network );
379 }
380
381 /**
382 * Retrieve one item in the collection in JSON export format.
383 *
384 * @param WP_REST_Request $request Full data about the request.
385 *
386 * @return WP_Error|WP_REST_Response
387 */
388 public function export_item( WP_REST_Request $request ) {
389 $export = $this->build_export( $request );
390 $result = $export->create_export_object();
391 return rest_ensure_response( $result );
392 }
393
394 /**
395 * Retrieve one item in the collection in the code export format.
396 *
397 * @param WP_REST_Request $request Full data about the request.
398 *
399 * @return WP_Error|WP_REST_Response
400 */
401 public function export_item_code( WP_REST_Request $request ) {
402 $export = $this->build_export( $request );
403 $result = $export->export_snippets_code();
404
405 return rest_ensure_response( $result );
406 }
407
408 /**
409 * Prepares one item for create or update operation.
410 *
411 * @param WP_REST_Request $request Request object.
412 * @param Snippet|null $item Existing item to augment.
413 *
414 * @return Snippet The prepared item.
415 */
416 protected function prepare_item_for_database( $request, ?Snippet $item = null ): ?Snippet {
417 if ( ! $item instanceof Snippet ) {
418 $item = new Snippet();
419 }
420
421 foreach ( $item->get_allowed_fields() as $field ) {
422 if ( isset( $request[ $field ] ) ) {
423 $item->set_field( $field, $request[ $field ] );
424 }
425 }
426
427 return $item;
428 }
429
430 /**
431 * Prepare the item for the REST response.
432 *
433 * @param Snippet $item Snippet object.
434 * @param WP_REST_Request $request Request object.
435 *
436 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
437 */
438 public function prepare_item_for_response( $item, $request ) {
439 $schema = $this->get_item_schema();
440 $response = [];
441
442 foreach ( array_keys( $schema['properties'] ) as $property ) {
443 $response[ $property ] = $item->$property;
444 }
445
446 return rest_ensure_response( $response );
447 }
448
449 /**
450 * Check if a given request has access to get items.
451 *
452 * @param WP_REST_Request $request Full data about the request.
453 *
454 * @return boolean
455 */
456 public function get_items_permissions_check( $request ): bool {
457 return code_snippets()->current_user_can();
458 }
459
460 /**
461 * Check if a given request has access to get a specific item.
462 *
463 * @param WP_REST_Request $request Full data about the request.
464 *
465 * @return boolean
466 */
467 public function get_item_permissions_check( $request ): bool {
468 return $this->get_items_permissions_check( $request );
469 }
470
471 /**
472 * Check if a given request has access to create items.
473 *
474 * @param WP_REST_Request $request Full data about the request.
475 *
476 * @return boolean
477 */
478 public function create_item_permissions_check( $request ): bool {
479 return code_snippets()->current_user_can();
480 }
481
482 /**
483 * Check if a given request has access to update a specific item.
484 *
485 * @param WP_REST_Request $request Full data about the request.
486 *
487 * @return boolean
488 */
489 public function update_item_permissions_check( $request ): bool {
490 return $this->create_item_permissions_check( $request );
491 }
492
493 /**
494 * Check if a given request has access to delete a specific item.
495 *
496 * @param WP_REST_Request $request Full data about the request.
497 *
498 * @return boolean
499 */
500 public function delete_item_permissions_check( $request ): bool {
501 return $this->create_item_permissions_check( $request );
502 }
503
504 /**
505 * Get our sample schema for a post.
506 *
507 * @return array<string, mixed> The sample schema for a post
508 */
509 public function get_item_schema(): array {
510 if ( $this->schema ) {
511 return $this->schema;
512 }
513
514 $this->schema = [
515 '$schema' => 'http://json-schema.org/draft-04/schema#',
516 'title' => 'snippet',
517 'type' => 'object',
518 'properties' => [
519 'id' => [
520 'description' => esc_html__( 'Unique identifier for the snippet.', 'code-snippets' ),
521 'type' => 'integer',
522 'readonly' => true,
523 ],
524 'name' => [
525 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ),
526 'type' => 'string',
527 ],
528 'desc' => [
529 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
530 'type' => 'string',
531 ],
532 'code' => [
533 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
534 'type' => 'string',
535 ],
536 'tags' => [
537 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ),
538 'type' => 'array',
539 'items' => [
540 'type' => 'string',
541 ],
542 ],
543 'scope' => [
544 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
545 'type' => 'string',
546 ],
547 'condition_id' => [
548 'description' => esc_html__( 'Identifier of condition linked to this snippet.', 'code-snippets' ),
549 'type' => 'integer',
550 ],
551 'active' => [
552 'description' => esc_html__( 'Snippet activation status.', 'code-snippets' ),
553 'type' => 'boolean',
554 ],
555 'priority' => [
556 'description' => esc_html__( 'Relative priority in which the snippet is executed.', 'code-snippets' ),
557 'type' => 'integer',
558 ],
559 'network' => [
560 'description' => esc_html__( 'Whether the snippet is network-wide instead of site-wide.', 'code-snippets' ),
561 'type' => [ 'boolean', 'null' ],
562 'default' => null,
563 ],
564 'shared_network' => [
565 'description' => esc_html__( 'If a network snippet, whether can be activated on discrete sites instead of network-wide.', 'code-snippets' ),
566 'type' => [ 'boolean', 'null' ],
567 ],
568 'modified' => [
569 'description' => esc_html__( 'Date and time when the snippet was last modified, in ISO format.', 'code-snippets' ),
570 'type' => 'string',
571 'format' => 'date-time',
572 'readonly' => true,
573 ],
574 'code_error' => [
575 'description' => esc_html__( 'Error message if the snippet code could not be parsed.', 'code-snippets' ),
576 'type' => 'string',
577 'readonly' => true,
578 ],
579 ],
580 ];
581
582 return $this->schema;
583 }
584 }
585