Add onboarding/plugins REST endpoint (#38174)
* Remove unused $job_id argument * Support WP_Upgrader argument -- helps for testing * Minor refactor to support a custom logger in install_plugins * Support a custom logger in install_plugins * Support swapping out WP_Upgrader impl. * Added woocommerce_plugins_install_async_callback callback to call install_plugins with a custom logger from action scheduler * Add a new REST endpoint for onboarding plugins * POST /wc-admin/onboarding/plugins/install-async -- queues plugin installs to action scheudler * GET /wc-admin/onboarding/plugins/scheduled-installs/:job_id -- returns current status of given job id * Remove WP_Upgrader support -- not using it * Add changelog * Fix phpcs errors * Call complete * Update test to wait 1 sec before checking the action scheduler * Remove test that requires waiting an async action * Add test for 404 status * Add status field * Remove woocommerce_plugins_install_async_callback action -- no longer used * Call run-in-background endpoint with blocking=false to simulate a background process * Update plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsynPluginsInstallLogger.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update plugins/woocommerce/src/Admin/PluginsInstallLoggers/PluginsInstallLogger.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update plugins/woocommerce/tests/php/src/Admin/API/OnboardingPluginsTest.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update plugins/woocommerce/tests/php/src/Admin/API/OnboardingPluginsTest.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update plugins/woocommerce/tests/php/src/Admin/API/OnboardingPluginsTest.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update plugins/woocommerce/src/Admin/PluginsInstallLoggers/PluginsInstallLogger.php Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Fix incorrect logger name * Add max_execution_time setting * Remove test code * Skip failing test -- hard to test as the function uses site url and test env does not actaully have a running WP * code format * Revert background process changes * Add install and activate endpoint * Update plugins/woocommerce/src/Admin/PluginsInstallLoggers/AsynPluginsInstallLogger.php Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com> * Update plugins/woocommerce/src/Admin/API/OnboardingPlugins.php Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com> * Require activate_plugins permission * Add test to check for permissions * Update install and activate schema * Fix: import AsyncPluginsInstallLogger correctly * Fix: add missing comments --------- Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> Co-authored-by: Ilyas Foo <foo.ilyas@gmail.com>
This commit is contained in:
parent
600b19c6f8
commit
baf7714dfa
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Add new REST endpoints at onboarding/plugins to support async plugin installation with real time error tracking.
|
|
@ -87,6 +87,7 @@ class Init {
|
|||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\Taxes',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Onboarding Profile Controller
|
||||
*
|
||||
* Handles requests to /onboarding/profile
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use ActionScheduler;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsynPluginsInstallLogger;
|
||||
use WC_REST_Data_Controller;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Onboarding Plugins controller.
|
||||
*
|
||||
* @internal
|
||||
* @extends WC_REST_Data_Controller
|
||||
*/
|
||||
class OnboardingPlugins extends WC_REST_Data_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'onboarding/plugins';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/install-async',
|
||||
array(
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'install_async' ),
|
||||
'permission_callback' => array( $this, 'can_install_plugins' ),
|
||||
'args' => array(
|
||||
'plugins' => array(
|
||||
'description' => 'A list of plugins to install',
|
||||
'type' => 'array',
|
||||
'items' => 'string',
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return array_map(
|
||||
function ( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
$value
|
||||
);
|
||||
},
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_install_async_schema' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/install-and-activate',
|
||||
array(
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'install_and_activate' ),
|
||||
'permission_callback' => array( $this, 'can_install_and_activate_plugins' ),
|
||||
|
||||
),
|
||||
'schema' => array( $this, 'get_install_activate_schema' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/scheduled-installs/(?P<job_id>\w+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_scheduled_installs' ),
|
||||
'permission_callback' => array( $this, 'can_install_plugins' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_install_async_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install and activate a plugin.
|
||||
*
|
||||
* @param WP_REST_Request $request WP Request object.
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function install_and_activate( WP_REST_Request $request ) {
|
||||
$response = array();
|
||||
$response['install'] = PluginsHelper::install_plugins( $request->get_param( 'plugins' ) );
|
||||
$response['activate'] = PluginsHelper::activate_plugins( $response['install']['installed'] );
|
||||
|
||||
return new WP_REST_Response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue plugin install request.
|
||||
*
|
||||
* @param WP_REST_Request $request WP_REST_Request object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function install_async( WP_REST_Request $request ) {
|
||||
$plugins = $request->get_param( 'plugins' );
|
||||
$job_id = uniqid();
|
||||
|
||||
WC()->queue()->add( 'woocommerce_plugins_install_async_callback', array( $plugins, $job_id ) );
|
||||
|
||||
$plugin_status = array();
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$plugin_status[ $plugin ] = array(
|
||||
'status' => 'pending',
|
||||
'errors' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'job_id' => $job_id,
|
||||
'status' => 'pending',
|
||||
'plugins' => $plugin_status,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current status of given job.
|
||||
*
|
||||
* @param WP_REST_Request $request WP_REST_Request object.
|
||||
*
|
||||
* @return array|WP_REST_Response
|
||||
*/
|
||||
public function get_scheduled_installs( WP_REST_Request $request ) {
|
||||
$job_id = $request->get_param( 'job_id' );
|
||||
|
||||
$actions = WC()->queue()->search(
|
||||
array(
|
||||
'hook' => 'woocommerce_plugins_install_async_callback',
|
||||
'search' => $job_id,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
$actions = array_filter(
|
||||
PluginsHelper::get_action_data( $actions ),
|
||||
function( $action ) use ( $job_id ) {
|
||||
return $action['job_id'] === $job_id;
|
||||
}
|
||||
);
|
||||
|
||||
if ( empty( $actions ) ) {
|
||||
return new WP_REST_Response( null, 404 );
|
||||
}
|
||||
|
||||
$response = array(
|
||||
'job_id' => $actions[0]['job_id'],
|
||||
'status' => $actions[0]['status'],
|
||||
);
|
||||
|
||||
$option = get_option( 'woocommerce_onboarding_plugins_install_async_' . $job_id );
|
||||
if ( isset( $option['plugins'] ) ) {
|
||||
$response['plugins'] = $option['plugins'];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user has permission to install plugins
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function can_install_plugins() {
|
||||
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() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user has permission to install and activate plugins
|
||||
*
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function can_install_and_activate_plugins() {
|
||||
if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'woocommerce_rest_cannot_update',
|
||||
__( 'Sorry, you cannot manage plugins.', 'woocommerce' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON Schema for both install-async and scheduled-installs endpoints.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_install_async_schema() {
|
||||
return array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'Install Async Schema',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'job_id' => 'integer',
|
||||
'status' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'pending', 'complete', 'failed' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON Schema for install-and-activate endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_install_activate_schema() {
|
||||
$error_schema = array(
|
||||
'type' => 'object',
|
||||
'patternProperties' => array(
|
||||
'^.*$' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
|
||||
$install_schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'installed' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'results' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'errors' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'errors' => $error_schema,
|
||||
'error_data' => $error_schema,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$activate_schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'activated' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'active' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'errors' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'errors' => $error_schema,
|
||||
'error_data' => $error_schema,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'Install and Activate Schema',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'install' => $install_schema,
|
||||
'activate' => $activate_schema,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,16 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Admin;
|
||||
|
||||
use ActionScheduler;
|
||||
use ActionScheduler_DBStore;
|
||||
use ActionScheduler_QueueRunner;
|
||||
use Automatic_Upgrader_Skin;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsyncPluginsInstallLogger;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\PluginsInstallLogger;
|
||||
use Plugin_Upgrader;
|
||||
use WP_Error;
|
||||
use WP_Upgrader;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
|
@ -23,6 +33,7 @@ class PluginsHelper {
|
|||
*/
|
||||
public static function init() {
|
||||
add_action( 'woocommerce_plugins_install_callback', array( __CLASS__, 'install_plugins' ), 10, 2 );
|
||||
add_action( 'woocommerce_plugins_install_async_callback', array( __CLASS__, 'install_plugins_async_callback' ), 10, 2 );
|
||||
add_action( 'woocommerce_plugins_activate_callback', array( __CLASS__, 'activate_plugins' ), 10, 2 );
|
||||
}
|
||||
|
||||
|
@ -60,8 +71,9 @@ class PluginsHelper {
|
|||
*/
|
||||
public static function get_installed_plugin_slugs() {
|
||||
return array_map(
|
||||
function( $plugin_path ) {
|
||||
function ( $plugin_path ) {
|
||||
$path_parts = explode( '/', $plugin_path );
|
||||
|
||||
return $path_parts[0];
|
||||
},
|
||||
array_keys( get_plugins() )
|
||||
|
@ -93,8 +105,9 @@ class PluginsHelper {
|
|||
*/
|
||||
public static function get_active_plugin_slugs() {
|
||||
return array_map(
|
||||
function( $plugin_path ) {
|
||||
function ( $plugin_path ) {
|
||||
$path_parts = explode( '/', $plugin_path );
|
||||
|
||||
return $path_parts[0];
|
||||
},
|
||||
get_option( 'active_plugins', array() )
|
||||
|
@ -110,6 +123,7 @@ class PluginsHelper {
|
|||
*/
|
||||
public static function is_plugin_installed( $plugin ) {
|
||||
$plugin_path = self::get_plugin_path_from_slug( $plugin );
|
||||
|
||||
return $plugin_path ? array_key_exists( $plugin_path, get_plugins() ) : false;
|
||||
}
|
||||
|
||||
|
@ -122,6 +136,7 @@ class PluginsHelper {
|
|||
*/
|
||||
public static function is_plugin_active( $plugin ) {
|
||||
$plugin_path = self::get_plugin_path_from_slug( $plugin );
|
||||
|
||||
return $plugin_path ? in_array( $plugin_path, get_option( 'active_plugins', array() ), true ) : false;
|
||||
}
|
||||
|
||||
|
@ -142,20 +157,26 @@ class PluginsHelper {
|
|||
/**
|
||||
* Install an array of plugins.
|
||||
*
|
||||
* @param array $plugins Plugins to install.
|
||||
* @param array $plugins Plugins to install.
|
||||
* @param PluginsInstallLogger|null $logger an optional logger.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function install_plugins( $plugins ) {
|
||||
public static function install_plugins( $plugins, PluginsInstallLogger $logger = null ) {
|
||||
/**
|
||||
* Filter the list of plugins to install.
|
||||
*
|
||||
* @param array $plugins A list of the plugins to install.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
$plugins = apply_filters( 'woocommerce_admin_plugins_pre_install', $plugins );
|
||||
|
||||
if ( empty( $plugins ) || ! is_array( $plugins ) ) {
|
||||
return new \WP_Error( 'woocommerce_plugins_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ) );
|
||||
return new WP_Error(
|
||||
'woocommerce_plugins_invalid_plugins',
|
||||
__( 'Plugins must be a non-empty array.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
@ -169,13 +190,15 @@ class PluginsHelper {
|
|||
$installed_plugins = array();
|
||||
$results = array();
|
||||
$time = array();
|
||||
$errors = new \WP_Error();
|
||||
$errors = new WP_Error();
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$slug = sanitize_key( $plugin );
|
||||
$logger && $logger->install_requested( $plugin );
|
||||
|
||||
if ( isset( $existing_plugins[ $slug ] ) ) {
|
||||
$installed_plugins[] = $plugin;
|
||||
$logger && $logger->installed( $plugin, 0 );
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -193,8 +216,14 @@ class PluginsHelper {
|
|||
|
||||
if ( is_wp_error( $api ) ) {
|
||||
$properties = array(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
'error_message' => sprintf( __( 'The requested plugin `%s` could not be installed. Plugin API call failed.', 'woocommerce' ), $slug ),
|
||||
'error_message' => sprintf(
|
||||
// translators: %s: plugin slug (example: woocommerce-services).
|
||||
__(
|
||||
'The requested plugin `%s` could not be installed. Plugin API call failed.',
|
||||
'woocommerce'
|
||||
),
|
||||
$slug
|
||||
),
|
||||
'api_error_message' => $api->get_error_message(),
|
||||
'slug' => $slug,
|
||||
);
|
||||
|
@ -204,32 +233,40 @@ class PluginsHelper {
|
|||
* Action triggered when a plugin API call failed.
|
||||
*
|
||||
* @param string $slug The plugin slug.
|
||||
* @param \WP_Error $api The API response.
|
||||
* @param WP_Error $api The API response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_plugins_install_api_error', $slug, $api );
|
||||
|
||||
$errors->add(
|
||||
$plugin,
|
||||
sprintf(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
__( 'The requested plugin `%s` could not be installed. Plugin API call failed.', 'woocommerce' ),
|
||||
$slug
|
||||
)
|
||||
$error_message = sprintf(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
__( 'The requested plugin `%s` could not be installed. Plugin API call failed.', 'woocommerce' ),
|
||||
$slug
|
||||
);
|
||||
|
||||
$errors->add( $plugin, $error_message );
|
||||
$logger && $logger->add_error( $plugin, $error_message );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() );
|
||||
$result = $upgrader->install( $api->download_link );
|
||||
$upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
|
||||
$result = $upgrader->install( $api->download_link );
|
||||
// result can be false or WP_Error.
|
||||
$results[ $plugin ] = $result;
|
||||
$time[ $plugin ] = round( ( microtime( true ) - $start_time ) * 1000 );
|
||||
|
||||
if ( is_wp_error( $result ) || is_null( $result ) ) {
|
||||
$properties = array(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
'error_message' => sprintf( __( 'The requested plugin `%s` could not be installed.', 'woocommerce' ), $slug ),
|
||||
'error_message' => sprintf(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
__(
|
||||
'The requested plugin `%s` could not be installed.',
|
||||
'woocommerce'
|
||||
),
|
||||
$slug
|
||||
),
|
||||
'slug' => $slug,
|
||||
'api_version' => $api->version,
|
||||
'api_download_link' => $api->download_link,
|
||||
|
@ -243,26 +280,33 @@ class PluginsHelper {
|
|||
*
|
||||
* @param string $slug The plugin slug.
|
||||
* @param object $api The plugin API object.
|
||||
* @param \WP_Error|null $result The result of the plugin installation.
|
||||
* @param \Plugin_Upgrader $upgrader The plugin upgrader.
|
||||
* @param WP_Error|null $result The result of the plugin installation.
|
||||
* @param Plugin_Upgrader $upgrader The plugin upgrader.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
*/
|
||||
do_action( 'woocommerce_plugins_install_error', $slug, $api, $result, $upgrader );
|
||||
|
||||
$install_error_message = sprintf(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
__( 'The requested plugin `%s` could not be installed. Upgrader install failed.', 'woocommerce' ),
|
||||
$slug
|
||||
);
|
||||
$errors->add(
|
||||
$plugin,
|
||||
sprintf(
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
__( 'The requested plugin `%s` could not be installed. Upgrader install failed.', 'woocommerce' ),
|
||||
$slug
|
||||
)
|
||||
$install_error_message
|
||||
);
|
||||
$logger && $logger->add_error( $plugin, $install_error_message );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$installed_plugins[] = $plugin;
|
||||
$logger && $logger->installed( $plugin, $time[ $plugin ] );
|
||||
}
|
||||
|
||||
$logger && $logger->complete();
|
||||
|
||||
$data = array(
|
||||
'installed' => $installed_plugins,
|
||||
'results' => $results,
|
||||
|
@ -273,19 +317,40 @@ class PluginsHelper {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback regsitered by OnboardingPlugins::install_async.
|
||||
*
|
||||
* It is used to call install_plugins with a custom logger.
|
||||
*
|
||||
* @param array $plugins A list of plugins to install.
|
||||
* @param string $job_id An unique job I.D.
|
||||
* @return bool
|
||||
*/
|
||||
public function install_plugins_async_callback( array $plugins, string $job_id ) {
|
||||
$option_name = 'woocommerce_onboarding_plugins_install_async_' . $job_id;
|
||||
$logger = new AsyncPluginsInstallLogger( $option_name );
|
||||
self::install_plugins( $plugins, $logger );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule plugin installation.
|
||||
*
|
||||
* @param array $plugins Plugins to install.
|
||||
*
|
||||
* @return string Job ID.
|
||||
*/
|
||||
public static function schedule_install_plugins( $plugins ) {
|
||||
if ( empty( $plugins ) || ! is_array( $plugins ) ) {
|
||||
return new \WP_Error( 'woocommerce_plugins_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ), 404 );
|
||||
return new WP_Error(
|
||||
'woocommerce_plugins_invalid_plugins',
|
||||
__( 'Plugins must be a non-empty array.', 'woocommerce' ),
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
$job_id = uniqid();
|
||||
WC()->queue()->schedule_single( time() + 5, 'woocommerce_plugins_install_callback', array( $plugins, $job_id ) );
|
||||
WC()->queue()->schedule_single( time() + 5, 'woocommerce_plugins_install_callback', array( $plugins ) );
|
||||
|
||||
return $job_id;
|
||||
}
|
||||
|
@ -294,11 +359,16 @@ class PluginsHelper {
|
|||
* Activate the requested plugins.
|
||||
*
|
||||
* @param array $plugins Plugins.
|
||||
*
|
||||
* @return WP_Error|array Plugin Status
|
||||
*/
|
||||
public static function activate_plugins( $plugins ) {
|
||||
if ( empty( $plugins ) || ! is_array( $plugins ) ) {
|
||||
return new \WP_Error( 'woocommerce_plugins_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ), 404 );
|
||||
return new WP_Error(
|
||||
'woocommerce_plugins_invalid_plugins',
|
||||
__( 'Plugins must be a non-empty array.', 'woocommerce' ),
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
@ -310,12 +380,13 @@ class PluginsHelper {
|
|||
* Filter the list of plugins to activate.
|
||||
*
|
||||
* @param array $plugins A list of the plugins to activate.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
$plugins = apply_filters( 'woocommerce_admin_plugins_pre_activate', $plugins );
|
||||
|
||||
$plugin_paths = self::get_installed_plugins_paths();
|
||||
$errors = new \WP_Error();
|
||||
$errors = new WP_Error();
|
||||
$activated_plugins = array();
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
|
@ -337,7 +408,8 @@ class PluginsHelper {
|
|||
* Action triggered when a plugin activation fails.
|
||||
*
|
||||
* @param string $slug The plugin slug.
|
||||
* @param null|\WP_Error $result The result of the plugin activation.
|
||||
* @param null|WP_Error $result The result of the plugin activation.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
do_action( 'woocommerce_plugins_activate_error', $slug, $result );
|
||||
|
@ -366,15 +438,24 @@ class PluginsHelper {
|
|||
* Schedule plugin activation.
|
||||
*
|
||||
* @param array $plugins Plugins to activate.
|
||||
*
|
||||
* @return string Job ID.
|
||||
*/
|
||||
public static function schedule_activate_plugins( $plugins ) {
|
||||
if ( empty( $plugins ) || ! is_array( $plugins ) ) {
|
||||
return new \WP_Error( 'woocommerce_plugins_invalid_plugins', __( 'Plugins must be a non-empty array.', 'woocommerce' ), 404 );
|
||||
return new WP_Error(
|
||||
'woocommerce_plugins_invalid_plugins',
|
||||
__( 'Plugins must be a non-empty array.', 'woocommerce' ),
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
$job_id = uniqid();
|
||||
WC()->queue()->schedule_single( time() + 5, 'woocommerce_plugins_activate_callback', array( $plugins, $job_id ) );
|
||||
WC()->queue()->schedule_single(
|
||||
time() + 5,
|
||||
'woocommerce_plugins_activate_callback',
|
||||
array( $plugins, $job_id )
|
||||
);
|
||||
|
||||
return $job_id;
|
||||
}
|
||||
|
@ -383,6 +464,7 @@ class PluginsHelper {
|
|||
* Installation status.
|
||||
*
|
||||
* @param int $job_id Job ID.
|
||||
*
|
||||
* @return array Job data.
|
||||
*/
|
||||
public static function get_installation_status( $job_id = null ) {
|
||||
|
@ -402,14 +484,14 @@ class PluginsHelper {
|
|||
* Gets the plugin data for the first action.
|
||||
*
|
||||
* @param array $actions Array of AS actions.
|
||||
*
|
||||
* @return array Array of action data.
|
||||
*/
|
||||
public static function get_action_data( $actions ) {
|
||||
$data = [];
|
||||
$data = array();
|
||||
|
||||
foreach ( $actions as $action_id => $action ) {
|
||||
$store = new \ActionScheduler_DBStore();
|
||||
$status = $store->get_status( $action_id );
|
||||
$store = new ActionScheduler_DBStore();
|
||||
$args = $action->get_args();
|
||||
$data[] = array(
|
||||
'job_id' => $args[1],
|
||||
|
@ -425,6 +507,7 @@ class PluginsHelper {
|
|||
* Activation status.
|
||||
*
|
||||
* @param int $job_id Job ID.
|
||||
*
|
||||
* @return array Array of action data.
|
||||
*/
|
||||
public static function get_activation_status( $job_id = null ) {
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\PluginsInstallLoggers;
|
||||
|
||||
/**
|
||||
* A logger to log plugin installation progress in real time to an option.
|
||||
*/
|
||||
class AsyncPluginsInstallLogger implements PluginsInstallLogger {
|
||||
|
||||
/**
|
||||
* Variable to store logs.
|
||||
*
|
||||
* @var string $option_name option name to store logs.
|
||||
*/
|
||||
private $option_name;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $option_name option name.
|
||||
*/
|
||||
public function __construct( string $option_name ) {
|
||||
$this->option_name = $option_name;
|
||||
add_option(
|
||||
$this->option_name,
|
||||
array(
|
||||
'created_time' => time(),
|
||||
'status' => 'pending',
|
||||
'plugins' => array(),
|
||||
),
|
||||
'',
|
||||
'no'
|
||||
);
|
||||
|
||||
// Set status as failed in case we run out of exectuion time.
|
||||
register_shutdown_function(
|
||||
function () {
|
||||
$error = error_get_last();
|
||||
if ( isset( $error['type'] ) && E_ERROR === $error['type'] ) {
|
||||
$option = $this->get();
|
||||
$option['status'] = 'failed';
|
||||
$this->update( $option );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the option.
|
||||
*
|
||||
* @param array $data New data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function update( array $data ) {
|
||||
return update_option( $this->option_name, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive the option.
|
||||
*
|
||||
* @return false|mixed|void
|
||||
*/
|
||||
private function get() {
|
||||
return get_option( $this->option_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add requested plugin.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function install_requested( string $plugin_name ) {
|
||||
$option = $this->get();
|
||||
if ( ! isset( $option['plugins'][ $plugin_name ] ) ) {
|
||||
$option['plugins'][ $plugin_name ] = array(
|
||||
'status' => 'installing',
|
||||
'errors' => array(),
|
||||
'install_duration' => 0,
|
||||
);
|
||||
}
|
||||
$this->update( $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add installed plugin.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
* @param int $duration time took to install plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function installed( string $plugin_name, int $duration ) {
|
||||
$option = $this->get();
|
||||
|
||||
$option['plugins'][ $plugin_name ]['status'] = 'installed';
|
||||
$option['plugins'][ $plugin_name ]['install_duration'] = $duration;
|
||||
$this->update( $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
* @param string|null $error_message error message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_error( string $plugin_name, string $error_message = null ) {
|
||||
$option = $this->get();
|
||||
|
||||
$option['plugins'][ $plugin_name ]['errors'][] = $error_message;
|
||||
$option['plugins'][ $plugin_name ]['status'] = 'failed';
|
||||
$option['status'] = 'failed';
|
||||
|
||||
$this->update( $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Record completed_time.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function complete() {
|
||||
$option = $this->get();
|
||||
|
||||
$option['complete_time'] = time();
|
||||
$option['status'] = 'complete';
|
||||
|
||||
$this->update( $option );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\PluginsInstallLoggers;
|
||||
|
||||
/**
|
||||
* A logger used in PluginsHelper::install_plugins to log the installation progress.
|
||||
*/
|
||||
interface PluginsInstallLogger {
|
||||
|
||||
/**
|
||||
* Called when a plugin install requested.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
* @return mixed
|
||||
*/
|
||||
public function install_requested( string $plugin_name );
|
||||
|
||||
/**
|
||||
* Called when a plugin installed successfully.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
* @param int $duration # of seconds it took to install $plugin_name.
|
||||
* @return mixed
|
||||
*/
|
||||
public function installed( string $plugin_name, int $duration);
|
||||
|
||||
/**
|
||||
* Called when an error occurred while installing a plugin.
|
||||
*
|
||||
* @param string $plugin_name plugin name.
|
||||
* @param string|null $error_message error message.
|
||||
* @return mixed
|
||||
*/
|
||||
public function add_error( string $plugin_name, string $error_message = null);
|
||||
|
||||
/**
|
||||
* Called when all plugins are processed.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function complete();
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* Test the API controller class that handles the onboarding plugins REST endpoints.
|
||||
*
|
||||
* @package WooCommerce\Admin\Tests\Admin\API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Admin\API;
|
||||
|
||||
use WC_REST_Unit_Test_Case;
|
||||
use WP_REST_Request;
|
||||
|
||||
/**
|
||||
* OnboardingPlugins API controller test.
|
||||
*
|
||||
* @class OnboardingPluginsTest.
|
||||
*/
|
||||
class OnboardingPluginsTest extends WC_REST_Unit_Test_Case {
|
||||
/**
|
||||
* Endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ENDPOINT = '/wc-admin/onboarding/plugins';
|
||||
|
||||
/**
|
||||
* Set up.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->useAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a user with administrator role.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useAdmin() {
|
||||
// Register an administrator user and log in.
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
wp_set_current_user( $this->user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a user without any permissions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function useUserWithoutPluginsPermission() {
|
||||
$this->user = $this->factory->user->create();
|
||||
wp_set_current_user( $this->user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to install-async endpoint.
|
||||
*
|
||||
* @param string $endpoint Request endpoint.
|
||||
* @param string $body Request body.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function request( $endpoint, $body ) {
|
||||
$request = new WP_REST_Request( 'POST', self::ENDPOINT . $endpoint );
|
||||
$request->set_header( 'content-type', 'application/json' );
|
||||
$request->set_body( $body );
|
||||
$response = $this->server->dispatch( $request );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to scheduled-installs endpoint.
|
||||
*
|
||||
* @param string $job_id job id.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function get( $job_id ) {
|
||||
$request = new WP_REST_Request( 'GET', self::ENDPOINT . '/scheduled-installs/' . $job_id );
|
||||
|
||||
return $this->server->dispatch( $request )->get_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to confirm install-async response format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_response_format() {
|
||||
$data = $this->request(
|
||||
'/install-async',
|
||||
wp_json_encode(
|
||||
array(
|
||||
'plugins' => array( 'test' ),
|
||||
)
|
||||
)
|
||||
)->get_data();
|
||||
$this->assertArrayHasKey( 'job_id', $data );
|
||||
$this->assertArrayHasKey( 'status', $data );
|
||||
$this->assertArrayHasKey( 'plugins', $data );
|
||||
$this->assertTrue( isset( $data['plugins']['test'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to confirm it queues an action scheduler job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_it_queues_action() {
|
||||
$this->markTestSkipped( 'Skipping it for now until we find a better way of testing it.' );
|
||||
$data = $this->request(
|
||||
'/install-async',
|
||||
wp_json_encode(
|
||||
array(
|
||||
'plugins' => array( 'test' ),
|
||||
)
|
||||
)
|
||||
)->get_data();
|
||||
$action_id = $data['job_id'];
|
||||
$data = $this->get( $action_id );
|
||||
$this->assertIsArray( $data );
|
||||
$this->assertEquals( $action_id, $data['job_id'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test it returns 404 when an unknown job id is given.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_it_returns_404_with_unknown_job_id() {
|
||||
$request = new WP_REST_Request( 'GET', self::ENDPOINT . '/scheduled-installs/i-do-not-exist' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 404, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test permissions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_permissions() {
|
||||
$this->useUserWithoutPluginsPermission();
|
||||
foreach ( array( '/install-and-activate', '/install-async' ) as $endpoint ) {
|
||||
$response = $this->request(
|
||||
$endpoint,
|
||||
wp_json_encode(
|
||||
array(
|
||||
'plugins' => array( 'test' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->assertEquals( 403, $response->get_status() );
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue