[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-plugins-controller.php (source)

   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'        => 'object',
 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  }


Generated : Fri Mar 29 08:20:02 2024 Cross-referenced by PHPXref