* REST API Plugins Controller
* Handles requests to install and activate dependent plugins.
namespace Automattic\WooCommerce\Admin\API;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\PluginsHelper;
defined( 'ABSPATH' ) || exit;
* @extends \WC_REST_Data_Controller
class Plugins extends \WC_REST_Data_Controller {
protected $namespace = 'wc-admin';
protected $rest_base = 'plugins';
public function register_routes() {
'/' . $this->rest_base . '/install',
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'install_plugins' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/install/status',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_installation_status' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/install/status/(?P<job_id>[a-z0-9_\-]+)',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_job_installation_status' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/active',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'active_plugins' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/installed',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'installed_plugins' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/activate',
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'activate_plugins' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/activate/status',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_activation_status' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/activate/status/(?P<job_id>[a-z0-9_\-]+)',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_job_activation_status' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_item_schema' ),
'/' . $this->rest_base . '/connect-jetpack',
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'connect_jetpack' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_connect_schema' ),
'/' . $this->rest_base . '/request-wccom-connect',
'callback' => array( $this, 'request_wccom_connect' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_connect_schema' ),
'/' . $this->rest_base . '/finish-wccom-connect',
'callback' => array( $this, 'finish_wccom_connect' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_connect_schema' ),
'/' . $this->rest_base . '/connect-wcpay',
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connect_wcpay' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_connect_schema' ),
'/' . $this->rest_base . '/connect-square',
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'connect_square' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'schema' => array( $this, 'get_connect_schema' ),
* Check if a given request has access to manage plugins.
* @param \WP_REST_Request $request Full details about the request.
* @return \WP_Error|boolean
public function update_item_permissions_check( $request ) {
if ( ! current_user_can( 'install_plugins' ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot manage plugins.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
* Install the requested plugin.
* @param \WP_REST_Request $request Full details about the request.
* @return \WP_Error|array Plugin Status
public function install_plugin( $request ) {
wc_deprecated_function( 'install_plugin', '4.3', '\Automattic\WooCommerce\Admin\API\Plugins()->install_plugins' );
// This method expects a `plugin` argument to be sent, install plugins requires plugins.
$request['plugins'] = $request['plugin'];
return self::install_plugins( $request );
* Installs the requested plugins.
* @param \WP_REST_Request $request Full details about the request.
* @return \WP_Error|array Plugin Status
public function install_plugins( $request ) {
$plugins = explode( ',', $request['plugins'] );
$source = ! empty( $request['source'] ) ? $request['source'] : null;
if ( empty( $request['plugins'] ) || ! is_array( $plugins ) ) {
return new \WP_Error( 'woocommerce_rest_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ), 404 );
if ( isset( $request['async'] ) && $request['async'] ) {
$job_id = PluginsHelper::schedule_install_plugins( $plugins );
'message' => __( 'Plugin installation has been scheduled.', 'woocommerce' ),
$data = PluginsHelper::install_plugins( $plugins, null, $source );
// Gather some plugin details for each installed plugin.
$plugin_details = array();
if ( is_array( $data['installed'] ) ) {
foreach ( $data['installed'] as $plugin_slug ) {
$plugin_data = PluginsHelper::get_plugin_data( $plugin_slug );
if ( empty( $plugin_data ) ) {
$plugin_details[ $plugin_slug ] = array(
'name' => $plugin_data['Name'],
'description' => $plugin_data['Description'],
'uri' => $plugin_data['PluginURI'],
'version' => $plugin_data['Version'],
'installed' => $data['installed'],
'results' => $data['results'],
'install_time' => $data['time'],
'plugin_details' => $plugin_details,
'errors' => $data['errors'],
'success' => count( $data['errors']->errors ) === 0,
'message' => count( $data['errors']->errors ) === 0
? __( 'Plugins were successfully installed.', 'woocommerce' )
: __( 'There was a problem installing some of the requested plugins.', 'woocommerce' ),
* Returns a list of recently scheduled installation jobs.
* @param \WP_REST_Request $request Full details about the request.
public function get_installation_status( $request ) {
return PluginsHelper::get_installation_status();
* Returns a list of recently scheduled installation jobs.
* @param \WP_REST_Request $request Full details about the request.
public function get_job_installation_status( $request ) {
$job_id = $request->get_param( 'job_id' );
$jobs = PluginsHelper::get_installation_status( $job_id );
* Returns a list of active plugins in API format.
* @return array Active plugins
public static function active_plugins() {
'plugins' => array_values( PluginsHelper::get_active_plugin_slugs() ),
* Returns a list of active plugins.
* @return array Active plugins
public static function get_active_plugins() {
$data = self::active_plugins();
* Returns a list of installed plugins.
* @return array Installed plugins
public function installed_plugins() {
'plugins' => PluginsHelper::get_installed_plugin_slugs(),
* Activate the requested plugin.
* @param \WP_REST_Request $request Full details about the request.
* @return \WP_Error|array Plugin Status
public function activate_plugins( $request ) {
$plugins = explode( ',', $request['plugins'] );
if ( empty( $request['plugins'] ) || ! is_array( $plugins ) ) {
return new \WP_Error( 'woocommerce_rest_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ), 404 );
if ( isset( $request['async'] ) && $request['async'] ) {
$job_id = PluginsHelper::schedule_activate_plugins( $plugins );
'message' => __( 'Plugin activation has been scheduled.', 'woocommerce' ),
$data = PluginsHelper::activate_plugins( $plugins );
// Gather some plugin details for each activated plugin.
$plugin_details = array();
if ( is_array( $data['activated'] ) ) {
foreach ( $data['activated'] as $plugin_slug ) {
$plugin_data = PluginsHelper::get_plugin_data( $plugin_slug );
if ( empty( $plugin_data ) ) {
$plugin_details[ $plugin_slug ] = array(
'name' => $plugin_data['Name'],
'description' => $plugin_data['Description'],
'uri' => $plugin_data['PluginURI'],
'version' => $plugin_data['Version'],
'activated' => $data['activated'],
'active' => $data['active'],
'plugin_details' => $plugin_details,
'errors' => $data['errors'],
'success' => count( $data['errors']->errors ) === 0,
'message' => count( $data['errors']->errors ) === 0
? __( 'Plugins were successfully activated.', 'woocommerce' )
: __( 'There was a problem activating some of the requested plugins.', 'woocommerce' ),
* Returns a list of recently scheduled activation jobs.
* @param \WP_REST_Request $request Full details about the request.
public function get_activation_status( $request ) {
return PluginsHelper::get_activation_status();
* Returns a list of recently scheduled activation jobs.
* @param \WP_REST_Request $request Full details about the request.
public function get_job_activation_status( $request ) {
$job_id = $request->get_param( 'job_id' );
$jobs = PluginsHelper::get_activation_status( $job_id );
* Generates a Jetpack Connect URL.
* @param \WP_REST_Request $request Full details about the request.
* @return \WP_Error|array Connection URL for Jetpack
public function connect_jetpack( $request ) {
if ( ! class_exists( '\Jetpack' ) ) {
return new \WP_Error( 'woocommerce_rest_jetpack_not_active', __( 'Jetpack is not installed or active.', 'woocommerce' ), 404 );
// phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment
$redirect_url = apply_filters( 'woocommerce_admin_onboarding_jetpack_connect_redirect_url', esc_url_raw( $request['redirect_url'] ) );
$connect_url = \Jetpack::init()->build_connect_url( true, $redirect_url, 'woocommerce-onboarding' );
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
$connect_url = add_query_arg( array( 'calypso_env' => $calypso_env ), $connect_url );
'name' => __( 'Jetpack', 'woocommerce' ),
'connectAction' => $connect_url,
* Kicks off the WCCOM Connect process.
* @return \WP_Error|array Connection URL for WooCommerce.com
public function request_wccom_connect() {
include_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-api.php';
if ( ! class_exists( 'WC_Helper_API' ) ) {
return new \WP_Error( 'woocommerce_rest_helper_not_active', __( 'There was an error loading the WooCommerce.com Helper API.', 'woocommerce' ), 404 );
$redirect_uri = wc_admin_url( '&task=connect&wccom-connected=1' );
$request = \WC_Helper_API::post(
'home_url' => home_url(),
'redirect_uri' => $redirect_uri,
$code = wp_remote_retrieve_response_code( $request );
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
$secret = json_decode( wp_remote_retrieve_body( $request ) );
if ( empty( $secret ) ) {
return new \WP_Error( 'woocommerce_rest_helper_connect', __( 'There was an error connecting to WooCommerce.com. Please try again.', 'woocommerce' ), 500 );
do_action( 'woocommerce_helper_connect_start' );
$connect_url = add_query_arg(
'home_url' => rawurlencode( home_url() ),
'redirect_uri' => rawurlencode( $redirect_uri ),
'secret' => rawurlencode( $secret ),
'wccom-from' => 'onboarding',
\WC_Helper_API::url( 'oauth/authorize' )