"Fossies" - the Fresh Open Source Software Archive

Member "wordpress/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php" (10 Jan 2021, 28186 Bytes) of package /linux/www/wordpress-5.7-RC1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "class-wp-rest-plugins-controller.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6.2_vs_5.7-RC1.

    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      * @param WP_REST_Request $request Full details about the request.
  269      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  270      */
  271     public function create_item( $request ) {
  272         require_once ABSPATH . 'wp-admin/includes/file.php';
  273         require_once ABSPATH . 'wp-admin/includes/plugin.php';
  274         require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  275         require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
  276 
  277         $slug = $request['slug'];
  278 
  279         // Verify filesystem is accessible first.
  280         $filesystem_available = $this->is_filesystem_available();
  281         if ( is_wp_error( $filesystem_available ) ) {
  282             return $filesystem_available;
  283         }
  284 
  285         $api = plugins_api(
  286             'plugin_information',
  287             array(
  288                 'slug'   => $slug,
  289                 'fields' => array(
  290                     'sections'       => false,
  291                     'language_packs' => true,
  292                 ),
  293             )
  294         );
  295 
  296         if ( is_wp_error( $api ) ) {
  297             if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
  298                 $api->add_data( array( 'status' => 404 ) );
  299             } else {
  300                 $api->add_data( array( 'status' => 500 ) );
  301             }
  302 
  303             return $api;
  304         }
  305 
  306         $skin     = new WP_Ajax_Upgrader_Skin();
  307         $upgrader = new Plugin_Upgrader( $skin );
  308 
  309         $result = $upgrader->install( $api->download_link );
  310 
  311         if ( is_wp_error( $result ) ) {
  312             $result->add_data( array( 'status' => 500 ) );
  313 
  314             return $result;
  315         }
  316 
  317         // This should be the same as $result above.
  318         if ( is_wp_error( $skin->result ) ) {
  319             $skin->result->add_data( array( 'status' => 500 ) );
  320 
  321             return $skin->result;
  322         }
  323 
  324         if ( $skin->get_errors()->has_errors() ) {
  325             $error = $skin->get_errors();
  326             $error->add_data( array( 'status' => 500 ) );
  327 
  328             return $error;
  329         }
  330 
  331         if ( is_null( $result ) ) {
  332             global $wp_filesystem;
  333             // Pass through the error from WP_Filesystem if one was raised.
  334             if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
  335                 return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) );
  336             }
  337 
  338             return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.' ), array( 'status' => 500 ) );
  339         }
  340 
  341         $file = $upgrader->plugin_info();
  342 
  343         if ( ! $file ) {
  344             return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.' ), array( 'status' => 500 ) );
  345         }
  346 
  347         if ( 'inactive' !== $request['status'] ) {
  348             $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' );
  349 
  350             if ( is_wp_error( $can_change_status ) ) {
  351                 return $can_change_status;
  352             }
  353 
  354             $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' );
  355 
  356             if ( is_wp_error( $changed_status ) ) {
  357                 return $changed_status;
  358             }
  359         }
  360 
  361         // Install translations.
  362         $installed_locales = array_values( get_available_languages() );
  363         /** This filter is documented in wp-includes/update.php */
  364         $installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales );
  365 
  366         $language_packs = array_map(
  367             function( $item ) {
  368                 return (object) $item;
  369             },
  370             $api->language_packs
  371         );
  372 
  373         $language_packs = array_filter(
  374             $language_packs,
  375             function( $pack ) use ( $installed_locales ) {
  376                 return in_array( $pack->language, $installed_locales, true );
  377             }
  378         );
  379 
  380         if ( $language_packs ) {
  381             $lp_upgrader = new Language_Pack_Upgrader( $skin );
  382 
  383             // Install all applicable language packs for the plugin.
  384             $lp_upgrader->bulk_upgrade( $language_packs );
  385         }
  386 
  387         $path          = WP_PLUGIN_DIR . '/' . $file;
  388         $data          = get_plugin_data( $path, false, false );
  389         $data['_file'] = $file;
  390 
  391         $response = $this->prepare_item_for_response( $data, $request );
  392         $response->set_status( 201 );
  393         $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) );
  394 
  395         return $response;
  396     }
  397 
  398     /**
  399      * Checks if a given request has access to update a specific plugin.
  400      *
  401      * @since 5.5.0
  402      *
  403      * @param WP_REST_Request $request Full details about the request.
  404      * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
  405      */
  406     public function update_item_permissions_check( $request ) {
  407         require_once ABSPATH . 'wp-admin/includes/plugin.php';
  408 
  409         if ( ! current_user_can( 'activate_plugins' ) ) {
  410             return new WP_Error(
  411                 'rest_cannot_manage_plugins',
  412                 __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  413                 array( 'status' => rest_authorization_required_code() )
  414             );
  415         }
  416 
  417         $can_read = $this->check_read_permission( $request['plugin'] );
  418 
  419         if ( is_wp_error( $can_read ) ) {
  420             return $can_read;
  421         }
  422 
  423         $status = $this->get_plugin_status( $request['plugin'] );
  424 
  425         if ( $request['status'] && $status !== $request['status'] ) {
  426             $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status );
  427 
  428             if ( is_wp_error( $can_change_status ) ) {
  429                 return $can_change_status;
  430             }
  431         }
  432 
  433         return true;
  434     }
  435 
  436     /**
  437      * Updates one plugin.
  438      *
  439      * @since 5.5.0
  440      *
  441      * @param WP_REST_Request $request Full details about the request.
  442      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  443      */
  444     public function update_item( $request ) {
  445         require_once ABSPATH . 'wp-admin/includes/plugin.php';
  446 
  447         $data = $this->get_plugin_data( $request['plugin'] );
  448 
  449         if ( is_wp_error( $data ) ) {
  450             return $data;
  451         }
  452 
  453         $status = $this->get_plugin_status( $request['plugin'] );
  454 
  455         if ( $request['status'] && $status !== $request['status'] ) {
  456             $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status );
  457 
  458             if ( is_wp_error( $handled ) ) {
  459                 return $handled;
  460             }
  461         }
  462 
  463         $this->update_additional_fields_for_object( $data, $request );
  464 
  465         $request['context'] = 'edit';
  466 
  467         return $this->prepare_item_for_response( $data, $request );
  468     }
  469 
  470     /**
  471      * Checks if a given request has access to delete a specific plugin.
  472      *
  473      * @since 5.5.0
  474      *
  475      * @param WP_REST_Request $request Full details about the request.
  476      * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
  477      */
  478     public function delete_item_permissions_check( $request ) {
  479         if ( ! current_user_can( 'activate_plugins' ) ) {
  480             return new WP_Error(
  481                 'rest_cannot_manage_plugins',
  482                 __( 'Sorry, you are not allowed to manage plugins for this site.' ),
  483                 array( 'status' => rest_authorization_required_code() )
  484             );
  485         }
  486 
  487         if ( ! current_user_can( 'delete_plugins' ) ) {
  488             return new WP_Error(
  489                 'rest_cannot_manage_plugins',
  490                 __( 'Sorry, you are not allowed to delete plugins for this site.' ),
  491                 array( 'status' => rest_authorization_required_code() )
  492             );
  493         }
  494 
  495         $can_read = $this->check_read_permission( $request['plugin'] );
  496 
  497         if ( is_wp_error( $can_read ) ) {
  498             return $can_read;
  499         }
  500 
  501         return true;
  502     }
  503 
  504     /**
  505      * Deletes one plugin from the site.
  506      *
  507      * @since 5.5.0
  508      *
  509      * @param WP_REST_Request $request Full details about the request.
  510      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  511      */
  512     public function delete_item( $request ) {
  513         require_once ABSPATH . 'wp-admin/includes/file.php';
  514         require_once ABSPATH . 'wp-admin/includes/plugin.php';
  515 
  516         $data = $this->get_plugin_data( $request['plugin'] );
  517 
  518         if ( is_wp_error( $data ) ) {
  519             return $data;
  520         }
  521 
  522         if ( is_plugin_active( $request['plugin'] ) ) {
  523             return new WP_Error(
  524                 'rest_cannot_delete_active_plugin',
  525                 __( 'Cannot delete an active plugin. Please deactivate it first.' ),
  526                 array( 'status' => 400 )
  527             );
  528         }
  529 
  530         $filesystem_available = $this->is_filesystem_available();
  531         if ( is_wp_error( $filesystem_available ) ) {
  532             return $filesystem_available;
  533         }
  534 
  535         $prepared = $this->prepare_item_for_response( $data, $request );
  536         $deleted  = delete_plugins( array( $request['plugin'] ) );
  537 
  538         if ( is_wp_error( $deleted ) ) {
  539             $deleted->add_data( array( 'status' => 500 ) );
  540 
  541             return $deleted;
  542         }
  543 
  544         return new WP_REST_Response(
  545             array(
  546                 'deleted'  => true,
  547                 'previous' => $prepared->get_data(),
  548             )
  549         );
  550     }
  551 
  552     /**
  553      * Prepares the plugin for the REST response.
  554      *
  555      * @since 5.5.0
  556      *
  557      * @param mixed           $item    Unmarked up and untranslated plugin data from {@see get_plugin_data()}.
  558      * @param WP_REST_Request $request Request object.
  559      * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  560      */
  561     public function prepare_item_for_response( $item, $request ) {
  562         $item   = _get_plugin_data_markup_translate( $item['_file'], $item, false );
  563         $marked = _get_plugin_data_markup_translate( $item['_file'], $item, true );
  564 
  565         $data = array(
  566             'plugin'       => substr( $item['_file'], 0, - 4 ),
  567             'status'       => $this->get_plugin_status( $item['_file'] ),
  568             'name'         => $item['Name'],
  569             'plugin_uri'   => $item['PluginURI'],
  570             'author'       => $item['Author'],
  571             'author_uri'   => $item['AuthorURI'],
  572             'description'  => array(
  573                 'raw'      => $item['Description'],
  574                 'rendered' => $marked['Description'],
  575             ),
  576             'version'      => $item['Version'],
  577             'network_only' => $item['Network'],
  578             'requires_wp'  => $item['RequiresWP'],
  579             'requires_php' => $item['RequiresPHP'],
  580             'textdomain'   => $item['TextDomain'],
  581         );
  582 
  583         $data = $this->add_additional_fields_to_object( $data, $request );
  584 
  585         $response = new WP_REST_Response( $data );
  586         $response->add_links( $this->prepare_links( $item ) );
  587 
  588         /**
  589          * Filters plugin data for a REST API response.
  590          *
  591          * @since 5.5.0
  592          *
  593          * @param WP_REST_Response $response The response object.
  594          * @param array            $item     The plugin item from {@see get_plugin_data()}.
  595          * @param WP_REST_Request  $request  The request object.
  596          */
  597         return apply_filters( 'rest_prepare_plugin', $response, $item, $request );
  598     }
  599 
  600     /**
  601      * Prepares links for the request.
  602      *
  603      * @since 5.5.0
  604      *
  605      * @param array $item The plugin item.
  606      * @return array[]
  607      */
  608     protected function prepare_links( $item ) {
  609         return array(
  610             'self' => array(
  611                 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ),
  612             ),
  613         );
  614     }
  615 
  616     /**
  617      * Gets the plugin header data for a plugin.
  618      *
  619      * @since 5.5.0
  620      *
  621      * @param string $plugin The plugin file to get data for.
  622      * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed.
  623      */
  624     protected function get_plugin_data( $plugin ) {
  625         $plugins = get_plugins();
  626 
  627         if ( ! isset( $plugins[ $plugin ] ) ) {
  628             return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
  629         }
  630 
  631         $data          = $plugins[ $plugin ];
  632         $data['_file'] = $plugin;
  633 
  634         return $data;
  635     }
  636 
  637     /**
  638      * Get's the activation status for a plugin.
  639      *
  640      * @since 5.5.0
  641      *
  642      * @param string $plugin The plugin file to check.
  643      * @return string Either 'network-active', 'active' or 'inactive'.
  644      */
  645     protected function get_plugin_status( $plugin ) {
  646         if ( is_plugin_active_for_network( $plugin ) ) {
  647             return 'network-active';
  648         }
  649 
  650         if ( is_plugin_active( $plugin ) ) {
  651             return 'active';
  652         }
  653 
  654         return 'inactive';
  655     }
  656 
  657     /**
  658      * Handle updating a plugin's status.
  659      *
  660      * @since 5.5.0
  661      *
  662      * @param string $plugin         The plugin file to update.
  663      * @param string $new_status     The plugin's new status.
  664      * @param string $current_status The plugin's current status.
  665      *
  666      * @return true|WP_Error
  667      */
  668     protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) {
  669         if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) {
  670             return new WP_Error(
  671                 'rest_cannot_manage_network_plugins',
  672                 __( 'Sorry, you are not allowed to manage network plugins.' ),
  673                 array( 'status' => rest_authorization_required_code() )
  674             );
  675         }
  676 
  677         if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) {
  678             return new WP_Error(
  679                 'rest_cannot_activate_plugin',
  680                 __( 'Sorry, you are not allowed to activate this plugin.' ),
  681                 array( 'status' => rest_authorization_required_code() )
  682             );
  683         }
  684 
  685         if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) {
  686             return new WP_Error(
  687                 'rest_cannot_deactivate_plugin',
  688                 __( 'Sorry, you are not allowed to deactivate this plugin.' ),
  689                 array( 'status' => rest_authorization_required_code() )
  690             );
  691         }
  692 
  693         return true;
  694     }
  695 
  696     /**
  697      * Handle updating a plugin's status.
  698      *
  699      * @since 5.5.0
  700      *
  701      * @param string $plugin         The plugin file to update.
  702      * @param string $new_status     The plugin's new status.
  703      * @param string $current_status The plugin's current status.
  704      * @return true|WP_Error
  705      */
  706     protected function handle_plugin_status( $plugin, $new_status, $current_status ) {
  707         if ( 'inactive' === $new_status ) {
  708             deactivate_plugins( $plugin, false, 'network-active' === $current_status );
  709 
  710             return true;
  711         }
  712 
  713         if ( 'active' === $new_status && 'network-active' === $current_status ) {
  714             return true;
  715         }
  716 
  717         $network_activate = 'network-active' === $new_status;
  718 
  719         if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) {
  720             return new WP_Error(
  721                 'rest_network_only_plugin',
  722                 __( 'Network only plugin must be network activated.' ),
  723                 array( 'status' => 400 )
  724             );
  725         }
  726 
  727         $activated = activate_plugin( $plugin, '', $network_activate );
  728 
  729         if ( is_wp_error( $activated ) ) {
  730             $activated->add_data( array( 'status' => 500 ) );
  731 
  732             return $activated;
  733         }
  734 
  735         return true;
  736     }
  737 
  738     /**
  739      * Checks that the "plugin" parameter is a valid path.
  740      *
  741      * @since 5.5.0
  742      *
  743      * @param string $file The plugin file parameter.
  744      * @return bool
  745      */
  746     public function validate_plugin_param( $file ) {
  747         if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) {
  748             return false;
  749         }
  750 
  751         $validated = validate_file( plugin_basename( $file ) );
  752 
  753         return 0 === $validated;
  754     }
  755 
  756     /**
  757      * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended.
  758      *
  759      * @since 5.5.0
  760      *
  761      * @param string $file The plugin file parameter.
  762      * @return string
  763      */
  764     public function sanitize_plugin_param( $file ) {
  765         return plugin_basename( sanitize_text_field( $file . '.php' ) );
  766     }
  767 
  768     /**
  769      * Checks if the plugin matches the requested parameters.
  770      *
  771      * @since 5.5.0
  772      *
  773      * @param WP_REST_Request $request The request to require the plugin matches against.
  774      * @param array           $item    The plugin item.
  775      *
  776      * @return bool
  777      */
  778     protected function does_plugin_match_request( $request, $item ) {
  779         $search = $request['search'];
  780 
  781         if ( $search ) {
  782             $matched_search = false;
  783 
  784             foreach ( $item as $field ) {
  785                 if ( is_string( $field ) && false !== strpos( strip_tags( $field ), $search ) ) {
  786                     $matched_search = true;
  787                     break;
  788                 }
  789             }
  790 
  791             if ( ! $matched_search ) {
  792                 return false;
  793             }
  794         }
  795 
  796         $status = $request['status'];
  797 
  798         if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) {
  799             return false;
  800         }
  801 
  802         return true;
  803     }
  804 
  805     /**
  806      * Checks if the plugin is installed.
  807      *
  808      * @since 5.5.0
  809      *
  810      * @param string $plugin The plugin file.
  811      * @return bool
  812      */
  813     protected function is_plugin_installed( $plugin ) {
  814         return file_exists( WP_PLUGIN_DIR . '/' . $plugin );
  815     }
  816 
  817     /**
  818      * Determine if the endpoints are available.
  819      *
  820      * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
  821      *
  822      * @since 5.5.0
  823      *
  824      * @return true|WP_Error True if filesystem is available, WP_Error otherwise.
  825      */
  826     protected function is_filesystem_available() {
  827         $filesystem_method = get_filesystem_method();
  828 
  829         if ( 'direct' === $filesystem_method ) {
  830             return true;
  831         }
  832 
  833         ob_start();
  834         $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
  835         ob_end_clean();
  836 
  837         if ( $filesystem_credentials_are_stored ) {
  838             return true;
  839         }
  840 
  841         return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) );
  842     }
  843 
  844     /**
  845      * Retrieves the plugin's schema, conforming to JSON Schema.
  846      *
  847      * @since 5.5.0
  848      *
  849      * @return array Item schema data.
  850      */
  851     public function get_item_schema() {
  852         if ( $this->schema ) {
  853             return $this->add_additional_fields_schema( $this->schema );
  854         }
  855 
  856         $this->schema = array(
  857             '$schema'    => 'http://json-schema.org/draft-04/schema#',
  858             'title'      => 'plugin',
  859             'type'       => 'object',
  860             'properties' => array(
  861                 'plugin'       => array(
  862                     'description' => __( 'The plugin file.' ),
  863                     'type'        => 'string',
  864                     'pattern'     => self::PATTERN,
  865                     'readonly'    => true,
  866                     'context'     => array( 'view', 'edit', 'embed' ),
  867                 ),
  868                 'status'       => array(
  869                     'description' => __( 'The plugin activation status.' ),
  870                     'type'        => 'string',
  871                     'enum'        => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
  872                     'context'     => array( 'view', 'edit', 'embed' ),
  873                 ),
  874                 'name'         => array(
  875                     'description' => __( 'The plugin name.' ),
  876                     'type'        => 'string',
  877                     'readonly'    => true,
  878                     'context'     => array( 'view', 'edit', 'embed' ),
  879                 ),
  880                 'plugin_uri'   => array(
  881                     'description' => __( 'The plugin\'s website address.' ),
  882                     'type'        => 'string',
  883                     'format'      => 'uri',
  884                     'readonly'    => true,
  885                     'context'     => array( 'view', 'edit' ),
  886                 ),
  887                 'author'       => array(
  888                     'description' => __( 'The plugin author.' ),
  889                     'type'        => 'object',
  890                     'readonly'    => true,
  891                     'context'     => array( 'view', 'edit' ),
  892                 ),
  893                 'author_uri'   => array(
  894                     'description' => __( 'Plugin author\'s website address.' ),
  895                     'type'        => 'string',
  896                     'format'      => 'uri',
  897                     'readonly'    => true,
  898                     'context'     => array( 'view', 'edit' ),
  899                 ),
  900                 'description'  => array(
  901                     'description' => __( 'The plugin description.' ),
  902                     'type'        => 'object',
  903                     'readonly'    => true,
  904                     'context'     => array( 'view', 'edit' ),
  905                     'properties'  => array(
  906                         'raw'      => array(
  907                             'description' => __( 'The raw plugin description.' ),
  908                             'type'        => 'string',
  909                         ),
  910                         'rendered' => array(
  911                             'description' => __( 'The plugin description formatted for display.' ),
  912                             'type'        => 'string',
  913                         ),
  914                     ),
  915                 ),
  916                 'version'      => array(
  917                     'description' => __( 'The plugin version number.' ),
  918                     'type'        => 'string',
  919                     'readonly'    => true,
  920                     'context'     => array( 'view', 'edit' ),
  921                 ),
  922                 'network_only' => array(
  923                     'description' => __( 'Whether the plugin can only be activated network-wide.' ),
  924                     'type'        => 'boolean',
  925                     'readonly'    => true,
  926                     'context'     => array( 'view', 'edit', 'embed' ),
  927                 ),
  928                 'requires_wp'  => array(
  929                     'description' => __( 'Minimum required version of WordPress.' ),
  930                     'type'        => 'string',
  931                     'readonly'    => true,
  932                     'context'     => array( 'view', 'edit', 'embed' ),
  933                 ),
  934                 'requires_php' => array(
  935                     'description' => __( 'Minimum required version of PHP.' ),
  936                     'type'        => 'string',
  937                     'readonly'    => true,
  938                     'context'     => array( 'view', 'edit', 'embed' ),
  939                 ),
  940                 'textdomain'   => array(
  941                     'description' => __( 'The plugin\'s text domain.' ),
  942                     'type'        => 'string',
  943                     'readonly'    => true,
  944                     'context'     => array( 'view', 'edit' ),
  945                 ),
  946             ),
  947         );
  948 
  949         return $this->add_additional_fields_schema( $this->schema );
  950     }
  951 
  952     /**
  953      * Retrieves the query params for the collections.
  954      *
  955      * @since 5.5.0
  956      *
  957      * @return array Query parameters for the collection.
  958      */
  959     public function get_collection_params() {
  960         $query_params = parent::get_collection_params();
  961 
  962         $query_params['context']['default'] = 'view';
  963 
  964         $query_params['status'] = array(
  965             'description' => __( 'Limits results to plugins with the given status.' ),
  966             'type'        => 'array',
  967             'items'       => array(
  968                 'type' => 'string',
  969                 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
  970             ),
  971         );
  972 
  973         unset( $query_params['page'], $query_params['per_page'] );
  974 
  975         return $query_params;
  976     }
  977 }