Add plugin installer to allow installation of plugins via URL (https://github.com/woocommerce/woocommerce-admin/pull/6805)
* Allow any plugin to be installed or activated * Add PluginInstaller class * Redirect to referring page if one exists * Store message and show after redirect * Add changelog and testing instructions
This commit is contained in:
parent
9e05116326
commit
8f018fc518
|
@ -2,6 +2,12 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Add plugin installer to allow installation of plugins via URL #6805
|
||||
|
||||
1. Visit any admin page with the params `plugin_action` (`install`, `activate`, or `install-activate`) and `plugins` (list of comma separated plugins). `wp-admin/admin.php?page=wc-admin&plugin_action=install&plugins=jetpack`
|
||||
2. If visiting this URL from a link, make sure you are sent back to the referer.
|
||||
3. Check that the plugins provided are installed, activated, or both depending on your query.
|
||||
|
||||
### Retain persisted queries when navigating to Homescreen #6614
|
||||
|
||||
1. Go to Analytics Report.
|
||||
|
|
|
@ -14,9 +14,9 @@ To enable the new onboarding experience manually, log-in to `wp-admin`, and go t
|
|||
|
||||
To power the new onboarding flow client side, new REST API endpoints have been introduced. These are purpose built endpoints that exist under the `/wc-admin/onboarding/` namespace, and are not meant to be shipped in the core rest API package. The source is stored in `src/API/Plugins.php`, `src/API/OnboardingProfile.php`, and `src/API/OnboardingTasks.php` respectively.
|
||||
|
||||
* POST `/wc-admin/plugins/install` - Installs a requested plugin, if present in the `woocommerce_admin_plugins_whitelist` array.
|
||||
* POST `/wc-admin/plugins/install` - Install requested plugins.
|
||||
* GET `/wc-admin/plugins/active` - Returns a list of the currently active plugins.
|
||||
* POST `/wc-admin/plugins/activate` - Activates the requested plugins, if present in the `woocommerce_admin_plugins_whitelist` array. Multiple plugins can be passed to activate at once.
|
||||
* POST `/wc-admin/plugins/activate` - Activates the requested plugins. Multiple plugins can be passed to activate at once.
|
||||
* GET `/wc-admin/plugins/connect-jetpack` - Generates a URL for connecting to Jetpack. A `redirect_url` is accepted, which is used upon a successful connection.
|
||||
* POST `/wc-admin/plugins/request-wccom-connect` - Generates a URL for the WooCommerce.com connection process.
|
||||
* POST `/wc-admin/plugins/finish-wccom-connect` - Finishes the WooCommerce.com connection process by storing the received access token.
|
||||
|
|
|
@ -75,6 +75,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
|
|||
|
||||
== Unreleased ==
|
||||
|
||||
- Add: Add plugin installer to allow installation of plugins via URL #6805
|
||||
- Update: Adding setup required icon for non-configured payment methods #6811
|
||||
- Update: UI updates to Payment Task screen #6766
|
||||
- Dev: Add data source filter to remote inbox notification system #6794
|
||||
|
|
|
@ -80,13 +80,9 @@ class MarketingOverview extends \WC_REST_Data_Controller {
|
|||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function activate_plugin( $request ) {
|
||||
$allowed_plugins = InstalledExtensions::get_allowed_plugins();
|
||||
$plugin_slug = $request->get_param( 'plugin' );
|
||||
$plugin_slug = $request->get_param( 'plugin' );
|
||||
|
||||
if (
|
||||
! PluginsHelper::is_plugin_installed( $plugin_slug ) ||
|
||||
! in_array( $plugin_slug, $allowed_plugins, true )
|
||||
) {
|
||||
if ( ! PluginsHelper::is_plugin_installed( $plugin_slug ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 );
|
||||
}
|
||||
|
||||
|
|
|
@ -204,8 +204,7 @@ class Plugins extends \WC_REST_Data_Controller {
|
|||
* @return WP_Error|array Plugin Status
|
||||
*/
|
||||
public function install_plugins( $request ) {
|
||||
$allowed_plugins = self::get_allowed_plugins();
|
||||
$plugins = explode( ',', $request['plugins'] );
|
||||
$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-admin' ), 404 );
|
||||
|
@ -218,25 +217,15 @@ class Plugins extends \WC_REST_Data_Controller {
|
|||
include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php';
|
||||
include_once ABSPATH . '/wp-admin/includes/class-plugin-upgrader.php';
|
||||
|
||||
$existing_plugins = get_plugins();
|
||||
$existing_plugins = PluginsHelper::get_installed_plugins_paths();
|
||||
$installed_plugins = array();
|
||||
$results = array();
|
||||
$errors = new \WP_Error();
|
||||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$slug = sanitize_key( $plugin );
|
||||
$path = isset( $allowed_plugins[ $slug ] ) ? $allowed_plugins[ $slug ] : false;
|
||||
|
||||
if ( ! $path ) {
|
||||
$errors->add(
|
||||
$plugin,
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
sprintf( __( 'The requested plugin `%s` is not in the list of allowed plugins.', 'woocommerce-admin' ), $slug )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $path, array_keys( $existing_plugins ), true ) ) {
|
||||
if ( isset( $existing_plugins[ $slug ] ) ) {
|
||||
$installed_plugins[] = $plugin;
|
||||
continue;
|
||||
}
|
||||
|
@ -318,25 +307,14 @@ class Plugins extends \WC_REST_Data_Controller {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of plugins that can be installed & activated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_allowed_plugins() {
|
||||
return apply_filters( 'woocommerce_admin_plugins_whitelist', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of active plugins in API format.
|
||||
*
|
||||
* @return array Active plugins
|
||||
*/
|
||||
public static function active_plugins() {
|
||||
$allowed = self::get_allowed_plugins();
|
||||
$plugins = array_values( array_intersect( PluginsHelper::get_active_plugin_slugs(), array_keys( $allowed ) ) );
|
||||
return( array(
|
||||
'plugins' => array_values( $plugins ),
|
||||
'plugins' => PluginsHelper::get_active_plugin_slugs(),
|
||||
) );
|
||||
}
|
||||
/**
|
||||
|
@ -367,7 +345,7 @@ class Plugins extends \WC_REST_Data_Controller {
|
|||
* @return WP_Error|array Plugin Status
|
||||
*/
|
||||
public function activate_plugins( $request ) {
|
||||
$allowed_plugins = self::get_allowed_plugins();
|
||||
$plugin_paths = PluginsHelper::get_installed_plugins_paths();
|
||||
$plugins = explode( ',', $request['plugins'] );
|
||||
$errors = new \WP_Error();
|
||||
$activated_plugins = array();
|
||||
|
@ -383,18 +361,9 @@ class Plugins extends \WC_REST_Data_Controller {
|
|||
|
||||
foreach ( $plugins as $plugin ) {
|
||||
$slug = $plugin;
|
||||
$path = isset( $allowed_plugins[ $slug ] ) ? $allowed_plugins[ $slug ] : false;
|
||||
$path = isset( $plugin_paths[ $slug ] ) ? $plugin_paths[ $slug ] : false;
|
||||
|
||||
if ( ! $path ) {
|
||||
$errors->add(
|
||||
$plugin,
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
sprintf( __( 'The requested plugin `%s`. is not in the list of allowed plugins.', 'woocommerce-admin' ), $slug )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! PluginsHelper::is_plugin_installed( $path ) ) {
|
||||
$errors->add(
|
||||
$plugin,
|
||||
/* translators: %s: plugin slug (example: woocommerce-services) */
|
||||
|
|
|
@ -176,6 +176,7 @@ class FeaturePlugin {
|
|||
Events::instance()->init();
|
||||
API\Init::instance();
|
||||
ReportExporter::init();
|
||||
PluginsInstaller::init();
|
||||
|
||||
// CRUD classes.
|
||||
Notes::init();
|
||||
|
|
|
@ -48,7 +48,6 @@ class Homescreen {
|
|||
// priority is 20 to run after https://github.com/woocommerce/woocommerce/blob/a55ae325306fc2179149ba9b97e66f32f84fdd9c/includes/admin/class-wc-admin-menus.php#L165.
|
||||
add_action( 'admin_head', array( $this, 'update_link_structure' ), 20 );
|
||||
}
|
||||
add_filter( 'woocommerce_admin_plugins_whitelist', array( $this, 'get_homescreen_allowed_plugins' ) );
|
||||
add_filter( 'woocommerce_admin_preload_options', array( $this, 'preload_options' ) );
|
||||
add_filter( 'woocommerce_shared_settings', array( $this, 'component_settings' ), 20 );
|
||||
}
|
||||
|
@ -117,21 +116,6 @@ class Homescreen {
|
|||
array_unshift( $submenu['woocommerce'], $menu );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of plugins that can be installed & activated via the home screen.
|
||||
*
|
||||
* @param array $plugins Array of plugin slugs to be allowed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_homescreen_allowed_plugins( $plugins ) {
|
||||
$homescreen_plugins = array(
|
||||
'jetpack' => 'jetpack/jetpack.php',
|
||||
);
|
||||
|
||||
return array_merge( $plugins, $homescreen_plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload options to prime state of the application.
|
||||
*
|
||||
|
|
|
@ -204,7 +204,6 @@ class Onboarding {
|
|||
private function add_filters() {
|
||||
// Rest API hooks need to run before is_admin() checks.
|
||||
add_filter( 'woocommerce_rest_prepare_themes', array( $this, 'add_uploaded_theme_data' ) );
|
||||
add_filter( 'woocommerce_admin_plugins_whitelist', array( $this, 'get_onboarding_allowed_plugins' ), 10, 2 );
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
|
@ -636,7 +635,7 @@ class Onboarding {
|
|||
return false;
|
||||
}
|
||||
|
||||
return in_array( $current_page['path'], $allowed_paths );
|
||||
return in_array( $current_page['path'], $allowed_paths, true );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -737,44 +736,6 @@ class Onboarding {
|
|||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of plugins that can be installed & activated via the onboarding wizard.
|
||||
*
|
||||
* @param array $plugins Array of plugin slugs to be allowed.
|
||||
*
|
||||
* @return array
|
||||
* @todo Handle edgecase of where installed plugins may have versioned folder names (i.e. `jetpack-main/jetpack.php`).
|
||||
*/
|
||||
public static function get_onboarding_allowed_plugins( $plugins ) {
|
||||
$onboarding_plugins = apply_filters(
|
||||
'woocommerce_admin_onboarding_plugins_whitelist',
|
||||
array(
|
||||
'facebook-for-woocommerce' => 'facebook-for-woocommerce/facebook-for-woocommerce.php',
|
||||
'mailchimp-for-woocommerce' => 'mailchimp-for-woocommerce/mailchimp-woocommerce.php',
|
||||
'creative-mail-by-constant-contact' => 'creative-mail-by-constant-contact/creative-mail-plugin.php',
|
||||
'kliken-marketing-for-google' => 'kliken-marketing-for-google/kliken-marketing-for-google.php',
|
||||
'jetpack' => 'jetpack/jetpack.php',
|
||||
'woocommerce-services' => 'woocommerce-services/woocommerce-services.php',
|
||||
'woocommerce-gateway-stripe' => 'woocommerce-gateway-stripe/woocommerce-gateway-stripe.php',
|
||||
'woocommerce-paypal-payments' => 'woocommerce-paypal-payments/woocommerce-paypal-payments.php',
|
||||
'klarna-checkout-for-woocommerce' => 'klarna-checkout-for-woocommerce/klarna-checkout-for-woocommerce.php',
|
||||
'klarna-payments-for-woocommerce' => 'klarna-payments-for-woocommerce/klarna-payments-for-woocommerce.php',
|
||||
'woocommerce-square' => 'woocommerce-square/woocommerce-square.php',
|
||||
'woocommerce-shipstation-integration' => 'woocommerce-shipstation-integration/woocommerce-shipstation.php',
|
||||
'woocommerce-payfast-gateway' => 'woocommerce-payfast-gateway/gateway-payfast.php',
|
||||
'woo-paystack' => 'woo-paystack/woo-paystack.php',
|
||||
'woocommerce-payments' => 'woocommerce-payments/woocommerce-payments.php',
|
||||
'woocommerce-gateway-eway' => 'woocommerce-gateway-eway/woocommerce-gateway-eway.php',
|
||||
'woo-razorpay' => 'woo-razorpay/woo-razorpay.php',
|
||||
'mollie-payments-for-woocommerce' => 'mollie-payments-for-woocommerce/mollie-payments-for-woocommerce.php',
|
||||
'payu-india' => 'payu-india/index.php',
|
||||
'mailpoet' => 'mailpoet/mailpoet.php',
|
||||
'woocommerce-mercadopago' => 'woocommerce-mercadopago/woocommerce-mercadopago.php',
|
||||
)
|
||||
);
|
||||
return array_merge( $plugins, $onboarding_plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of themes that can be installed & activated via the onboarding wizard.
|
||||
*
|
||||
|
|
|
@ -25,28 +25,12 @@ class ShippingLabelBanner {
|
|||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'woocommerce_admin_plugins_whitelist', array( $this, 'get_shipping_banner_allowed_plugins' ), 10, 2 );
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 6, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of plugins that can be installed & activated via shipping label prompt.
|
||||
*
|
||||
* @param array $plugins Array of plugin slugs to be allowed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_shipping_banner_allowed_plugins( $plugins ) {
|
||||
$shipping_banner_plugins = array(
|
||||
'woocommerce-services' => 'woocommerce-services/woocommerce-services.php',
|
||||
);
|
||||
return array_merge( $plugins, $shipping_banner_plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce Shipping makes sense for this merchant.
|
||||
*
|
||||
|
|
|
@ -60,6 +60,24 @@ class PluginsHelper {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of installed plugins with their file paths as a key value pair.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_installed_plugins_paths() {
|
||||
$plugins = get_plugins();
|
||||
$installed_plugins = array();
|
||||
|
||||
foreach ( $plugins as $path => $plugin ) {
|
||||
$path_parts = explode( '/', $path );
|
||||
$slug = $path_parts[0];
|
||||
$installed_plugins[ $slug ] = $path;
|
||||
}
|
||||
|
||||
return $installed_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of active plugin slugs.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
/**
|
||||
* PluginsInstaller
|
||||
*
|
||||
* Installer to allow plugin installation via URL query.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Admin\API\Plugins;
|
||||
|
||||
/**
|
||||
* Class PluginsInstaller
|
||||
*/
|
||||
class PluginsInstaller {
|
||||
/**
|
||||
* Message option name.
|
||||
*/
|
||||
const MESSAGE_OPTION = 'woocommerce_admin_plugin_installer_message';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'admin_init', array( __CLASS__, 'possibly_install_activate_plugins' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'display_message' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an install or activation is being requested via URL query.
|
||||
*/
|
||||
public static function possibly_install_activate_plugins() {
|
||||
/* phpcs:disable WordPress.Security.NonceVerification.Recommended */
|
||||
if ( ! isset( $_GET['plugin_action'] ) || ! isset( $_GET['plugins'] ) || ! current_user_can( 'install_plugins' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugins = sanitize_text_field( wp_unslash( $_GET['plugins'] ) );
|
||||
$plugin_action = sanitize_text_field( wp_unslash( $_GET['plugin_action'] ) );
|
||||
/* phpcs:enable WordPress.Security.NonceVerification.Recommended */
|
||||
|
||||
$plugins_api = new Plugins();
|
||||
$install_result = null;
|
||||
$activate_result = null;
|
||||
|
||||
switch ( $plugin_action ) {
|
||||
case 'install':
|
||||
$install_result = $plugins_api->install_plugins( array( 'plugins' => $plugins ) );
|
||||
break;
|
||||
case 'activate':
|
||||
$activate_result = $plugins_api->activate_plugins( array( 'plugins' => $plugins ) );
|
||||
break;
|
||||
case 'install-activate':
|
||||
$install_result = $plugins_api->install_plugins( array( 'plugins' => $plugins ) );
|
||||
$activate_result = $plugins_api->activate_plugins( array( 'plugins' => implode( ',', $install_result['data']['installed'] ) ) );
|
||||
break;
|
||||
}
|
||||
|
||||
self::cache_results( $install_result, $activate_result );
|
||||
self::redirect_to_referer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the results of installation and activation on the page.
|
||||
*
|
||||
* @param array $install_result Result of installation.
|
||||
* @param array $activate_result Result of activation.
|
||||
*/
|
||||
public static function cache_results( $install_result, $activate_result ) {
|
||||
if ( ! $install_result && ! $activate_result ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = $activate_result ? $activate_result['message'] : $install_result['message'];
|
||||
|
||||
// Show install error message if one exists.
|
||||
if ( $install_result && ! $install_result['success'] ) {
|
||||
$message = $install_result['message'];
|
||||
}
|
||||
|
||||
update_option( self::MESSAGE_OPTION, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the results of installation and activation on the page.
|
||||
*/
|
||||
public static function display_message() {
|
||||
$message = get_option( self::MESSAGE_OPTION );
|
||||
|
||||
if ( ! $message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_option( self::MESSAGE_OPTION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect back to the referring page if one exists.
|
||||
*/
|
||||
public static function redirect_to_referer() {
|
||||
$referer = wp_get_referer();
|
||||
if ( $referer && 0 !== strpos( $referer, wp_login_url() ) ) {
|
||||
wp_safe_redirect( $referer );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = remove_query_arg( 'plugin_action', wp_unslash( $_SERVER['REQUEST_URI'] ) ); // phpcs:ignore sanitization ok.
|
||||
$url = remove_query_arg( 'plugins', $url );
|
||||
wp_safe_redirect( $url );
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
|
@ -109,44 +109,4 @@ class WC_Tests_API_Plugins extends WC_REST_Unit_Test_Case {
|
|||
|
||||
$this->assertEquals( 'woocommerce_rest_invalid_plugins', $data['code'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installing a non-whitelisted plugin fails, but installs the whitelisted.
|
||||
*/
|
||||
public function test_install_non_allowed_plugins() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/install' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugins' => 'facebook-for-woocommerce,hello-dolly',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( false, $data['success'] );
|
||||
$this->assertArrayHasKey( 'hello-dolly', $data['errors']->errors );
|
||||
$this->assertEquals( array( 'facebook-for-woocommerce' ), $data['data']['installed'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that activating a non-whitelisted plugin fails, but activates the whitelisted.
|
||||
*/
|
||||
public function test_activate_non_allowed_plugins() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/activate' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugins' => 'facebook-for-woocommerce,hello-dolly',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( false, $data['success'] );
|
||||
$this->assertArrayHasKey( 'hello-dolly', $data['errors']->errors );
|
||||
$this->assertEquals( array( 'facebook-for-woocommerce' ), $data['data']['activated'] );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue