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 / Snippets / Snippets_REST_Controller.php

Snippets_REST_Controller.php in Code Snippets 3.10.1, at php/REST_API/Snippets/Snippets_REST_Controller.php

938 lines 27.9 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\Snippets;
4
5 use Code_Snippets\Migration\Export\Export;
6 use Code_Snippets\Migration\Export\Export_Code;
7 use Code_Snippets\Migration\Export\Export_JSON;
8 use Code_Snippets\Model\Snippet;
9 use Code_Snippets\REST_API\REST_Collection_Controller;
10 use WP_Error;
11 use WP_REST_Request;
12 use WP_REST_Response;
13 use WP_REST_Server;
14 use function Code_Snippets\activate_snippet;
15 use function Code_Snippets\clean_active_snippets_cache;
16 use function Code_Snippets\code_snippets;
17 use function Code_Snippets\deactivate_snippet;
18 use function Code_Snippets\delete_snippet;
19 use function Code_Snippets\get_snippet;
20 use function Code_Snippets\get_snippets;
21 use function Code_Snippets\restore_snippet;
22 use function Code_Snippets\save_snippet;
23 use function Code_Snippets\trash_snippet;
24
25 /**
26 * Allows fetching snippet data through the WordPress REST API.
27 *
28 * @since 3.4.0
29 * @package Code_Snippets
30 */
31 final class Snippets_REST_Controller extends REST_Collection_Controller {
32
33 /**
34 * Current API version.
35 */
36 public const VERSION = 1;
37
38 /**
39 * The base of this controller's route.
40 */
41 public const BASE_ROUTE = 'snippets';
42
43 /**
44 * Register REST routes.
45 */
46 public function register_routes() {
47 $route = '/' . self::BASE_ROUTE;
48 $id_route = $route . '/(?P<id>[\d]+)';
49
50 $network_args = array_intersect_key(
51 $this->get_endpoint_args_for_item_schema(),
52 [ 'network' ]
53 );
54
55 // Allow standard collection parameters (page, per_page, etc.) on the collection route.
56 $collection_args = array_merge( $network_args, $this->get_collection_params() );
57
58 $collection_args['status'] = [
59 'description' => esc_html__( 'Filter snippets by activation status.', 'code-snippets' ),
60 'type' => 'string',
61 'enum' => [ 'all', 'active', 'inactive' ],
62 'default' => 'all',
63 'sanitize_callback' => 'sanitize_key',
64 ];
65
66 $collection_args['exclude_types'] = [
67 'description' => esc_html__( 'List of snippet types to exclude from the response.', 'code-snippets' ),
68 'type' => 'array',
69 'items' => [
70 'type' => 'string',
71 ],
72 'default' => [],
73 'sanitize_callback' => static function ( $value ): array {
74 $values = is_array( $value ) ? $value : [ $value ];
75 return array_values( array_filter( array_map( 'sanitize_key', $values ) ) );
76 },
77 ];
78
79 $collection_args['orderby'] = [
80 'description' => esc_html__( 'Sort collection by object attribute.', 'code-snippets' ),
81 'type' => 'string',
82 'enum' => [ 'id', 'name', 'display_name' ],
83 'sanitize_callback' => 'sanitize_key',
84 ];
85
86 $collection_args['order'] = [
87 'description' => esc_html__( 'Sort direction.', 'code-snippets' ),
88 'type' => 'string',
89 'enum' => [ 'asc', 'desc' ],
90 'default' => 'asc',
91 'sanitize_callback' => 'sanitize_key',
92 ];
93
94 register_rest_route(
95 $this->namespace,
96 $route,
97 [
98 [
99 'methods' => WP_REST_Server::READABLE,
100 'callback' => [ $this, 'get_items' ],
101 'permission_callback' => [ $this, 'get_items_permissions_check' ],
102 'args' => $collection_args,
103 ],
104 [
105 'methods' => WP_REST_Server::CREATABLE,
106 'callback' => [ $this, 'create_item' ],
107 'permission_callback' => [ $this, 'create_item_permissions_check' ],
108 'args' => $this->get_endpoint_args_for_item_schema( true ),
109 ],
110 'schema' => [ $this, 'get_item_schema' ],
111 ]
112 );
113
114 register_rest_route(
115 $this->namespace,
116 $id_route,
117 [
118 [
119 'methods' => WP_REST_Server::READABLE,
120 'callback' => [ $this, 'get_item' ],
121 'permission_callback' => [ $this, 'get_item_permissions_check' ],
122 'args' => $network_args,
123 ],
124 [
125 'methods' => WP_REST_Server::EDITABLE,
126 'callback' => [ $this, 'update_item' ],
127 'permission_callback' => [ $this, 'update_item_permissions_check' ],
128 'args' => $this->get_endpoint_args_for_item_schema( false ),
129 ],
130 [
131 'methods' => WP_REST_Server::DELETABLE,
132 'callback' => [ $this, 'delete_item' ],
133 'permission_callback' => [ $this, 'delete_item_permissions_check' ],
134 'args' => $network_args,
135 ],
136 'schema' => [ $this, 'get_item_schema' ],
137 ]
138 );
139
140 register_rest_route(
141 $this->namespace,
142 $id_route . '/restore',
143 [
144 'methods' => WP_REST_Server::EDITABLE,
145 'callback' => [ $this, 'restore_item' ],
146 'permission_callback' => [ $this, 'update_item_permissions_check' ],
147 'args' => $network_args,
148 ]
149 );
150
151 register_rest_route(
152 $this->namespace,
153 $route . '/schema',
154 [
155 'methods' => WP_REST_Server::READABLE,
156 'callback' => [ $this, 'get_public_item_schema' ],
157 'permission_callback' => '__return_true',
158 ]
159 );
160
161 register_rest_route(
162 $this->namespace,
163 $id_route . '/activate',
164 [
165 'methods' => WP_REST_Server::EDITABLE,
166 'callback' => [ $this, 'activate_item' ],
167 'permission_callback' => [ $this, 'toggle_item_permissions_check' ],
168 'schema' => [ $this, 'get_item_schema' ],
169 'args' => $network_args,
170 ]
171 );
172
173 register_rest_route(
174 $this->namespace,
175 $id_route . '/deactivate',
176 [
177 'methods' => WP_REST_Server::EDITABLE,
178 'callback' => [ $this, 'deactivate_item' ],
179 'permission_callback' => [ $this, 'toggle_item_permissions_check' ],
180 'schema' => [ $this, 'get_item_schema' ],
181 'args' => $network_args,
182 ]
183 );
184
185 register_rest_route(
186 $this->namespace,
187 $id_route . '/export',
188 [
189 'methods' => WP_REST_Server::READABLE,
190 'callback' => [ $this, 'export_item' ],
191 'permission_callback' => [ $this, 'get_item_permissions_check' ],
192 'schema' => [ $this, 'get_item_schema' ],
193 'args' => $network_args,
194 ]
195 );
196
197 register_rest_route(
198 $this->namespace,
199 $id_route . '/export-code',
200 [
201 'methods' => WP_REST_Server::READABLE,
202 'callback' => [ $this, 'export_item_code' ],
203 'permission_callback' => [ $this, 'get_item_permissions_check' ],
204 'schema' => [ $this, 'get_item_schema' ],
205 'args' => $network_args,
206 ]
207 );
208 }
209
210 /**
211 * Determine whether the request has permission to access snippets.
212 *
213 * @param WP_REST_Request $request Incoming HTTP request.
214 *
215 * @return bool
216 */
217 public function permission_callback( WP_REST_Request $request ): bool {
218 return code_snippets()->current_user_can();
219 }
220
221 /**
222 * Determine whether a request targets network-scoped snippets.
223 *
224 * Only the literal boolean `true` (or its common string/integer equivalents)
225 * is treated as a network-scoped request. A missing or null `network` param
226 * means "site-scoped", and must not be escalated to the network capability.
227 *
228 * @param WP_REST_Request|WP_Error $request Full data about the request.
229 *
230 * @return bool
231 */
232 private function is_network_scoped_request( $request ): bool {
233 if ( ! is_multisite() || ! $request instanceof WP_REST_Request || ! $request->has_param( 'network' ) ) {
234 return false;
235 }
236
237 $network = $request->get_param( 'network' );
238
239 if ( is_bool( $network ) ) {
240 return $network;
241 }
242
243 if ( is_string( $network ) ) {
244 return ! in_array( strtolower( $network ), [ '0', 'false', 'no', '' ], true );
245 }
246
247 return (bool) $network;
248 }
249
250 /**
251 * Verify the current user has permission for the scope implied by the request.
252 *
253 * @param WP_REST_Request $request Full data about the request.
254 *
255 * @return bool
256 */
257 private function check_request_capability( WP_REST_Request $request ): bool {
258 return $this->is_network_scoped_request( $request )
259 ? code_snippets()->user_can_manage_network_snippets()
260 : code_snippets()->current_user_can();
261 }
262
263 /**
264 * Determine whether the request targets a shared network snippet.
265 *
266 * Shared network snippets are stored network-wide but each site decides whether
267 * to activate them via the per-site `active_shared_network_snippets` option. The
268 * `id` route parameter is used to look up the snippet so the result reflects the
269 * actual stored row rather than a value supplied in the request payload.
270 *
271 * @param WP_REST_Request|WP_Error $request Full data about the request.
272 *
273 * @return bool
274 */
275 private function is_shared_network_snippet_request( $request ): bool {
276 if ( ! is_multisite() || ! $request instanceof WP_REST_Request ) {
277 return false;
278 }
279
280 $snippet_id = absint( $request->get_param( 'id' ) );
281
282 if ( ! $snippet_id ) {
283 return false;
284 }
285
286 $snippet = get_snippet( $snippet_id, true );
287 return $snippet->id && $snippet->shared_network;
288 }
289
290 /**
291 * Check if a given request has access to get items.
292 *
293 * @param WP_REST_Request $request Full data about the request.
294 *
295 * @return bool
296 */
297 public function get_items_permissions_check( $request ): bool {
298 return $this->check_request_capability( $request );
299 }
300
301 /**
302 * Check if a given request has access to get a specific item.
303 *
304 * Shared network snippets are readable by any user who can manage snippets on
305 * the current site, since the snippet is intentionally exposed to subsites.
306 *
307 * @param WP_REST_Request $request Full data about the request.
308 *
309 * @return bool
310 */
311 public function get_item_permissions_check( $request ): bool {
312 if ( $this->is_shared_network_snippet_request( $request ) ) {
313 return code_snippets()->current_user_can();
314 }
315
316 return $this->check_request_capability( $request );
317 }
318
319 /**
320 * Check if a given request has access to create items.
321 *
322 * @param WP_REST_Request $request Full data about the request.
323 *
324 * @return bool
325 */
326 public function create_item_permissions_check( $request ): bool {
327 return $this->check_request_capability( $request );
328 }
329
330 /**
331 * Check if a given request has access to update a specific item.
332 *
333 * @param WP_REST_Request $request Full data about the request.
334 *
335 * @return bool
336 */
337 public function update_item_permissions_check( $request ): bool {
338 return $this->check_request_capability( $request );
339 }
340
341 /**
342 * Check if a given request has access to delete a specific item.
343 *
344 * @param WP_REST_Request $request Full data about the request.
345 *
346 * @return bool
347 */
348 public function delete_item_permissions_check( $request ): bool {
349 return $this->check_request_capability( $request );
350 }
351
352 /**
353 * Check if a given request has access to toggle a snippet's activation.
354 *
355 * For shared network snippets the activation toggle only writes to the
356 * per-site `active_shared_network_snippets` option, so the site capability
357 * is sufficient. For all other snippets we keep the strict capability check
358 * that prevents a subsite admin from forging `network=true` to operate on
359 * exclusive network-scoped snippets.
360 *
361 * @param WP_REST_Request $request Full data about the request.
362 *
363 * @return bool
364 */
365 public function toggle_item_permissions_check( WP_REST_Request $request ): bool {
366 if ( $this->is_shared_network_snippet_request( $request ) ) {
367 return code_snippets()->current_user_can();
368 }
369
370 return $this->check_request_capability( $request );
371 }
372
373 /**
374 * Retrieves a collection of snippets, with pagination.
375 *
376 * @param WP_REST_Request $request Full details about the request.
377 *
378 * @return WP_REST_Response Response object on success.
379 */
380 public function get_items( $request ): WP_REST_Response {
381 $network = $request->get_param( 'network' );
382 $all_snippets = $this->get_network_items( get_snippets( [], $network ), $network );
383
384 $status = sanitize_key( (string) $request->get_param( 'status' ) );
385
386 $exclude_types = $request->get_param( 'exclude_types' );
387 $exclude_types = is_array( $exclude_types ) ? array_map( 'sanitize_key', $exclude_types ) : [];
388
389 if ( $exclude_types || 'all' !== $status ) {
390 $all_snippets = array_filter(
391 $all_snippets,
392 static function ( Snippet $snippet ) use ( $exclude_types, $status ): bool {
393 if ( $exclude_types && in_array( $snippet->type, $exclude_types, true ) ) {
394 return false;
395 }
396
397 if ( 'active' === $status ) {
398 return ! $snippet->trashed && $snippet->active;
399 }
400
401 if ( 'inactive' === $status ) {
402 return ! $snippet->trashed && ! $snippet->active;
403 }
404
405 return true;
406 }
407 );
408 }
409
410 $orderby = sanitize_key( (string) $request->get_param( 'orderby' ) );
411 $order = sanitize_key( (string) $request->get_param( 'order' ) );
412
413 if ( $orderby ) {
414 $direction = 'desc' === $order ? -1 : 1;
415
416 usort(
417 $all_snippets,
418 static function ( Snippet $a, Snippet $b ) use ( $orderby, $direction ): int {
419 switch ( $orderby ) {
420 case 'display_name':
421 $cmp = strcasecmp( $a->display_name, $b->display_name );
422 break;
423 case 'name':
424 $cmp = strcasecmp( $a->name, $b->name );
425 break;
426 case 'id':
427 default:
428 $cmp = $a->id <=> $b->id;
429 break;
430 }
431
432 return 0 === $cmp ? ( $a->id <=> $b->id ) * $direction : $cmp * $direction;
433 }
434 );
435 }
436
437 $total_items = count( $all_snippets );
438 $query_params = $request->get_query_params();
439
440 if ( isset( $query_params['per_page'] ) || isset( $query_params['page'] ) ) {
441 $collection_params = $this->get_collection_params();
442 $per_page = isset( $query_params['per_page'] )
443 ? max( 1, (int) $query_params['per_page'] )
444 : (int) $collection_params['per_page']['default'];
445
446 $page_request = (int) $request->get_param( 'page' );
447 $page = max( 1, $page_request ? $page_request : (int) $collection_params['page']['default'] );
448 $total_pages = (int) ceil( $total_items / $per_page );
449
450 $offset = ( $page - 1 ) * $per_page;
451 $snippets = array_slice( $all_snippets, $offset, $per_page );
452 } else {
453 $snippets = $all_snippets;
454 $total_pages = 1;
455 }
456
457 $response = rest_ensure_response(
458 array_map(
459 function ( $snippet ) use ( $request ) {
460 $response_item = $this->prepare_item_for_response( $snippet, $request );
461 return $this->prepare_response_for_collection( $response_item );
462 },
463 $snippets
464 )
465 );
466
467 $response->header( 'X-WP-Total', (string) $total_items );
468 $response->header( 'X-WP-TotalPages', (string) $total_pages );
469
470 return $response;
471 }
472
473 /**
474 * Retrieve and merge shared network snippets.
475 *
476 * @param Snippet[] $all_snippets List of snippets to merge with.
477 * @param bool|null $network Whether fetching network snippets.
478 *
479 * @return Snippet[] Modified list of snippets.
480 */
481 private function get_network_items( array $all_snippets, ?bool $network ): array {
482 if ( ! is_multisite() || $network ) {
483 return $all_snippets;
484 }
485
486 $shared_ids = get_site_option( 'shared_network_snippets' );
487
488 if ( ! $shared_ids || ! is_array( $shared_ids ) ) {
489 return $all_snippets;
490 }
491
492 $active_shared_snippets = get_option( 'active_shared_network_snippets', [] );
493 $shared_snippets = get_snippets( $shared_ids, true );
494
495 foreach ( $shared_snippets as $snippet ) {
496 $snippet->shared_network = true;
497 $snippet->active = in_array( $snippet->id, $active_shared_snippets, true );
498 }
499
500 return array_merge( $all_snippets, $shared_snippets );
501 }
502
503 /**
504 * Retrieves one item from the collection.
505 *
506 * @param WP_REST_Request $request Full details about the request.
507 *
508 * @return WP_REST_Response|WP_Error Response object on success.
509 */
510 public function get_item( $request ) {
511 $snippet_id = $request->get_param( 'id' );
512 $item = get_snippet( $snippet_id, $request->get_param( 'network' ) );
513
514 if ( ! $item->id && 0 !== $snippet_id && '0' !== $snippet_id ) {
515 return new WP_Error(
516 'rest_cannot_get',
517 __( 'The snippet could not be found.', 'code-snippets' ),
518 [ 'status' => 404 ]
519 );
520 }
521
522 $data = $this->prepare_item_for_response( $item, $request );
523 return rest_ensure_response( $data );
524 }
525
526 /**
527 * Create one item from the collection
528 *
529 * @param WP_REST_Request|array $request Full data about the request.
530 *
531 * @return WP_REST_Response|WP_Error
532 */
533 public function create_item( $request ) {
534 $snippet = $this->prepare_item_for_database( $request );
535 $result = save_snippet( $snippet );
536
537 return $result
538 ? $this->prepare_item_for_response( $result, $request )
539 : new WP_Error(
540 'rest_cannot_create',
541 __( 'The snippet could not be created.', 'code-snippets' ),
542 [ 'status' => 500 ]
543 );
544 }
545
546 /**
547 * Update one item from the collection
548 *
549 * @param WP_REST_Request $request Full data about the request.
550 *
551 * @return WP_Error|WP_REST_Response
552 */
553 public function update_item( $request ) {
554 $snippet_id = absint( $request->get_param( 'id' ) );
555 $snippet = $snippet_id ? get_snippet( $snippet_id, $request->get_param( 'network' ) ) : null;
556
557 if ( ! $snippet_id || ! $snippet || ! $snippet->id ) {
558 return new WP_Error(
559 'rest_cannot_update',
560 __( 'Cannot update a snippet without a valid ID.', 'code-snippets' ),
561 [ 'status' => 400 ]
562 );
563 }
564
565 $item = $this->prepare_item_for_database( $request, $snippet );
566 $result = save_snippet( $item );
567
568 return $result
569 ? rest_ensure_response( $this->prepare_item_for_response( $result, $request ) )
570 : new WP_Error(
571 'rest_cannot_update',
572 __( 'The snippet could not be updated.', 'code-snippets' ),
573 [ 'status' => 500 ]
574 );
575 }
576
577 /**
578 * Delete one item from the collection, or trash it if not already trashed.
579 *
580 * @param WP_REST_Request $request Full data about the request.
581 *
582 * @return WP_Error|WP_REST_Response
583 */
584 public function delete_item( $request ) {
585 $item = $this->prepare_item_for_database( $request );
586 $snippet = get_snippet( $item->id, $item->network );
587
588 if ( ! $snippet || ! $snippet->id ) {
589 return new WP_Error(
590 'rest_cannot_delete',
591 __( 'The snippet could not be found.', 'code-snippets' ),
592 [ 'status' => 404 ]
593 );
594 }
595
596 if ( $snippet->trashed ) {
597 return delete_snippet( $snippet->id, $snippet->network )
598 ? new WP_REST_Response( null, 204 )
599 : new WP_Error(
600 'rest_cannot_delete',
601 __( 'The snippet could not be deleted.', 'code-snippets' ),
602 [ 'status' => 500 ]
603 );
604 } else {
605 return trash_snippet( $snippet->id, $snippet->network )
606 ? $this->get_item( $request )
607 : new WP_Error(
608 'rest_cannot_trash',
609 __( 'The snippet could not be trashed.', 'code-snippets' ),
610 [ 'status' => 500 ]
611 );
612 }
613 }
614
615 /**
616 * Restore a deleted item from the trash.
617 *
618 * @param WP_REST_Request $request Snippet information.
619 *
620 * @return WP_Error|WP_REST_Response
621 */
622 public function restore_item( WP_REST_Request $request ) {
623 $item = $this->prepare_item_for_database( $request );
624
625 return restore_snippet( $item->id, $item->network )
626 ? new WP_REST_Response( null, 204 )
627 : new WP_Error(
628 'rest_cannot_restore',
629 __( 'The snippet could not be restored.', 'code-snippets' ),
630 [ 'status' => 500 ]
631 );
632 }
633
634 /**
635 * Fetch snippet using data from request.
636 *
637 * @param WP_REST_Request $request Request containing 'id' and 'network' parameters.
638 *
639 * @return Snippet|WP_Error
640 */
641 private function get_requested_snippet( WP_REST_Request $request ) {
642 $id = $request->get_param( 'id' );
643
644 $snippet = $id && is_numeric( $id )
645 ? get_snippet( $id, $request->get_param( 'network' ) )
646 : null;
647
648 if ( ! $snippet || ! $snippet->id ) {
649 return new WP_Error(
650 'rest_cannot_activate',
651 __( 'The snippet could not be found.', 'code-snippets' ),
652 [ 'status' => 404 ]
653 );
654 }
655
656 return $snippet;
657 }
658
659 /**
660 * Activate one item in the collection.
661 *
662 * @param WP_REST_Request $request Full data about the request.
663 *
664 * @return WP_Error|WP_REST_Response
665 */
666 public function activate_item( WP_REST_Request $request ) {
667 $snippet = $this->get_requested_snippet( $request );
668
669 if ( is_wp_error( $snippet ) ) {
670 return rest_ensure_response( $snippet );
671 }
672
673 if ( $snippet->shared_network ) {
674 $this->set_shared_network_active( $snippet->id, true );
675 $snippet->active = true;
676 return rest_ensure_response( $snippet );
677 }
678
679 $result = activate_snippet( $snippet->id, $snippet->network );
680
681 return $result instanceof Snippet
682 ? rest_ensure_response( $result )
683 : new WP_Error(
684 'rest_cannot_activate',
685 $result,
686 [ 'status' => 500 ]
687 );
688 }
689
690 /**
691 * Deactivate one item in the collection.
692 *
693 * @param WP_REST_Request $request Full data about the request.
694 *
695 * @return WP_Error|WP_REST_Response
696 */
697 public function deactivate_item( WP_REST_Request $request ) {
698 $snippet = $this->get_requested_snippet( $request );
699
700 if ( is_wp_error( $snippet ) ) {
701 return rest_ensure_response( $snippet );
702 }
703
704 if ( $snippet->shared_network ) {
705 $this->set_shared_network_active( $snippet->id, false );
706 $snippet->active = false;
707 return rest_ensure_response( $snippet );
708 }
709
710 $result = deactivate_snippet( $snippet->id, $snippet->network );
711
712 return $result instanceof Snippet
713 ? rest_ensure_response( $result )
714 : new WP_Error(
715 'rest_cannot_activate',
716 __( 'The snippet could not be deactivated.', 'code-snippets' ),
717 [ 'status' => 500 ]
718 );
719 }
720
721 /**
722 * Toggle a shared network snippet's active state for the current site only.
723 *
724 * @param int $snippet_id Snippet identifier.
725 * @param bool $active Whether the snippet should be active on the current site.
726 *
727 * @return void
728 */
729 private function set_shared_network_active( int $snippet_id, bool $active ): void {
730 $active_shared_snippets = get_option( 'active_shared_network_snippets', [] );
731
732 if ( ! is_array( $active_shared_snippets ) ) {
733 $active_shared_snippets = [];
734 }
735
736 $already_active = in_array( $snippet_id, $active_shared_snippets, true );
737
738 if ( $active === $already_active ) {
739 return;
740 }
741
742 $active_shared_snippets = $active
743 ? array_merge( $active_shared_snippets, [ $snippet_id ] )
744 : array_values( array_diff( $active_shared_snippets, [ $snippet_id ] ) );
745
746 update_option( 'active_shared_network_snippets', $active_shared_snippets );
747 clean_active_snippets_cache( code_snippets()->db->ms_table );
748 }
749
750 /**
751 * Prepare an instance of the Export class from a request.
752 *
753 * @param Export $export Instance of Export class to use for generating response.
754 *
755 * @return WP_REST_Response
756 */
757 protected function build_export_response( Export $export ): WP_REST_Response {
758 $response = rest_ensure_response( $export->generate_export() );
759 $response->header( 'X-Suggested-Filename', $export->build_filename() );
760 return $response;
761 }
762
763 /**
764 * Retrieve one item in the collection in JSON export format.
765 *
766 * @param WP_REST_Request $request Full data about the request.
767 *
768 * @return WP_REST_Response
769 */
770 public function export_item( WP_REST_Request $request ): WP_REST_Response {
771 $item = $this->prepare_item_for_database( $request );
772 $export = new Export_JSON( [ $item->id ], $item->network );
773
774 return $this->build_export_response( $export );
775 }
776
777 /**
778 * Retrieve one item in the collection in the code export format.
779 *
780 * @param WP_REST_Request $request Full data about the request.
781 *
782 * @return WP_REST_Response
783 */
784 public function export_item_code( WP_REST_Request $request ): WP_REST_Response {
785 $item = $this->prepare_item_for_database( $request );
786 $export = new Export_Code( [ $item->id ], $item->network );
787
788 return $this->build_export_response( $export );
789 }
790
791 /**
792 * Prepares one item for create or update operation.
793 *
794 * @param WP_REST_Request $request Request object.
795 * @param Snippet|null $item Existing item to augment.
796 *
797 * @return Snippet The prepared item.
798 */
799 protected function prepare_item_for_database( $request, ?Snippet $item = null ): Snippet {
800 if ( ! $item instanceof Snippet ) {
801 $item = new Snippet();
802 }
803
804 foreach ( $item->get_allowed_fields() as $field ) {
805 if ( $request->has_param( $field ) ) {
806 $item->set_field( $field, $request->get_param( $field ) );
807 }
808 }
809
810 return $item;
811 }
812
813 /**
814 * Prepare the item for the REST response.
815 *
816 * @param Snippet $item Snippet object.
817 * @param WP_REST_Request $request Request object.
818 *
819 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
820 */
821 public function prepare_item_for_response( $item, $request ) {
822 $schema = $this->get_item_schema();
823 $response = [];
824
825 foreach ( array_keys( $schema['properties'] ) as $property ) {
826 $response[ $property ] = $item->$property;
827 }
828
829 // The schema declares this as a date-time, so send one: the stored value
830 // is UTC without an offset, which clients read as local time.
831 $response['modified'] = $item->modified_iso;
832
833 return rest_ensure_response( $response );
834 }
835
836 /**
837 * Get our sample schema for a post.
838 *
839 * @return array<string, mixed> The sample schema for a post
840 */
841 public function get_item_schema(): array {
842 if ( $this->schema ) {
843 return $this->schema;
844 }
845
846 $this->schema = [
847 '$schema' => 'http://json-schema.org/draft-04/schema#',
848 'title' => 'snippet',
849 'type' => 'object',
850 'properties' => [
851 'id' => [
852 'description' => esc_html__( 'Unique identifier for the snippet.', 'code-snippets' ),
853 'type' => 'integer',
854 'readonly' => true,
855 ],
856 'name' => [
857 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ),
858 'type' => 'string',
859 ],
860 'desc' => [
861 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
862 'type' => 'string',
863 ],
864 'code' => [
865 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
866 'type' => 'string',
867 ],
868 'tags' => [
869 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ),
870 'type' => 'array',
871 'items' => [
872 'type' => 'string',
873 ],
874 ],
875 'scope' => [
876 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
877 'type' => 'string',
878 ],
879 'condition_id' => [
880 'description' => esc_html__( 'Identifier of condition linked to this snippet.', 'code-snippets' ),
881 'type' => 'integer',
882 ],
883 'active' => [
884 'description' => esc_html__( 'Snippet activation status.', 'code-snippets' ),
885 'type' => 'boolean',
886 ],
887 'trashed' => [
888 'description' => esc_html__( 'Whether the snippet is marked as deleted.', 'code-snippets' ),
889 'type' => 'boolean',
890 ],
891 'locked' => [
892 'description' => esc_html__( 'Whether the snippet is locked from modification or deletion.', 'code-snippets' ),
893 'type' => 'boolean',
894 ],
895 'priority' => [
896 'description' => esc_html__( 'Relative priority in which the snippet is executed.', 'code-snippets' ),
897 'type' => 'integer',
898 ],
899 'network' => [
900 'description' => esc_html__( 'Whether the snippet is network-wide instead of site-wide.', 'code-snippets' ),
901 'type' => [ 'boolean', 'null' ],
902 'default' => null,
903 ],
904 'shared_network' => [
905 'description' => esc_html__( 'If a network snippet, whether can be activated on discrete sites instead of network-wide.', 'code-snippets' ),
906 'type' => [ 'boolean', 'null' ],
907 ],
908 'modified' => [
909 'description' => esc_html__( 'Date and time when the snippet was last modified, in ISO format.', 'code-snippets' ),
910 'type' => 'string',
911 'format' => 'date-time',
912 'readonly' => true,
913 ],
914 'last_active' => [
915 'description' => esc_html__( 'Timestamp of when the snippet was last active, if available.', 'code-snippets' ),
916 'type' => 'integer',
917 'readonly' => true,
918 ],
919 'code_error' => [
920 'description' => esc_html__( 'Error message if the snippet code could not be parsed.', 'code-snippets' ),
921 'type' => [ 'array', 'null' ],
922 'items' => [
923 'type' => [ 'string', 'integer' ],
924 ],
925 'readonly' => true,
926 ],
927 'code_error_trace' => [
928 'description' => esc_html__( 'Stack trace for the most recent snippet code error.', 'code-snippets' ),
929 'type' => [ 'string', 'null' ],
930 'readonly' => true,
931 ],
932 ],
933 ];
934
935 return $this->schema;
936 }
937 }
938