[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[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 ( str_contains( $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 array $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 $fields = $this->get_fields_for_response( $request ); 580 581 $item = _get_plugin_data_markup_translate( $item['_file'], $item, false ); 582 $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true ); 583 584 $data = array( 585 'plugin' => substr( $item['_file'], 0, - 4 ), 586 'status' => $this->get_plugin_status( $item['_file'] ), 587 'name' => $item['Name'], 588 'plugin_uri' => $item['PluginURI'], 589 'author' => $item['Author'], 590 'author_uri' => $item['AuthorURI'], 591 'description' => array( 592 'raw' => $item['Description'], 593 'rendered' => $marked['Description'], 594 ), 595 'version' => $item['Version'], 596 'network_only' => $item['Network'], 597 'requires_wp' => $item['RequiresWP'], 598 'requires_php' => $item['RequiresPHP'], 599 'textdomain' => $item['TextDomain'], 600 ); 601 602 $data = $this->add_additional_fields_to_object( $data, $request ); 603 604 $response = new WP_REST_Response( $data ); 605 606 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 607 $response->add_links( $this->prepare_links( $item ) ); 608 } 609 610 /** 611 * Filters plugin data for a REST API response. 612 * 613 * @since 5.5.0 614 * 615 * @param WP_REST_Response $response The response object. 616 * @param array $item The plugin item from {@see get_plugin_data()}. 617 * @param WP_REST_Request $request The request object. 618 */ 619 return apply_filters( 'rest_prepare_plugin', $response, $item, $request ); 620 } 621 622 /** 623 * Prepares links for the request. 624 * 625 * @since 5.5.0 626 * 627 * @param array $item The plugin item. 628 * @return array[] 629 */ 630 protected function prepare_links( $item ) { 631 return array( 632 'self' => array( 633 'href' => rest_url( 634 sprintf( 635 '%s/%s/%s', 636 $this->namespace, 637 $this->rest_base, 638 substr( $item['_file'], 0, - 4 ) 639 ) 640 ), 641 ), 642 ); 643 } 644 645 /** 646 * Gets the plugin header data for a plugin. 647 * 648 * @since 5.5.0 649 * 650 * @param string $plugin The plugin file to get data for. 651 * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed. 652 */ 653 protected function get_plugin_data( $plugin ) { 654 $plugins = get_plugins(); 655 656 if ( ! isset( $plugins[ $plugin ] ) ) { 657 return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) ); 658 } 659 660 $data = $plugins[ $plugin ]; 661 $data['_file'] = $plugin; 662 663 return $data; 664 } 665 666 /** 667 * Get's the activation status for a plugin. 668 * 669 * @since 5.5.0 670 * 671 * @param string $plugin The plugin file to check. 672 * @return string Either 'network-active', 'active' or 'inactive'. 673 */ 674 protected function get_plugin_status( $plugin ) { 675 if ( is_plugin_active_for_network( $plugin ) ) { 676 return 'network-active'; 677 } 678 679 if ( is_plugin_active( $plugin ) ) { 680 return 'active'; 681 } 682 683 return 'inactive'; 684 } 685 686 /** 687 * Handle updating a plugin's status. 688 * 689 * @since 5.5.0 690 * 691 * @param string $plugin The plugin file to update. 692 * @param string $new_status The plugin's new status. 693 * @param string $current_status The plugin's current status. 694 * @return true|WP_Error 695 */ 696 protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) { 697 if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) { 698 return new WP_Error( 699 'rest_cannot_manage_network_plugins', 700 __( 'Sorry, you are not allowed to manage network plugins.' ), 701 array( 'status' => rest_authorization_required_code() ) 702 ); 703 } 704 705 if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) { 706 return new WP_Error( 707 'rest_cannot_activate_plugin', 708 __( 'Sorry, you are not allowed to activate this plugin.' ), 709 array( 'status' => rest_authorization_required_code() ) 710 ); 711 } 712 713 if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) { 714 return new WP_Error( 715 'rest_cannot_deactivate_plugin', 716 __( 'Sorry, you are not allowed to deactivate this plugin.' ), 717 array( 'status' => rest_authorization_required_code() ) 718 ); 719 } 720 721 return true; 722 } 723 724 /** 725 * Handle updating a plugin's status. 726 * 727 * @since 5.5.0 728 * 729 * @param string $plugin The plugin file to update. 730 * @param string $new_status The plugin's new status. 731 * @param string $current_status The plugin's current status. 732 * @return true|WP_Error 733 */ 734 protected function handle_plugin_status( $plugin, $new_status, $current_status ) { 735 if ( 'inactive' === $new_status ) { 736 deactivate_plugins( $plugin, false, 'network-active' === $current_status ); 737 738 return true; 739 } 740 741 if ( 'active' === $new_status && 'network-active' === $current_status ) { 742 return true; 743 } 744 745 $network_activate = 'network-active' === $new_status; 746 747 if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) { 748 return new WP_Error( 749 'rest_network_only_plugin', 750 __( 'Network only plugin must be network activated.' ), 751 array( 'status' => 400 ) 752 ); 753 } 754 755 $activated = activate_plugin( $plugin, '', $network_activate ); 756 757 if ( is_wp_error( $activated ) ) { 758 $activated->add_data( array( 'status' => 500 ) ); 759 760 return $activated; 761 } 762 763 return true; 764 } 765 766 /** 767 * Checks that the "plugin" parameter is a valid path. 768 * 769 * @since 5.5.0 770 * 771 * @param string $file The plugin file parameter. 772 * @return bool 773 */ 774 public function validate_plugin_param( $file ) { 775 if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) { 776 return false; 777 } 778 779 $validated = validate_file( plugin_basename( $file ) ); 780 781 return 0 === $validated; 782 } 783 784 /** 785 * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended. 786 * 787 * @since 5.5.0 788 * 789 * @param string $file The plugin file parameter. 790 * @return string 791 */ 792 public function sanitize_plugin_param( $file ) { 793 return plugin_basename( sanitize_text_field( $file . '.php' ) ); 794 } 795 796 /** 797 * Checks if the plugin matches the requested parameters. 798 * 799 * @since 5.5.0 800 * 801 * @param WP_REST_Request $request The request to require the plugin matches against. 802 * @param array $item The plugin item. 803 * @return bool 804 */ 805 protected function does_plugin_match_request( $request, $item ) { 806 $search = $request['search']; 807 808 if ( $search ) { 809 $matched_search = false; 810 811 foreach ( $item as $field ) { 812 if ( is_string( $field ) && str_contains( strip_tags( $field ), $search ) ) { 813 $matched_search = true; 814 break; 815 } 816 } 817 818 if ( ! $matched_search ) { 819 return false; 820 } 821 } 822 823 $status = $request['status']; 824 825 if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) { 826 return false; 827 } 828 829 return true; 830 } 831 832 /** 833 * Checks if the plugin is installed. 834 * 835 * @since 5.5.0 836 * 837 * @param string $plugin The plugin file. 838 * @return bool 839 */ 840 protected function is_plugin_installed( $plugin ) { 841 return file_exists( WP_PLUGIN_DIR . '/' . $plugin ); 842 } 843 844 /** 845 * Determine if the endpoints are available. 846 * 847 * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present. 848 * 849 * @since 5.5.0 850 * 851 * @return true|WP_Error True if filesystem is available, WP_Error otherwise. 852 */ 853 protected function is_filesystem_available() { 854 $filesystem_method = get_filesystem_method(); 855 856 if ( 'direct' === $filesystem_method ) { 857 return true; 858 } 859 860 ob_start(); 861 $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() ); 862 ob_end_clean(); 863 864 if ( $filesystem_credentials_are_stored ) { 865 return true; 866 } 867 868 return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) ); 869 } 870 871 /** 872 * Retrieves the plugin's schema, conforming to JSON Schema. 873 * 874 * @since 5.5.0 875 * 876 * @return array Item schema data. 877 */ 878 public function get_item_schema() { 879 if ( $this->schema ) { 880 return $this->add_additional_fields_schema( $this->schema ); 881 } 882 883 $this->schema = array( 884 '$schema' => 'http://json-schema.org/draft-04/schema#', 885 'title' => 'plugin', 886 'type' => 'object', 887 'properties' => array( 888 'plugin' => array( 889 'description' => __( 'The plugin file.' ), 890 'type' => 'string', 891 'pattern' => self::PATTERN, 892 'readonly' => true, 893 'context' => array( 'view', 'edit', 'embed' ), 894 ), 895 'status' => array( 896 'description' => __( 'The plugin activation status.' ), 897 'type' => 'string', 898 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), 899 'context' => array( 'view', 'edit', 'embed' ), 900 ), 901 'name' => array( 902 'description' => __( 'The plugin name.' ), 903 'type' => 'string', 904 'readonly' => true, 905 'context' => array( 'view', 'edit', 'embed' ), 906 ), 907 'plugin_uri' => array( 908 'description' => __( 'The plugin\'s website address.' ), 909 'type' => 'string', 910 'format' => 'uri', 911 'readonly' => true, 912 'context' => array( 'view', 'edit' ), 913 ), 914 'author' => array( 915 'description' => __( 'The plugin author.' ), 916 'type' => 'string', 917 'readonly' => true, 918 'context' => array( 'view', 'edit' ), 919 ), 920 'author_uri' => array( 921 'description' => __( 'Plugin author\'s website address.' ), 922 'type' => 'string', 923 'format' => 'uri', 924 'readonly' => true, 925 'context' => array( 'view', 'edit' ), 926 ), 927 'description' => array( 928 'description' => __( 'The plugin description.' ), 929 'type' => 'object', 930 'readonly' => true, 931 'context' => array( 'view', 'edit' ), 932 'properties' => array( 933 'raw' => array( 934 'description' => __( 'The raw plugin description.' ), 935 'type' => 'string', 936 ), 937 'rendered' => array( 938 'description' => __( 'The plugin description formatted for display.' ), 939 'type' => 'string', 940 ), 941 ), 942 ), 943 'version' => array( 944 'description' => __( 'The plugin version number.' ), 945 'type' => 'string', 946 'readonly' => true, 947 'context' => array( 'view', 'edit' ), 948 ), 949 'network_only' => array( 950 'description' => __( 'Whether the plugin can only be activated network-wide.' ), 951 'type' => 'boolean', 952 'readonly' => true, 953 'context' => array( 'view', 'edit', 'embed' ), 954 ), 955 'requires_wp' => array( 956 'description' => __( 'Minimum required version of WordPress.' ), 957 'type' => 'string', 958 'readonly' => true, 959 'context' => array( 'view', 'edit', 'embed' ), 960 ), 961 'requires_php' => array( 962 'description' => __( 'Minimum required version of PHP.' ), 963 'type' => 'string', 964 'readonly' => true, 965 'context' => array( 'view', 'edit', 'embed' ), 966 ), 967 'textdomain' => array( 968 'description' => __( 'The plugin\'s text domain.' ), 969 'type' => 'string', 970 'readonly' => true, 971 'context' => array( 'view', 'edit' ), 972 ), 973 ), 974 ); 975 976 return $this->add_additional_fields_schema( $this->schema ); 977 } 978 979 /** 980 * Retrieves the query params for the collections. 981 * 982 * @since 5.5.0 983 * 984 * @return array Query parameters for the collection. 985 */ 986 public function get_collection_params() { 987 $query_params = parent::get_collection_params(); 988 989 $query_params['context']['default'] = 'view'; 990 991 $query_params['status'] = array( 992 'description' => __( 'Limits results to plugins with the given status.' ), 993 'type' => 'array', 994 'items' => array( 995 'type' => 'string', 996 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), 997 ), 998 ); 999 1000 unset( $query_params['page'], $query_params['per_page'] ); 1001 1002 return $query_params; 1003 } 1004 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Dec 21 08:20:01 2024 | Cross-referenced by PHPXref |