namespace = 'wp/v2'; $this->rest_base = 'plugins'; } /** * Registers the routes for the plugins controller. * * @since 5.5.0 */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => array( 'slug' => array( 'type' => 'string', 'required' => true, 'description' => __( 'WordPress.org plugin directory slug.' ), 'pattern' => '[\w\-]+', ), 'status' => array( 'description' => __( 'The plugin activation status.' ), 'type' => 'string', 'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ), 'default' => 'inactive', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P' . self::PATTERN . ')', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'plugin' => array( 'type' => 'string', 'pattern' => self::PATTERN, 'validate_callback' => array( $this, 'validate_plugin_param' ), 'sanitize_callback' => array( $this, 'sanitize_plugin_param' ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to get plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_view_plugins', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a collection of plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugins = array(); foreach ( get_plugins() as $file => $data ) { if ( is_wp_error( $this->check_read_permission( $file ) ) ) { continue; } $data['_file'] = $file; if ( ! $this->does_plugin_match_request( $request, $data ) ) { continue; } $plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) ); } return new WP_REST_Response( $plugins ); } /** * Checks if a given request has access to get a specific plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_view_plugin', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } $can_read = $this->check_read_permission( $request['plugin'] ); if ( is_wp_error( $can_read ) ) { return $can_read; } return true; } /** * Retrieves one plugin from the site. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $data = $this->get_plugin_data( $request['plugin'] ); if ( is_wp_error( $data ) ) { return $data; } return $this->prepare_item_for_response( $data, $request ); } /** * Checks if the given plugin can be viewed by the current user. * * On multisite, this hides non-active network only plugins if the user does not have permission * to manage network plugins. * * @since 5.5.0 * * @param string $plugin The plugin file to check. * @return true|WP_Error True if can read, a WP_Error instance otherwise. */ protected function check_read_permission( $plugin ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( ! $this->is_plugin_installed( $plugin ) ) { return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) ); } if ( ! is_multisite() ) { return true; } if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) { return true; } return new WP_Error( 'rest_cannot_view_plugin', __( 'Sorry, you are not allowed to manage this plugin.' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Checks if a given request has access to upload plugins. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { if ( ! current_user_can( 'install_plugins' ) ) { return new WP_Error( 'rest_cannot_install_plugin', __( 'Sorry, you are not allowed to install plugins on this site.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_activate_plugin', __( 'Sorry, you are not allowed to activate plugins.' ), array( 'status' => rest_authorization_required_code(), ) ); } return true; } /** * Uploads a plugin and optionally activates it. * * @since 5.5.0 * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { global $wp_filesystem; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $slug = $request['slug']; // Verify filesystem is accessible first. $filesystem_available = $this->is_filesystem_available(); if ( is_wp_error( $filesystem_available ) ) { return $filesystem_available; } $api = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, 'language_packs' => true, ), ) ); if ( is_wp_error( $api ) ) { if ( str_contains( $api->get_error_message(), 'Plugin not found.' ) ) { $api->add_data( array( 'status' => 404 ) ); } else { $api->add_data( array( 'status' => 500 ) ); } return $api; } $skin = new WP_Ajax_Upgrader_Skin(); $upgrader = new Plugin_Upgrader( $skin ); $result = $upgrader->install( $api->download_link ); if ( is_wp_error( $result ) ) { $result->add_data( array( 'status' => 500 ) ); return $result; } // This should be the same as $result above. if ( is_wp_error( $skin->result ) ) { $skin->result->add_data( array( 'status' => 500 ) ); return $skin->result; } if ( $skin->get_errors()->has_errors() ) { $error = $skin->get_errors(); $error->add_data( array( 'status' => 500 ) ); return $error; } if ( is_null( $result ) ) { // Pass through the error from WP_Filesystem if one was raised. if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) { return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) ); } return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.' ), array( 'status' => 500 ) ); } $file = $upgrader->plugin_info(); if ( ! $file ) { return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.' ), array( 'status' => 500 ) ); } if ( 'inactive' !== $request['status'] ) { $can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' ); if ( is_wp_error( $can_change_status ) ) { return $can_change_status; } $changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' ); if ( is_wp_error( $changed_status ) ) { return $changed_status; } } // Install translations. $installed_locales = array_values( get_available_languages() ); /** This filter is documented in wp-includes/update.php */ $installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales ); $language_packs = array_map( static function ( $item ) { return (object) $item; }, $api->language_packs ); $language_packs = array_filter( $language_packs, static function ( $pack ) use ( $installed_locales ) { return in_array( $pack->language, $installed_locales, true ); } ); if ( $language_packs ) { $lp_upgrader = new Language_Pack_Upgrader( $skin ); // Install all applicable language packs for the plugin. $lp_upgrader->bulk_upgrade( $language_packs ); } $path = WP_PLUGIN_DIR . '/' . $file; $data = get_plugin_data( $path, false, false ); $data['_file'] = $file; $response = $this->prepare_item_for_response( $data, $request ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) ); return $response; } /** * Checks if a given request has access to update a specific plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( ! current_user_can( 'activate_plugins' ) ) { return new WP_Error( 'rest_cannot_manage_plugins', __( 'Sorry, you are not allowed to manage plugins for this site.' ), array( 'status' => rest_authorization_required_code() ) ); } $can_read = $this->check_read_permission( $request['plugin'] ); if ( is_wp_error( $can_read ) ) { return $can_read; } $status = $this->get_plugin_status( $request['plugin'] ); if ( $request['status'] && $status !== $request['status'] ) { $can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status ); if ( is_wp_error( $can_change_status ) ) { return $can_change_status; } } return true; } /** * Updates one plugin. * * @since 5.5.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; $data = $this->get_plugin_data( $request['plugin'] ); if ( is_wp_error( $data ) ) { return $data; } $status = $this->get_plugin_status( $request['plugin'] ); if ( $request['status'] && $status !== $request['status'] ) { $handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status ); if ( is_wp_error( $handled ) ) { return $handled; } } $this->update_additional_fields_for_object( $data, $request ); $request['context'] = 'edit'; return $this->prepare_item_for_response( $data, $request ); } /** * Checks if a given request has access to delete a specific Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException::class, Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::class => Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi::class, Automattic\WooCommerce\StoreApi\SchemaController::class => Automattic\WooCommerce\Blocks\StoreApi\SchemaController::class, Automattic\WooCommerce\StoreApi\RoutesController::class => Automattic\WooCommerce\Blocks\StoreApi\RoutesController::class, Automattic\WooCommerce\StoreApi\Formatters::class => Automattic\WooCommerce\Blocks\StoreApi\Formatters::class, Automattic\WooCommerce\StoreApi\Payments\PaymentResult::class => Automattic\WooCommerce\Blocks\Payments\PaymentResult::class, Automattic\WooCommerce\StoreApi\Payments\PaymentContext::class => Automattic\WooCommerce\Blocks\Payments\PaymentContext::class, // Old schemas to V1 schemas under new namespace. Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractAddressSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractAddressSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\BillingAddressSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\BillingAddressSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartCouponSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartCouponSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartExtensionsSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartExtensionsSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartFeeSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartFeeSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartItemSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CartShippingRateSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartShippingRateSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ErrorSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ErrorSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ImageAttachmentSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ImageAttachmentSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\OrderCouponSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\OrderCouponSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ProductAttributeSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductAttributeSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ProductCategorySchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCategorySchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ProductCollectionDataSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCollectionDataSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ProductReviewSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductReviewSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ProductSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\ShippingAddressSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema::class, Automattic\WooCommerce\StoreApi\Schemas\V1\TermSchema::class => Automattic\WooCommerce\Blocks\StoreApi\Schemas\TermSchema::class, // Old routes to V1 routes under new namespace. Automattic\WooCommerce\StoreApi\Routes\V1\AbstractCartRoute::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\AbstractCartRoute::class, Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\AbstractRoute::class, Automattic\WooCommerce\StoreApi\Routes\V1\AbstractTermsRoute::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\AbstractTermsRoute::class, Automattic\WooCommerce\StoreApi\Routes\V1\Batch::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\Batch::class, Automattic\WooCommerce\StoreApi\Routes\V1\Cart::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\Cart::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartAddItem::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartAddItem::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartApplyCoupon::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartApplyCoupon::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartCoupons::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartCoupons::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartCouponsByCode::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartCouponsByCode::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartExtensions::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartExtensions::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartItems::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartItems::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartItemsByKey::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartItemsByKey::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartRemoveCoupon::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartRemoveCoupon::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartRemoveItem::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartRemoveItem::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartSelectShippingRate::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartSelectShippingRate::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartUpdateCustomer::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartUpdateCustomer::class, Automattic\WooCommerce\StoreApi\Routes\V1\CartUpdateItem::class => Automattic\WooCommerce\Blocks\StoreApi\Routes\CartUpdateItem::class, Automattic\W