PluginProbe
Gutenberg / 9.0.0
Gutenberg v9.0.0
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / class-wp-rest-plugins-controller.php

class-wp-rest-plugins-controller.php in Gutenberg 9.0.0, at lib/class-wp-rest-plugins-controller.php

951 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Start: Include for phase 2
4 * Block Directory REST API: WP_REST_Plugins_Controller class
5 *
6 * @since 6.5.0
7 * @package gutenberg
8 */
9
10 /**
11 * Core class to access plugins via the REST API.
12 *
13 * This class can be removed when plugin support requires WordPress 5.5.0+.
14 *
15 * @since 5.5.0
16 *
17 * @see WP_REST_Controller
18 */
19 class WP_REST_Plugins_Controller extends WP_REST_Controller {
20
21 const PATTERN = '[^.\/]+(?:\/[^.\/]+)?';
22
23 /**
24 * Plugins controller constructor.
25 *
26 * @since 5.5.0
27 */
28 public function __construct() {
29 $this->namespace = 'wp/v2';
30 $this->rest_base = 'plugins';
31 }
32
33 /**
34 * Registers the routes for the plugins controller.
35 *
36 * @since 5.5.0
37 */
38 public function register_routes() {
39 register_rest_route(
40 $this->namespace,
41 '/' . $this->rest_base,
42 array(
43 array(
44 'methods' => WP_REST_Server::READABLE,
45 'callback' => array( $this, 'get_items' ),
46 'permission_callback' => array( $this, 'get_items_permissions_check' ),
47 'args' => $this->get_collection_params(),
48 ),
49 array(
50 'methods' => WP_REST_Server::CREATABLE,
51 'callback' => array( $this, 'create_item' ),
52 'permission_callback' => array( $this, 'create_item_permissions_check' ),
53 'args' => array(
54 'slug' => array(
55 'type' => 'string',
56 'required' => true,
57 'description' => __( 'WordPress.org plugin directory slug.', 'gutenberg' ),
58 'pattern' => '[\w\-]+',
59 ),
60 'status' => array(
61 'description' => __( 'The plugin activation status.', 'gutenberg' ),
62 'type' => 'string',
63 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
64 'default' => 'inactive',
65 ),
66 ),
67 ),
68 'schema' => array( $this, 'get_public_item_schema' ),
69 )
70 );
71
72 register_rest_route(
73 $this->namespace,
74 '/' . $this->rest_base . '/(?P<plugin>' . self::PATTERN . ')',
75 array(
76 array(
77 'methods' => WP_REST_Server::READABLE,
78 'callback' => array( $this, 'get_item' ),
79 'permission_callback' => array( $this, 'get_item_permissions_check' ),
80 ),
81 array(
82 'methods' => WP_REST_Server::EDITABLE,
83 'callback' => array( $this, 'update_item' ),
84 'permission_callback' => array( $this, 'update_item_permissions_check' ),
85 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
86 ),
87 array(
88 'methods' => WP_REST_Server::DELETABLE,
89 'callback' => array( $this, 'delete_item' ),
90 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
91 ),
92 'args' => array(
93 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
94 'plugin' => array(
95 'type' => 'string',
96 'pattern' => self::PATTERN,
97 'validate_callback' => array( $this, 'validate_plugin_param' ),
98 'sanitize_callback' => array( $this, 'sanitize_plugin_param' ),
99 ),
100 ),
101 'schema' => array( $this, 'get_public_item_schema' ),
102 )
103 );
104 }
105
106 /**
107 * Checks if a given request has access to get plugins.
108 *
109 * @since 5.5.0
110 *
111 * @param WP_REST_Request $request Full details about the request.
112 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
113 */
114 public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
115 if ( ! current_user_can( 'activate_plugins' ) ) {
116 return new WP_Error(
117 'rest_cannot_view_plugins',
118 __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ),
119 array( 'status' => rest_authorization_required_code() )
120 );
121 }
122
123 return true;
124 }
125
126 /**
127 * Retrieves a collection of plugins.
128 *
129 * @since 5.5.0
130 *
131 * @param WP_REST_Request $request Full details about the request.
132 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
133 */
134 public function get_items( $request ) {
135 require_once ABSPATH . 'wp-admin/includes/plugin.php';
136
137 $plugins = array();
138
139 foreach ( get_plugins() as $file => $data ) {
140 if ( is_wp_error( $this->check_read_permission( $file ) ) ) {
141 continue;
142 }
143
144 $data['_file'] = $file;
145
146 if ( ! $this->does_plugin_match_request( $request, $data ) ) {
147 continue;
148 }
149
150 $plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) );
151 }
152
153 return new WP_REST_Response( $plugins );
154 }
155
156 /**
157 * Checks if a given request has access to get a specific plugin.
158 *
159 * @since 5.5.0
160 *
161 * @param WP_REST_Request $request Full details about the request.
162 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
163 */
164 public function get_item_permissions_check( $request ) {
165 if ( ! current_user_can( 'activate_plugins' ) ) {
166 return new WP_Error(
167 'rest_cannot_view_plugin',
168 __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ),
169 array( 'status' => rest_authorization_required_code() )
170 );
171 }
172
173 $can_read = $this->check_read_permission( $request['plugin'] );
174
175 if ( is_wp_error( $can_read ) ) {
176 return $can_read;
177 }
178
179 return true;
180 }
181
182 /**
183 * Retrieves one plugin from the site.
184 *
185 * @since 5.5.0
186 *
187 * @param WP_REST_Request $request Full details about the request.
188 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
189 */
190 public function get_item( $request ) {
191 require_once ABSPATH . 'wp-admin/includes/plugin.php';
192
193 $data = $this->get_plugin_data( $request['plugin'] );
194
195 if ( is_wp_error( $data ) ) {
196 return $data;
197 }
198
199 return $this->prepare_item_for_response( $data, $request );
200 }
201
202 /**
203 * Checks if the given plugin can be viewed by the current user.
204 *
205 * On multisite, this hides non-active network only plugins if the user does not have permission
206 * to manage network plugins.
207 *
208 * @since 5.5.0
209 *
210 * @param string $plugin The plugin file to check.
211 * @return true|WP_Error True if can read, a WP_Error instance otherwise.
212 */
213 protected function check_read_permission( $plugin ) {
214 if ( ! $this->is_plugin_installed( $plugin ) ) {
215 return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.', 'gutenberg' ), array( 'status' => 404 ) );
216 }
217
218 if ( ! is_multisite() ) {
219 return true;
220 }
221
222 if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) {
223 return true;
224 }
225
226 return new WP_Error(
227 'rest_cannot_view_plugin',
228 __( 'Sorry, you are not allowed to manage this plugin.', 'gutenberg' ),
229 array( 'status' => rest_authorization_required_code() )
230 );
231 }
232
233 /**
234 * Checks if a given request has access to upload plugins.
235 *
236 * @since 5.5.0
237 *
238 * @param WP_REST_Request $request Full details about the request.
239 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
240 */
241 public function create_item_permissions_check( $request ) {
242 if ( ! current_user_can( 'install_plugins' ) ) {
243 return new WP_Error(
244 'rest_cannot_install_plugin',
245 __( 'Sorry, you are not allowed to install plugins on this site.', 'gutenberg' ),
246 array( 'status' => rest_authorization_required_code() )
247 );
248 }
249
250 if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) {
251 return new WP_Error(
252 'rest_cannot_activate_plugin',
253 __( 'Sorry, you are not allowed to activate plugins.', 'gutenberg' ),
254 array(
255 'status' => rest_authorization_required_code(),
256 )
257 );
258 }
259
260 return true;
261 }
262
263 /**
264 * Uploads a plugin and optionally activates it.
265 *
266 * @since 5.5.0
267 *
268 * @param WP_REST_Request $request Full details about the request.
269 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
270 */
271 public function create_item( $request ) {
272 require_once ABSPATH . 'wp-admin/includes/file.php';
273 require_once ABSPATH . 'wp-admin/includes/plugin.php';
274 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
275 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
276
277 $slug = $request['slug'];
278
279 // Verify filesystem is accessible first.
280 $filesystem_available = $this->is_filesystem_available();
281 if ( is_wp_error( $filesystem_available ) ) {
282 return $filesystem_available;
283 }
284
285 $api = plugins_api(
286 'plugin_information',
287 array(
288 'slug' => $slug,
289 'fields' => array(
290 'sections' => false,
291 ),
292 )
293 );
294
295 if ( is_wp_error( $api ) ) {
296 if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
297 $api->add_data( array( 'status' => 404 ) );
298 } else {
299 $api->add_data( array( 'status' => 500 ) );
300 }
301
302 return $api;
303 }
304
305 $skin = new WP_Ajax_Upgrader_Skin();
306 $upgrader = new Plugin_Upgrader( $skin );
307
308 $result = $upgrader->install( $api->download_link );
309
310 if ( is_wp_error( $result ) ) {
311 $result->add_data( array( 'status' => 500 ) );
312
313 return $result;
314 }
315
316 // This should be the same as $result above.
317 if ( is_wp_error( $skin->result ) ) {
318 $skin->result->add_data( array( 'status' => 500 ) );
319
320 return $skin->result;
321 }
322
323 if ( $skin->get_errors()->has_errors() ) {
324 $error = $skin->get_errors();
325 $error->add_data( array( 'status' => 500 ) );
326
327 return $error;
328 }
329
330 if ( is_null( $result ) ) {
331 global $wp_filesystem;
332 // Pass through the error from WP_Filesystem if one was raised.
333 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
334 return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) );
335 }
336
337 return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'gutenberg' ), array( 'status' => 500 ) );
338 }
339
340 $file = $upgrader->plugin_info();
341
342 if ( ! $file ) {
343 return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.', 'gutenberg' ), array( 'status' => 500 ) );
344 }
345
346 if ( 'inactive' !== $request['status'] ) {
347 $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' );
348
349 if ( is_wp_error( $can_change_status ) ) {
350 return $can_change_status;
351 }
352
353 $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' );
354
355 if ( is_wp_error( $changed_status ) ) {
356 return $changed_status;
357 }
358 }
359
360 $path = WP_PLUGIN_DIR . '/' . $file;
361 $data = get_plugin_data( $path, false, false );
362 $data['_file'] = $file;
363
364 $response = $this->prepare_item_for_response( $data, $request );
365 $response->set_status( 201 );
366 $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) );
367
368 return $response;
369 }
370
371 /**
372 * Checks if a given request has access to update a specific plugin.
373 *
374 * @since 5.5.0
375 *
376 * @param WP_REST_Request $request Full details about the request.
377 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
378 */
379 public function update_item_permissions_check( $request ) {
380 require_once ABSPATH . 'wp-admin/includes/plugin.php';
381
382 if ( ! current_user_can( 'activate_plugins' ) ) {
383 return new WP_Error(
384 'rest_cannot_manage_plugins',
385 __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ),
386 array( 'status' => rest_authorization_required_code() )
387 );
388 }
389
390 $can_read = $this->check_read_permission( $request['plugin'] );
391
392 if ( is_wp_error( $can_read ) ) {
393 return $can_read;
394 }
395
396 $status = $this->get_plugin_status( $request['plugin'] );
397
398 if ( $request['status'] && $status !== $request['status'] ) {
399 $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status );
400
401 if ( is_wp_error( $can_change_status ) ) {
402 return $can_change_status;
403 }
404 }
405
406 return true;
407 }
408
409 /**
410 * Updates one plugin.
411 *
412 * @since 5.5.0
413 *
414 * @param WP_REST_Request $request Full details about the request.
415 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
416 */
417 public function update_item( $request ) {
418 require_once ABSPATH . 'wp-admin/includes/plugin.php';
419
420 $data = $this->get_plugin_data( $request['plugin'] );
421
422 if ( is_wp_error( $data ) ) {
423 return $data;
424 }
425
426 $status = $this->get_plugin_status( $request['plugin'] );
427
428 if ( $request['status'] && $status !== $request['status'] ) {
429 $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status );
430
431 if ( is_wp_error( $handled ) ) {
432 return $handled;
433 }
434 }
435
436 $this->update_additional_fields_for_object( $data, $request );
437
438 $request['context'] = 'edit';
439
440 return $this->prepare_item_for_response( $data, $request );
441 }
442
443 /**
444 * Checks if a given request has access to delete a specific plugin.
445 *
446 * @since 5.5.0
447 *
448 * @param WP_REST_Request $request Full details about the request.
449 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
450 */
451 public function delete_item_permissions_check( $request ) {
452 if ( ! current_user_can( 'activate_plugins' ) ) {
453 return new WP_Error(
454 'rest_cannot_manage_plugins',
455 __( 'Sorry, you are not allowed to manage plugins for this site.', 'gutenberg' ),
456 array( 'status' => rest_authorization_required_code() )
457 );
458 }
459
460 if ( ! current_user_can( 'delete_plugins' ) ) {
461 return new WP_Error(
462 'rest_cannot_manage_plugins',
463 __( 'Sorry, you are not allowed to delete plugins for this site.', 'gutenberg' ),
464 array( 'status' => rest_authorization_required_code() )
465 );
466 }
467
468 $can_read = $this->check_read_permission( $request['plugin'] );
469
470 if ( is_wp_error( $can_read ) ) {
471 return $can_read;
472 }
473
474 return true;
475 }
476
477 /**
478 * Deletes one plugin from the site.
479 *
480 * @since 5.5.0
481 *
482 * @param WP_REST_Request $request Full details about the request.
483 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
484 */
485 public function delete_item( $request ) {
486 require_once ABSPATH . 'wp-admin/includes/file.php';
487 require_once ABSPATH . 'wp-admin/includes/plugin.php';
488
489 $data = $this->get_plugin_data( $request['plugin'] );
490
491 if ( is_wp_error( $data ) ) {
492 return $data;
493 }
494
495 if ( is_plugin_active( $request['plugin'] ) ) {
496 return new WP_Error(
497 'rest_cannot_delete_active_plugin',
498 __( 'Cannot delete an active plugin. Please deactivate it first.', 'gutenberg' ),
499 array( 'status' => 400 )
500 );
501 }
502
503 $filesystem_available = $this->is_filesystem_available();
504 if ( is_wp_error( $filesystem_available ) ) {
505 return $filesystem_available;
506 }
507
508 $prepared = $this->prepare_item_for_response( $data, $request );
509 $deleted = delete_plugins( array( $request['plugin'] ) );
510
511 if ( is_wp_error( $deleted ) ) {
512 $deleted->add_data( array( 'status' => 500 ) );
513
514 return $deleted;
515 }
516
517 return new WP_REST_Response(
518 array(
519 'deleted' => true,
520 'previous' => $prepared->get_data(),
521 )
522 );
523 }
524
525 /**
526 * Prepares the plugin for the REST response.
527 *
528 * @since 5.5.0
529 *
530 * @param mixed $item Unmarked up and untranslated plugin data from {@see get_plugin_data()}.
531 * @param WP_REST_Request $request Request object.
532 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
533 */
534 public function prepare_item_for_response( $item, $request ) {
535 $item = _get_plugin_data_markup_translate( $item['_file'], $item, false );
536 $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true );
537
538 $data = array(
539 'plugin' => substr( $item['_file'], 0, - 4 ),
540 'status' => $this->get_plugin_status( $item['_file'] ),
541 'name' => $item['Name'],
542 'plugin_uri' => $item['PluginURI'],
543 'author' => $item['Author'],
544 'author_uri' => $item['AuthorURI'],
545 'description' => array(
546 'raw' => $item['Description'],
547 'rendered' => $marked['Description'],
548 ),
549 'version' => $item['Version'],
550 'network_only' => $item['Network'],
551 'requires_wp' => $item['RequiresWP'],
552 'requires_php' => $item['RequiresPHP'],
553 'text_domain' => $item['TextDomain'],
554 );
555
556 $data = $this->add_additional_fields_to_object( $data, $request );
557
558 $response = new WP_REST_Response( $data );
559 $response->add_links( $this->prepare_links( $item ) );
560
561 /**
562 * Filters the plugin data for a response.
563 *
564 * @since 5.5.0
565 *
566 * @param WP_REST_Response $response The response object.
567 * @param array $item The plugin item from {@see get_plugin_data()}.
568 * @param WP_REST_Request $request The request object.
569 */
570 return apply_filters( 'rest_prepare_plugin', $response, $item, $request );
571 }
572
573 /**
574 * Prepares links for the request.
575 *
576 * @since 5.5.0
577 *
578 * @param array $item The plugin item.
579 * @return array[]
580 */
581 protected function prepare_links( $item ) {
582 return array(
583 'self' => array(
584 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ),
585 ),
586 );
587 }
588
589 /**
590 * Gets the plugin header data for a plugin.
591 *
592 * @since 5.5.0
593 *
594 * @param string $plugin The plugin file to get data for.
595 * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed.
596 */
597 protected function get_plugin_data( $plugin ) {
598 $plugins = get_plugins();
599
600 if ( ! isset( $plugins[ $plugin ] ) ) {
601 return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.', 'gutenberg' ), array( 'status' => 404 ) );
602 }
603
604 $data = $plugins[ $plugin ];
605 $data['_file'] = $plugin;
606
607 return $data;
608 }
609
610 /**
611 * Get's the activation status for a plugin.
612 *
613 * @since 5.5.0
614 *
615 * @param string $plugin The plugin file to check.
616 * @return string Either 'network-active', 'active' or 'inactive'.
617 */
618 protected function get_plugin_status( $plugin ) {
619 if ( is_plugin_active_for_network( $plugin ) ) {
620 return 'network-active';
621 }
622
623 if ( is_plugin_active( $plugin ) ) {
624 return 'active';
625 }
626
627 return 'inactive';
628 }
629
630 /**
631 * Handle updating a plugin's status.
632 *
633 * @since 5.5.0
634 *
635 * @param string $plugin The plugin file to update.
636 * @param string $new_status The plugin's new status.
637 * @param string $current_status The plugin's current status.
638 *
639 * @return true|WP_Error
640 */
641 protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) {
642 if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) {
643 return new WP_Error(
644 'rest_cannot_manage_network_plugins',
645 __( 'Sorry, you do not have permission to manage network plugins.', 'gutenberg' ),
646 array( 'status' => rest_authorization_required_code() )
647 );
648 }
649
650 if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) {
651 return new WP_Error(
652 'rest_cannot_activate_plugin',
653 __( 'Sorry, you are not allowed to activate this plugin.', 'gutenberg' ),
654 array( 'status' => rest_authorization_required_code() )
655 );
656 }
657
658 if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) {
659 return new WP_Error(
660 'rest_cannot_deactivate_plugin',
661 __( 'Sorry, you are not allowed to deactivate this plugin.', 'gutenberg' ),
662 array( 'status' => rest_authorization_required_code() )
663 );
664 }
665
666 return true;
667 }
668
669 /**
670 * Handle updating a plugin's status.
671 *
672 * @since 5.5.0
673 *
674 * @param string $plugin The plugin file to update.
675 * @param string $new_status The plugin's new status.
676 * @param string $current_status The plugin's current status.
677 * @return true|WP_Error
678 */
679 protected function handle_plugin_status( $plugin, $new_status, $current_status ) {
680 if ( 'inactive' === $new_status ) {
681 deactivate_plugins( $plugin, false, 'network-active' === $current_status );
682
683 return true;
684 }
685
686 if ( 'active' === $new_status && 'network-active' === $current_status ) {
687 return true;
688 }
689
690 $network_activate = 'network-active' === $new_status;
691
692 if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) {
693 return new WP_Error(
694 'rest_network_only_plugin',
695 __( 'Network only plugin must be network activated.', 'gutenberg' ),
696 array( 'status' => 400 )
697 );
698 }
699
700 $activated = activate_plugin( $plugin, '', $network_activate );
701
702 if ( is_wp_error( $activated ) ) {
703 $activated->add_data( array( 'status' => 500 ) );
704
705 return $activated;
706 }
707
708 return true;
709 }
710
711 /**
712 * Checks that the "plugin" parameter is a valid path.
713 *
714 * @since 5.5.0
715 *
716 * @param string $file The plugin file parameter.
717 * @return bool
718 */
719 public function validate_plugin_param( $file ) {
720 if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) {
721 return false;
722 }
723
724 $validated = validate_file( plugin_basename( $file ) );
725
726 return 0 === $validated;
727 }
728
729 /**
730 * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended.
731 *
732 * @since 5.5.0
733 *
734 * @param string $file The plugin file parameter.
735 * @return string
736 */
737 public function sanitize_plugin_param( $file ) {
738 return plugin_basename( sanitize_text_field( $file . '.php' ) );
739 }
740
741 /**
742 * Checks if the plugin matches the requested parameters.
743 *
744 * @since 5.5.0
745 *
746 * @param WP_REST_Request $request The request to require the plugin matches against.
747 * @param array $item The plugin item.
748 *
749 * @return bool
750 */
751 protected function does_plugin_match_request( $request, $item ) {
752 $search = $request['search'];
753
754 if ( $search ) {
755 $matched_search = false;
756
757 foreach ( $item as $field ) {
758 if ( is_string( $field ) && false !== strpos( strip_tags( $field ), $search ) ) {
759 $matched_search = true;
760 break;
761 }
762 }
763
764 if ( ! $matched_search ) {
765 return false;
766 }
767 }
768
769 $status = $request['status'];
770
771 if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) {
772 return false;
773 }
774
775 return true;
776 }
777
778 /**
779 * Checks if the plugin is installed.
780 *
781 * @since 5.5.0
782 *
783 * @param string $plugin The plugin file.
784 * @return bool
785 */
786 protected function is_plugin_installed( $plugin ) {
787 return file_exists( WP_PLUGIN_DIR . '/' . $plugin );
788 }
789
790 /**
791 * Determine if the endpoints are available.
792 *
793 * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
794 *
795 * @since 5.5.0
796 *
797 * @return true|WP_Error True if filesystem is available, WP_Error otherwise.
798 */
799 protected function is_filesystem_available() {
800 $filesystem_method = get_filesystem_method();
801
802 if ( 'direct' === $filesystem_method ) {
803 return true;
804 }
805
806 ob_start();
807 $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
808 ob_end_clean();
809
810 if ( $filesystem_credentials_are_stored ) {
811 return true;
812 }
813
814 return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.', 'gutenberg' ), array( 'status' => 500 ) );
815 }
816
817 /**
818 * Retrieves the plugin's schema, conforming to JSON Schema.
819 *
820 * @since 4.7.0
821 *
822 * @return array Item schema data.
823 */
824 public function get_item_schema() {
825 if ( $this->schema ) {
826 return $this->add_additional_fields_schema( $this->schema );
827 }
828
829 $this->schema = array(
830 '$schema' => 'http://json-schema.org/draft-04/schema#',
831 'title' => 'plugin',
832 'type' => 'object',
833 'properties' => array(
834 'plugin' => array(
835 'description' => __( 'The plugin file.', 'gutenberg' ),
836 'type' => 'string',
837 'pattern' => self::PATTERN,
838 'readonly' => true,
839 'context' => array( 'view', 'edit', 'embed' ),
840 ),
841 'status' => array(
842 'description' => __( 'The plugin activation status.', 'gutenberg' ),
843 'type' => 'string',
844 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
845 'context' => array( 'view', 'edit', 'embed' ),
846 ),
847 'name' => array(
848 'description' => __( 'The plugin name.', 'gutenberg' ),
849 'type' => 'string',
850 'readonly' => true,
851 'context' => array( 'view', 'edit', 'embed' ),
852 ),
853 'plugin_uri' => array(
854 'description' => __( 'The plugin\'s website address.', 'gutenberg' ),
855 'type' => 'string',
856 'format' => 'uri',
857 'readonly' => true,
858 'context' => array( 'view', 'edit' ),
859 ),
860 'author' => array(
861 'description' => __( 'The plugin author.', 'gutenberg' ),
862 'type' => 'object',
863 'readonly' => true,
864 'context' => array( 'view', 'edit' ),
865 ),
866 'author_uri' => array(
867 'description' => __( 'Plugin author\'s website address.', 'gutenberg' ),
868 'type' => 'string',
869 'format' => 'uri',
870 'readonly' => true,
871 'context' => array( 'view', 'edit' ),
872 ),
873 'description' => array(
874 'description' => __( 'The plugin description.', 'gutenberg' ),
875 'type' => 'object',
876 'readonly' => true,
877 'context' => array( 'view', 'edit' ),
878 'properties' => array(
879 'raw' => array(
880 'description' => __( 'The raw plugin description.', 'gutenberg' ),
881 'type' => 'string',
882 ),
883 'rendered' => array(
884 'description' => __( 'The plugin description formatted for display.', 'gutenberg' ),
885 'type' => 'string',
886 ),
887 ),
888 ),
889 'version' => array(
890 'description' => __( 'The plugin version number.', 'gutenberg' ),
891 'type' => 'string',
892 'readonly' => true,
893 'context' => array( 'view', 'edit' ),
894 ),
895 'network_only' => array(
896 'description' => __( 'Whether the plugin can only be activated network-wide.', 'gutenberg' ),
897 'type' => 'boolean',
898 'readonly' => true,
899 'context' => array( 'view', 'edit', 'embed' ),
900 ),
901 'requires_wp' => array(
902 'description' => __( 'Minimum required version of WordPress.', 'gutenberg' ),
903 'type' => 'string',
904 'readonly' => true,
905 'context' => array( 'view', 'edit', 'embed' ),
906 ),
907 'requires_php' => array(
908 'description' => __( 'Minimum required version of PHP.', 'gutenberg' ),
909 'type' => 'string',
910 'readonly' => true,
911 'context' => array( 'view', 'edit', 'embed' ),
912 ),
913 'text_domain' => array(
914 'description' => __( 'The plugin\'s text domain.', 'gutenberg' ),
915 'type' => 'string',
916 'readonly' => true,
917 'context' => array( 'view', 'edit' ),
918 ),
919 ),
920 );
921
922 return $this->add_additional_fields_schema( $this->schema );
923 }
924
925 /**
926 * Retrieves the query params for the collections.
927 *
928 * @since 5.5.0
929 *
930 * @return array Query parameters for the collection.
931 */
932 public function get_collection_params() {
933 $query_params = parent::get_collection_params();
934
935 $query_params['context']['default'] = 'view';
936
937 $query_params['status'] = array(
938 'description' => __( 'Limits results to plugins with the given status.', 'gutenberg' ),
939 'type' => 'array',
940 'items' => array(
941 'type' => 'string',
942 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
943 ),
944 );
945
946 unset( $query_params['page'], $query_params['per_page'] );
947
948 return $query_params;
949 }
950 }
951