PluginProbe
Code Snippets / 3.9.1
Code Snippets v3.9.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.9.1, at php/rest-api/class-snippets-rest-controller.php

619 lines 17.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\trash_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 $all_snippets = $this->get_network_items( $all_snippets, $network );
202
203 $total_items = count( $all_snippets );
204 $query_params = $request->get_query_params();
205
206 if ( isset( $query_params['per_page'] ) || isset( $query_params['page'] ) ) {
207 $collection_params = $this->get_collection_params();
208 $per_page = isset( $query_params['per_page'] )
209 ? max( 1, (int) $query_params['per_page'] )
210 : (int) $collection_params['per_page']['default'];
211 $page_request = (int) $request->get_param( 'page' );
212 $page = max( 1, $page_request ? $page_request : (int) $collection_params['page']['default'] );
213 $total_pages = (int) ceil( $total_items / $per_page );
214
215 $offset = ( $page - 1 ) * $per_page;
216 $snippets = array_slice( $all_snippets, $offset, $per_page );
217 } else {
218 $snippets = $all_snippets;
219 $total_pages = 1;
220 }
221
222 $snippets_data = [];
223
224 foreach ( $snippets as $snippet ) {
225 $snippet_data = $this->prepare_item_for_response( $snippet, $request );
226 $snippets_data[] = $this->prepare_response_for_collection( $snippet_data );
227 }
228
229 $response = rest_ensure_response( $snippets_data );
230 $response->header( 'X-WP-Total', (string) $total_items );
231 $response->header( 'X-WP-TotalPages', (string) $total_pages );
232
233 return $response;
234 }
235
236 /**
237 * Retrieve and merge shared network snippets.
238 *
239 * @param array<Snippet> $all_snippets List of snippets to merge with.
240 * @param bool|null $network Whether fetching network snippets.
241 *
242 * @return array<Snippet> Modified list of snippets.
243 */
244 private function get_network_items( array $all_snippets, $network ): array {
245 if ( ! is_multisite() || $network ) {
246 return $all_snippets;
247 }
248
249 $shared_ids = get_site_option( 'shared_network_snippets' );
250
251 if ( ! $shared_ids || ! is_array( $shared_ids ) ) {
252 return $all_snippets;
253 }
254
255 $active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
256 $shared_snippets = get_snippets( $shared_ids, true );
257
258 foreach ( $shared_snippets as $snippet ) {
259 $snippet->shared_network = true;
260 $snippet->active = in_array( $snippet->id, $active_shared_snippets, true );
261 }
262
263 return array_merge( $all_snippets, $shared_snippets );
264 }
265
266 /**
267 * Retrieves one item from the collection.
268 *
269 * @param WP_REST_Request $request Full details about the request.
270 *
271 * @return WP_REST_Response|WP_Error Response object on success.
272 */
273 public function get_item( $request ) {
274 $snippet_id = $request->get_param( 'id' );
275 $item = get_snippet( $snippet_id, $request->get_param( 'network' ) );
276
277 if ( ! $item->id && 0 !== $snippet_id && '0' !== $snippet_id ) {
278 return new WP_Error(
279 'rest_cannot_get',
280 __( 'The snippet could not be found.', 'code-snippets' ),
281 [ 'status' => 500 ]
282 );
283 }
284
285 $data = $this->prepare_item_for_response( $item, $request );
286 return rest_ensure_response( $data );
287 }
288
289 /**
290 * Create one item from the collection
291 *
292 * @param WP_REST_Request|array $request Full data about the request.
293 *
294 * @return WP_REST_Response|WP_Error
295 */
296 public function create_item( $request ) {
297 $snippet = $this->prepare_item_for_database( $request );
298 $result = $snippet ? save_snippet( $snippet ) : null;
299
300 return $result ?
301 $this->prepare_item_for_response( $result, $request ) :
302 new WP_Error(
303 'rest_cannot_create',
304 __( 'The snippet could not be created.', 'code-snippets' ),
305 [ 'status' => 500 ]
306 );
307 }
308
309 /**
310 * Update 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 update_item( $request ) {
317 $snippet_id = absint( $request->get_param( 'id' ) );
318 $snippet = $snippet_id ? get_snippet( $snippet_id, $request->get_param( 'network' ) ) : null;
319
320 if ( ! $snippet_id || ! $snippet || ! $snippet->id ) {
321 return new WP_Error(
322 'rest_cannot_update',
323 __( 'Cannot update a snippet without a valid ID.', 'code-snippets' ),
324 [ 'status' => 400 ]
325 );
326 }
327
328 $item = $this->prepare_item_for_database( $request, $snippet );
329 $result = save_snippet( $item );
330
331 if ( $result ) {
332 $request->set_param( 'id', $result->id );
333 return $this->get_item( $request );
334 }
335
336 return new WP_Error(
337 'rest_cannot_update',
338 __( 'The snippet could not be updated.', 'code-snippets' ),
339 [ 'status' => 500 ]
340 );
341 }
342
343 /**
344 * Delete one item from the collection (trash)
345 *
346 * @param WP_REST_Request $request Full data about the request.
347 *
348 * @return WP_Error|WP_REST_Response
349 */
350 public function delete_item( $request ) {
351 $item = $this->prepare_item_for_database( $request );
352 $result = trash_snippet( $item->id, $item->network );
353
354 return $result ?
355 new WP_REST_Response( null, 204 ) :
356 new WP_Error(
357 'rest_cannot_delete',
358 __( 'The snippet could not be deleted.', 'code-snippets' ),
359 [ 'status' => 500 ]
360 );
361 }
362
363 /**
364 * Activate one item in the collection.
365 *
366 * @param WP_REST_Request $request Full data about the request.
367 *
368 * @return WP_Error|WP_REST_Response
369 */
370 public function activate_item( WP_REST_Request $request ) {
371 $item = $this->prepare_item_for_database( $request );
372 $result = activate_snippet( $item->id, $item->network );
373
374 return $result instanceof Snippet ?
375 rest_ensure_response( $result ) :
376 new WP_Error(
377 'rest_cannot_activate',
378 $result,
379 [ 'status' => 500 ]
380 );
381 }
382
383 /**
384 * Deactivate one item in the collection.
385 *
386 * @param WP_REST_Request $request Full data about the request.
387 *
388 * @return WP_Error|WP_REST_Response
389 */
390 public function deactivate_item( WP_REST_Request $request ) {
391 $item = $this->prepare_item_for_database( $request );
392 $result = deactivate_snippet( $item->id, $item->network );
393
394 return $result instanceof Snippet ?
395 rest_ensure_response( $result ) :
396 new WP_Error(
397 'rest_cannot_activate',
398 __( 'The snippet could not be deactivated.', 'code-snippets' ),
399 [ 'status' => 500 ]
400 );
401 }
402
403 /**
404 * Prepare an instance of the Export class from a request.
405 *
406 * @param WP_REST_Request $request Full data about the request.
407 *
408 * @return Export
409 */
410 protected function build_export( WP_REST_Request $request ): Export {
411 $item = $this->prepare_item_for_database( $request );
412 return new Export( [ $item->id ], $item->network );
413 }
414
415 /**
416 * Retrieve one item in the collection in JSON export format.
417 *
418 * @param WP_REST_Request $request Full data about the request.
419 *
420 * @return WP_Error|WP_REST_Response
421 */
422 public function export_item( WP_REST_Request $request ) {
423 $export = $this->build_export( $request );
424 $result = $export->create_export_object();
425 return rest_ensure_response( $result );
426 }
427
428 /**
429 * Retrieve one item in the collection in the code export format.
430 *
431 * @param WP_REST_Request $request Full data about the request.
432 *
433 * @return WP_Error|WP_REST_Response
434 */
435 public function export_item_code( WP_REST_Request $request ) {
436 $export = $this->build_export( $request );
437 $result = $export->export_snippets_code();
438
439 return rest_ensure_response( $result );
440 }
441
442 /**
443 * Prepares one item for create or update operation.
444 *
445 * @param WP_REST_Request $request Request object.
446 * @param Snippet|null $item Existing item to augment.
447 *
448 * @return Snippet The prepared item.
449 */
450 protected function prepare_item_for_database( $request, ?Snippet $item = null ): ?Snippet {
451 if ( ! $item instanceof Snippet ) {
452 $item = new Snippet();
453 }
454
455 foreach ( $item->get_allowed_fields() as $field ) {
456 if ( isset( $request[ $field ] ) ) {
457 $item->set_field( $field, $request[ $field ] );
458 }
459 }
460
461 return $item;
462 }
463
464 /**
465 * Prepare the item for the REST response.
466 *
467 * @param Snippet $item Snippet object.
468 * @param WP_REST_Request $request Request object.
469 *
470 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
471 */
472 public function prepare_item_for_response( $item, $request ) {
473 $schema = $this->get_item_schema();
474 $response = [];
475
476 foreach ( array_keys( $schema['properties'] ) as $property ) {
477 $response[ $property ] = $item->$property;
478 }
479
480 return rest_ensure_response( $response );
481 }
482
483 /**
484 * Check if a given request has access to get items.
485 *
486 * @param WP_REST_Request $request Full data about the request.
487 *
488 * @return boolean
489 */
490 public function get_items_permissions_check( $request ): bool {
491 return code_snippets()->current_user_can();
492 }
493
494 /**
495 * Check if a given request has access to get a specific item.
496 *
497 * @param WP_REST_Request $request Full data about the request.
498 *
499 * @return boolean
500 */
501 public function get_item_permissions_check( $request ): bool {
502 return $this->get_items_permissions_check( $request );
503 }
504
505 /**
506 * Check if a given request has access to create items.
507 *
508 * @param WP_REST_Request $request Full data about the request.
509 *
510 * @return boolean
511 */
512 public function create_item_permissions_check( $request ): bool {
513 return code_snippets()->current_user_can();
514 }
515
516 /**
517 * Check if a given request has access to update a specific item.
518 *
519 * @param WP_REST_Request $request Full data about the request.
520 *
521 * @return boolean
522 */
523 public function update_item_permissions_check( $request ): bool {
524 return $this->create_item_permissions_check( $request );
525 }
526
527 /**
528 * Check if a given request has access to delete a specific item.
529 *
530 * @param WP_REST_Request $request Full data about the request.
531 *
532 * @return boolean
533 */
534 public function delete_item_permissions_check( $request ): bool {
535 return $this->create_item_permissions_check( $request );
536 }
537
538 /**
539 * Get our sample schema for a post.
540 *
541 * @return array<string, mixed> The sample schema for a post
542 */
543 public function get_item_schema(): array {
544 if ( $this->schema ) {
545 return $this->schema;
546 }
547
548 $this->schema = [
549 '$schema' => 'http://json-schema.org/draft-04/schema#',
550 'title' => 'snippet',
551 'type' => 'object',
552 'properties' => [
553 'id' => [
554 'description' => esc_html__( 'Unique identifier for the snippet.', 'code-snippets' ),
555 'type' => 'integer',
556 'readonly' => true,
557 ],
558 'name' => [
559 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ),
560 'type' => 'string',
561 ],
562 'desc' => [
563 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
564 'type' => 'string',
565 ],
566 'code' => [
567 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
568 'type' => 'string',
569 ],
570 'tags' => [
571 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ),
572 'type' => 'array',
573 'items' => [
574 'type' => 'string',
575 ],
576 ],
577 'scope' => [
578 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
579 'type' => 'string',
580 ],
581 'condition_id' => [
582 'description' => esc_html__( 'Identifier of condition linked to this snippet.', 'code-snippets' ),
583 'type' => 'integer',
584 ],
585 'active' => [
586 'description' => esc_html__( 'Snippet activation status.', 'code-snippets' ),
587 'type' => 'boolean',
588 ],
589 'priority' => [
590 'description' => esc_html__( 'Relative priority in which the snippet is executed.', 'code-snippets' ),
591 'type' => 'integer',
592 ],
593 'network' => [
594 'description' => esc_html__( 'Whether the snippet is network-wide instead of site-wide.', 'code-snippets' ),
595 'type' => [ 'boolean', 'null' ],
596 'default' => null,
597 ],
598 'shared_network' => [
599 'description' => esc_html__( 'If a network snippet, whether can be activated on discrete sites instead of network-wide.', 'code-snippets' ),
600 'type' => [ 'boolean', 'null' ],
601 ],
602 'modified' => [
603 'description' => esc_html__( 'Date and time when the snippet was last modified, in ISO format.', 'code-snippets' ),
604 'type' => 'string',
605 'format' => 'date-time',
606 'readonly' => true,
607 ],
608 'code_error' => [
609 'description' => esc_html__( 'Error message if the snippet code could not be parsed.', 'code-snippets' ),
610 'type' => 'string',
611 'readonly' => true,
612 ],
613 ],
614 ];
615
616 return $this->schema;
617 }
618 }
619