diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/plugins.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/plugins.js index 379992f03f7..c9c7bad46e0 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/plugins.js +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/plugins.js @@ -2,21 +2,32 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Button } from 'newspack-components'; import { Component, Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; -import { difference } from 'lodash'; +import { difference, get } from 'lodash'; import { withDispatch } from '@wordpress/data'; +/** + * WooCommerce depdencies + */ +import { H, Stepper, Card } from '@woocommerce/components'; +import { updateQueryString } from '@woocommerce/navigation'; + /** * Internal depdencies */ -import { H, Stepper, Card } from '@woocommerce/components'; import { recordEvent } from 'lib/tracks'; import withSelect from 'wc-api/with-select'; +import { pluginNames } from 'wc-api/onboarding/constants'; -const plugins = [ 'jetpack', 'woocommerce-services' ]; +const pluginsToInstall = [ 'jetpack', 'woocommerce-services' ]; +// We want to use the cached version of activePlugins here, otherwise the list we are dealing with could update as plugins are activated. +const plugins = difference( + pluginsToInstall, + get( wcSettings, [ 'onboarding', 'activePlugins' ], [] ) +); class Plugins extends Component { constructor() { @@ -30,11 +41,20 @@ class Plugins extends Component { } componentDidMount() { + if ( 0 === plugins.length ) { + return updateQueryString( { step: 'store-details' } ); + } this.props.installPlugins( plugins ); } componentDidUpdate( prevProps ) { - const { createNotice, errors, installedPlugins, jetpackConnectUrl } = this.props; + const { + createNotice, + errors, + installedPlugins, + activatedPlugins, + jetpackConnectUrl, + } = this.props; if ( jetpackConnectUrl ) { window.location = jetpackConnectUrl; @@ -51,6 +71,17 @@ class Plugins extends Component { this.setState( { step: 'activate' } ); /* eslint-enable react/no-did-update-set-state */ } + + // If Jetpack was already connected, we can go to store details after WCS is activated. + if ( + ! plugins.includes( 'jetpack' ) && + prevProps.activatedPlugins.length !== plugins.length && + activatedPlugins.length === plugins.length + ) { + /* eslint-disable react/no-did-update-set-state */ + return updateQueryString( { step: 'store-details' } ); + /* eslint-enable react/no-did-update-set-state */ + } } async activatePlugins( event ) { @@ -71,6 +102,10 @@ class Plugins extends Component { const { hasErrors, isRequesting } = this.props; const { step } = this.state; + const pluginLabel = plugins.includes( 'jetpack' ) + ? Object.values( pluginNames ).join( ' & ' ) + : pluginNames[ 'woocommerce-services' ]; + return ( @@ -84,11 +119,11 @@ class Plugins extends Component { isPending={ isRequesting && ! hasErrors } steps={ [ { - label: __( 'Install Jetpack and WooCommerce Services', 'woocommerce-admin' ), + label: sprintf( __( 'Install %s', 'woocommerce-admin' ), pluginLabel ), key: 'install', }, { - label: __( 'Activate Jetpack and WooCommerce Services', 'woocommerce-admin' ), + label: sprintf( __( 'Activate %s', 'woocommerce-admin' ), pluginLabel ), key: 'activate', }, ] } @@ -150,7 +185,7 @@ export default compose( errors.push( installationErrors[ plugin ].message ) ); if ( jetpackConnectUrlError ) { - errors.push( jetpackConnectUrlError ); + errors.push( jetpackConnectUrlError.message ); } const hasErrors = Boolean( errors.length ); diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/card.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/card.js new file mode 100644 index 00000000000..40c4fffe01c --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/card.js @@ -0,0 +1,33 @@ +/** @format */ +/*eslint-disable max-len*/ + +export default () => ( + + + + + + + + + + + + +); + +/*eslint-enable max-len*/ diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/print.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/print.js new file mode 100644 index 00000000000..1256bf12a2b --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/images/print.js @@ -0,0 +1,32 @@ +/** @format */ +/*eslint-disable max-len*/ + +export default () => ( + + + + + + + + + + + + +); + +/*eslint-enable max-len*/ diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/index.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/index.js index 9b47f036a02..d977df07d02 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/index.js +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/start/index.js @@ -2,60 +2,33 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { FormToggle } from '@wordpress/components'; import { Button, CheckboxControl } from 'newspack-components'; import { Component, Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import interpolateComponents from 'interpolate-components'; import { withDispatch } from '@wordpress/data'; +import { filter } from 'lodash'; + +/** + * WooCommerce depdencies + */ +import { Card, H, Link } from '@woocommerce/components'; +import { updateQueryString } from '@woocommerce/navigation'; /** * Internal depdencies */ -import { Card, H, Link } from '@woocommerce/components'; +import CardIcon from './images/card'; import SecurityIcon from './images/security'; import SalesTaxIcon from './images/local_atm'; import SpeedIcon from './images/flash_on'; import MobileAppIcon from './images/phone_android'; +import PrintIcon from './images/print'; import withSelect from 'wc-api/with-select'; import { recordEvent } from 'lib/tracks'; -const benefits = [ - { - title: __( 'Security', 'woocommerce-admin' ), - icon: , - description: __( - 'Jetpack automatically blocks brute force attacks to protect your store from unauthorized access.', - 'woocommerce-admin' - ), - }, - { - title: __( 'Sales Tax', 'woocommerce-admin' ), - icon: , - description: __( - 'With WooCommerce Services we ensure that the correct rate of tax is charged on all of your orders.', - 'woocommerce-admin' - ), - }, - { - title: __( 'Speed', 'woocommerce-admin' ), - icon: , - description: __( - 'Cache your images and static files on our own powerful global network of servers and speed up your site.', - 'woocommerce-admin' - ), - }, - { - title: __( 'Mobile App', 'woocommerce-admin' ), - icon: , - description: __( - 'Your store in your pocket. Manage orders, receive sales notifications, and more. Only with a Jetpack connection.', - 'woocommerce-admin' - ), - }, -]; - class Start extends Component { constructor() { super( ...arguments ); @@ -69,6 +42,15 @@ class Start extends Component { this.skipWizard = this.skipWizard.bind( this ); } + componentDidMount() { + if ( + this.props.activePlugins.includes( 'jetpack' ) && + this.props.activePlugins.includes( 'woocommerce-services' ) + ) { + return updateQueryString( { step: 'store-details' } ); + } + } + async updateTracking() { const { updateSettings } = this.props; const allowTracking = this.state.allowTracking ? 'yes' : 'no'; @@ -128,8 +110,84 @@ class Start extends Component { ); } + getBenefits() { + const { activePlugins } = this.props; + return [ + { + title: __( 'Security', 'woocommerce-admin' ), + icon: , + description: __( + 'Jetpack automatically blocks brute force attacks to protect your store from unauthorized access.', + 'woocommerce-admin' + ), + visible: ! activePlugins.includes( 'jetpack' ), + }, + { + title: __( 'Sales Tax', 'woocommerce-admin' ), + icon: , + description: __( + 'With WooCommerce Services we ensure that the correct rate of tax is charged on all of your orders.', + 'woocommerce-admin' + ), + visible: true, + }, + { + title: __( 'Speed', 'woocommerce-admin' ), + icon: , + description: __( + 'Cache your images and static files on our own powerful global network of servers and speed up your site.', + 'woocommerce-admin' + ), + visible: ! activePlugins.includes( 'jetpack' ), + }, + { + title: __( 'Mobile App', 'woocommerce-admin' ), + icon: , + description: __( + 'Your store in your pocket. Manage orders, receive sales notifications, and more. Only with a Jetpack connection.', + 'woocommerce-admin' + ), + visible: ! activePlugins.includes( 'jetpack' ), + }, + { + title: __( 'Print your own shipping labels', 'woocommerce-admin' ), + icon: , + description: __( + 'Save time at the Post Office by printing USPS shipping labels at home.', + 'woocommerce-admin' + ), + visible: + activePlugins.includes( 'jetpack' ) && ! activePlugins.includes( 'woocommerce-services' ), + }, + { + title: __( 'Simple payment setup', 'woocommerce-admin' ), + icon: , + description: __( + 'WooCommerce Services enables us to provision Stripe and Paypal accounts quickly and easily for you.', + 'woocommerce-admin' + ), + visible: + activePlugins.includes( 'jetpack' ) && ! activePlugins.includes( 'woocommerce-services' ), + }, + ]; + } + + renderBenefits() { + return ( +
+ { filter( this.getBenefits(), benefit => benefit.visible ).map( benefit => + this.renderBenefit( benefit ) + ) } +
+ ); + } + render() { const { allowTracking } = this.state; + const { activePlugins } = this.props; + const pluginNames = activePlugins.includes( 'jetpack' ) + ? __( 'WooCommerce Services', 'woocommerce-admin' ) + : __( 'Jetpack & WooCommerce Services', 'woocommerce-admin' ); const trackingLabel = interpolateComponents( { mixedString: __( @@ -151,10 +209,13 @@ class Start extends Component {

{ interpolateComponents( { - mixedString: __( - 'Simplify and enhance the setup of your store with the free features and benefits offered by ' + - '{{strong}}Jetpack & WooCommerce Services{{/strong}}.', - 'woocommerce-admin' + mixedString: sprintf( + __( + 'Simplify and enhance the setup of your store with the free features and benefits offered by ' + + '{{strong}}%s{{/strong}}.', + 'woocommerce-admin' + ), + pluginNames ), components: { strong: , @@ -163,9 +224,7 @@ class Start extends Component {

-
- { benefits.map( benefit => this.renderBenefit( benefit ) ) } -
+ { this.renderBenefits() }

@@ -205,15 +264,27 @@ class Start extends Component { export default compose( withSelect( select => { - const { getProfileItemsError, getSettings, getSettingsError, isGetSettingsRequesting } = select( - 'wc-api' - ); + const { + getProfileItemsError, + getSettings, + getSettingsError, + isGetSettingsRequesting, + getActivePlugins, + } = select( 'wc-api' ); const isSettingsError = Boolean( getSettingsError( 'advanced' ) ); const isSettingsRequesting = isGetSettingsRequesting( 'advanced' ); const isProfileItemsError = Boolean( getProfileItemsError() ); - return { getSettings, isSettingsError, isProfileItemsError, isSettingsRequesting }; + const activePlugins = getActivePlugins(); + + return { + getSettings, + isSettingsError, + isProfileItemsError, + isSettingsRequesting, + activePlugins, + }; } ), withDispatch( dispatch => { const { updateProfileItems, updateSettings } = dispatch( 'wc-api' ); diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/style.scss b/plugins/woocommerce-admin/client/dashboard/profile-wizard/style.scss index 19a9b8c4f40..16c6d9c668a 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/style.scss +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/style.scss @@ -263,7 +263,7 @@ .woocommerce-profile-wizard__plugins-card { .woocommerce-profile-wizard__plugins-actions { text-align: left; - margin-left: 64px; + margin-left: 44px; min-height: 28px; button.muriel-button { @@ -271,6 +271,7 @@ height: 40px; min-width: auto; display: initial; + margin-right: $gap-small; } } } diff --git a/plugins/woocommerce-admin/client/wc-api/onboarding/operations.js b/plugins/woocommerce-admin/client/wc-api/onboarding/operations.js index 9ea9dfd34ec..f6ecf94f6d8 100644 --- a/plugins/woocommerce-admin/client/wc-api/onboarding/operations.js +++ b/plugins/woocommerce-admin/client/wc-api/onboarding/operations.js @@ -14,6 +14,7 @@ import { NAMESPACE, pluginNames } from './constants'; function read( resourceNames, fetch = apiFetch ) { return [ + ...readActivePlugins( resourceNames, fetch ), ...readProfileItems( resourceNames, fetch ), ...readJetpackConnectUrl( resourceNames, fetch ), ]; @@ -98,6 +99,77 @@ function profileItemToResource( items ) { return resources; } +function readActivePlugins( resourceNames, fetch ) { + const resourceName = 'active-plugins'; + if ( resourceNames.includes( resourceName ) ) { + const url = NAMESPACE + '/onboarding/plugins/active'; + + return [ + fetch( { path: url } ) + .then( activePluginsToResources ) + .catch( error => { + return { [ resourceName ]: { error: String( error.message ) } }; + } ), + ]; + } + + return []; +} + +function activePluginsToResources( items ) { + const { plugins } = items; + const resourceName = 'active-plugins'; + return { + [ resourceName ]: { + data: plugins, + }, + }; +} + +function activatePlugins( resourceNames, data, fetch ) { + const resourceName = 'plugin-activate'; + if ( resourceNames.includes( resourceName ) ) { + const plugins = data[ resourceName ]; + const url = NAMESPACE + '/onboarding/plugins/activate'; + return [ + fetch( { + path: url, + method: 'POST', + data: { + plugins: plugins.join( ',' ), + }, + } ) + .then( response => activatePluginToResource( response, plugins ) ) + .catch( error => { + const resources = { [ resourceName ]: { error } }; + Object.keys( plugins ).forEach( key => { + const pluginError = { ...error }; + const item = plugins[ key ]; + pluginError.message = getPluginErrorMessage( 'activate', item ); + resources[ getResourceName( resourceName, item ) ] = { error: pluginError }; + } ); + return resources; + } ), + ]; + } + + return []; +} + +function activatePluginToResource( response, items ) { + const resourceName = 'plugin-activate'; + + const resources = { + [ resourceName ]: { data: items }, + [ 'active-plugins' ]: { data: response.active }, + }; + Object.keys( items ).forEach( key => { + const item = items[ key ]; + resources[ getResourceName( resourceName, item ) ] = { data: item }; + } ); + return resources; +} + function readJetpackConnectUrl( resourceNames, fetch ) { const resourceName = 'jetpack-connect-url'; @@ -112,7 +184,7 @@ function readJetpackConnectUrl( resourceNames, fetch ) { return { [ resourceName ]: { data: response.connectAction } }; } ) .catch( error => { - error.message = getPluginErrorMessage( 'activate', 'jetpack' ); + error.message = getPluginErrorMessage( 'connect', 'jetpack' ); return { [ resourceName ]: { error } }; } ), ]; @@ -121,64 +193,55 @@ function readJetpackConnectUrl( resourceNames, fetch ) { return []; } -function doPluginActions( fetch, action, plugins ) { - const resourceName = [ `plugin-${ action }` ]; - - return plugins.map( async plugin => { - return fetch( { - path: `${ NAMESPACE }/onboarding/plugins/${ action }`, - method: 'POST', - data: { - plugin, - }, - } ) - .then( response => { - return { - [ resourceName ]: { data: plugins }, - [ getResourceName( resourceName, plugin ) ]: { data: response }, - }; - } ) - .catch( error => { - error.message = getPluginErrorMessage( action, plugin ); - return { - [ resourceName ]: { data: plugins }, - [ getResourceName( resourceName, plugin ) ]: { error }, - }; - } ); - } ); -} - function getPluginErrorMessage( action, plugin ) { const pluginName = pluginNames[ plugin ] || plugin; - - return 'install' === action - ? sprintf( + switch ( action ) { + case 'install': + return sprintf( __( 'There was an error installing %s. Please try again.', 'woocommerce-admin' ), pluginName - ) - : sprintf( + ); + case 'connect': + return sprintf( + __( 'There was an error connecting to %s. Please try again.', 'woocommerce-admin' ), + pluginName + ); + case 'activate': + default: + return sprintf( __( 'There was an error activating %s. Please try again.', 'woocommerce-admin' ), pluginName ); + } } function installPlugins( resourceNames, data, fetch ) { const resourceName = 'plugin-install'; - if ( resourceNames.includes( resourceName ) ) { const plugins = data[ resourceName ]; - return doPluginActions( fetch, 'install', plugins ); - } - return []; -} - -function activatePlugins( resourceNames, data, fetch ) { - const resourceName = 'plugin-activate'; - - if ( resourceNames.includes( resourceName ) ) { - const plugins = data[ resourceName ]; - return doPluginActions( fetch, 'activate', plugins ); + return plugins.map( async plugin => { + return fetch( { + path: `${ NAMESPACE }/onboarding/plugins/install`, + method: 'POST', + data: { + plugin, + }, + } ) + .then( response => { + return { + [ resourceName ]: { data: plugins }, + [ getResourceName( resourceName, plugin ) ]: { data: response }, + }; + } ) + .catch( error => { + error.message = getPluginErrorMessage( 'install', pluginNames[ plugin ] || plugin ); + return { + [ resourceName ]: { data: plugins }, + [ getResourceName( resourceName, plugin ) ]: { error }, + }; + } ); + } ); } return []; diff --git a/plugins/woocommerce-admin/client/wc-api/onboarding/selectors.js b/plugins/woocommerce-admin/client/wc-api/onboarding/selectors.js index 832e60d861d..ba896c79e3a 100644 --- a/plugins/woocommerce-admin/client/wc-api/onboarding/selectors.js +++ b/plugins/woocommerce-admin/client/wc-api/onboarding/selectors.js @@ -77,6 +77,32 @@ const getPluginInstallations = getResource => plugins => { return installations; }; +const getActivePlugins = ( getResource, requireResource ) => ( + requirement = DEFAULT_REQUIREMENT +) => { + const resourceName = 'active-plugins'; + const data = requireResource( requirement, resourceName ).data || []; + if ( ! data.length ) { + return wcSettings.onboarding.activePlugins; + } + + return data; +}; + +const getActivePluginsError = getResource => () => { + return getResource( 'active-plugins' ).error; +}; + +const isGetActivePluginsRequesting = getResource => () => { + const { lastReceived, lastRequested } = getResource( 'active-plugins' ); + + if ( isNil( lastRequested ) || isNil( lastReceived ) ) { + return true; + } + + return lastRequested > lastReceived; +}; + const getPluginActivations = getResource => plugins => { const resourceName = 'plugin-activate'; @@ -146,6 +172,9 @@ export default { getJetpackConnectUrl, getJetpackConnectUrlError, isGetJetpackConnectUrlRequesting, + getActivePlugins, + getActivePluginsError, + isGetActivePluginsRequesting, getPluginActivations, getPluginInstallations, getPluginInstallationErrors, diff --git a/plugins/woocommerce-admin/src/API/OnboardingPlugins.php b/plugins/woocommerce-admin/src/API/OnboardingPlugins.php index bf49219307f..d411e1d2c13 100644 --- a/plugins/woocommerce-admin/src/API/OnboardingPlugins.php +++ b/plugins/woocommerce-admin/src/API/OnboardingPlugins.php @@ -8,6 +8,7 @@ */ namespace Automattic\WooCommerce\Admin\API; +use Automattic\WooCommerce\Admin\Features\Onboarding; defined( 'ABSPATH' ) || exit; @@ -49,13 +50,26 @@ class OnboardingPlugins extends \WC_REST_Data_Controller { ) ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/active', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'active_plugins' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_item_schema' ), + ) + ); + register_rest_route( $this->namespace, '/' . $this->rest_base . '/activate', array( array( 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'activate_plugin' ), + 'callback' => array( $this, 'activate_plugins' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), ), 'schema' => array( $this, 'get_item_schema' ), @@ -115,20 +129,6 @@ class OnboardingPlugins extends \WC_REST_Data_Controller { return true; } - /** - * Get an array of plugins that can be installed & activated via the endpoints. - */ - public function get_allowed_plugins() { - return apply_filters( - 'woocommerce_onboarding_plugins_whitelist', - array( - 'facebook-for-woocommerce' => 'facebook-for-woocommerce/facebook-for-woocommerce.php', - 'jetpack' => 'jetpack/jetpack.php', - 'woocommerce-services' => 'woocommerce-services/woocommerce-services.php', - ) - ); - } - /** * Installs the requested plugin. * @@ -136,7 +136,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller { * @return array Plugin Status */ public function install_plugin( $request ) { - $allowed_plugins = $this->get_allowed_plugins(); + $allowed_plugins = Onboarding::get_allowed_plugins(); $plugin = sanitize_title_with_dashes( $request['plugin'] ); if ( ! in_array( $plugin, array_keys( $allowed_plugins ), true ) ) { return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 ); @@ -190,37 +190,54 @@ class OnboardingPlugins extends \WC_REST_Data_Controller { ); } + /** + * Returns a list of active plugins. + * + * @param WP_REST_Request $request Full details about the request. + * @return array Active plugins + */ + public function active_plugins( $request ) { + $plugins = Onboarding::get_active_plugins(); + return( array( + 'plugins' => array_values( $plugins ), + ) ); + } + /** * Activate the requested plugin. * * @param WP_REST_Request $request Full details about the request. * @return array Plugin Status */ - public function activate_plugin( $request ) { - $allowed_plugins = $this->get_allowed_plugins(); - $plugin = sanitize_title_with_dashes( $request['plugin'] ); - if ( ! in_array( $plugin, array_keys( $allowed_plugins ), true ) ) { - return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 ); + public function activate_plugins( $request ) { + $allowed_plugins = Onboarding::get_allowed_plugins(); + $_plugins = explode( ',', $request['plugins'] ); + $plugins = array_intersect( array_keys( $allowed_plugins ), $_plugins ); + + if ( empty( $plugins ) || ! is_array( $plugins ) ) { + return new \WP_Error( 'woocommerce_rest_invalid_plugins', __( 'Invalid plugins.', 'woocommerce-admin' ), 404 ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; - $slug = $plugin; - $path = $allowed_plugins[ $slug ]; - $installed_plugins = get_plugins(); + foreach( $plugins as $plugin ) { + $slug = $plugin; + $path = $allowed_plugins[ $slug ]; + $installed_plugins = get_plugins(); - if ( ! in_array( $path, array_keys( $installed_plugins ), true ) ) { - return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 ); - } + if ( ! in_array( $path, array_keys( $installed_plugins ), true ) ) { + return new \WP_Error( 'woocommerce_rest_invalid_plugin', sprintf( __( 'Invalid plugin %s.', 'woocommerce-admin' ), $slug ), 404 ); + } - $result = activate_plugin( $path ); - if ( ! is_null( $result ) ) { - return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'The requested plugin could not be activated.', 'woocommerce-admin' ), 500 ); + $result = activate_plugin( $path ); + if ( ! is_null( $result ) ) { + return new \WP_Error( 'woocommerce_rest_invalid_plugin', sprintf( __( 'The requested plugins could not be activated.', 'woocommerce-admin' ), $slug ), 500 ); + } } return( array( - 'slug' => $slug, - 'name' => $installed_plugins[ $path ]['Name'], + 'activatedPlugins' => array_values( $plugins ), + 'active' => Onboarding::get_active_plugins(), 'status' => 'success', ) ); } @@ -231,7 +248,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller { * @return array Connection URL for Jetpack */ public function connect_jetpack() { - if ( ! class_exists( 'Jetpack' ) ) { + if ( ! class_exists( '\Jetpack' ) ) { return new \WP_Error( 'woocommerce_rest_jetpack_not_active', __( 'Jetpack is not installed or active.', 'woocommerce-admin' ), 404 ); } diff --git a/plugins/woocommerce-admin/src/Features/Onboarding.php b/plugins/woocommerce-admin/src/Features/Onboarding.php index e1c5b5c53f5..ae798ca20a9 100644 --- a/plugins/woocommerce-admin/src/Features/Onboarding.php +++ b/plugins/woocommerce-admin/src/Features/Onboarding.php @@ -311,14 +311,47 @@ class Onboarding { // Only fetch if the onboarding wizard is incomplete. if ( $this->should_show_profiler() ) { - $settings['onboarding']['productTypes'] = self::get_allowed_product_types(); - $settings['onboarding']['themes'] = self::get_themes(); - $settings['onboarding']['activeTheme'] = get_option( 'stylesheet' ); + $settings['onboarding']['productTypes'] = self::get_allowed_product_types(); + $settings['onboarding']['themes'] = self::get_themes(); + $settings['onboarding']['activeTheme'] = get_option( 'stylesheet' ); + $settings['onboarding']['activePlugins'] = self::get_active_plugins(); } return $settings; } + /** + * Gets an array of plugins that can be installed & activated via the onboarding wizard. + * + * @todo Handle edgecase of where installed plugins may have versioned folder names (i.e. `jetpack-master/jetpack.php`). + */ + public static function get_allowed_plugins() { + return apply_filters( + 'woocommerce_onboarding_plugins_whitelist', + array( + 'jetpack' => 'jetpack/jetpack.php', + 'woocommerce-services' => 'woocommerce-services/woocommerce-services.php', + ) + ); + } + /** + * Get a list of active plugins, relevent to the onboarding wizard. + * + * @return array + */ + public static function get_active_plugins() { + $all_active_plugins = get_option( 'active_plugins', array() ); + $allowed_plugins = self::get_allowed_plugins(); + $active_plugin_files = array_intersect( $all_active_plugins, $allowed_plugins ); + $allowed_plugin_slugs = array_flip( $allowed_plugins ); + $active_plugins = array(); + foreach ( $active_plugin_files as $file ) { + $slug = $allowed_plugin_slugs[ $file ]; + $active_plugins[] = $slug; + } + return $active_plugins; + } + /** * Let the app know that we will be showing the onboarding route, so wp-admin elements should be hidden while loading. *