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