Onboarding - Profile Wizard: Update plugin installation step to deal with previously installed plugins (https://github.com/woocommerce/woocommerce-admin/pull/2825)

* Handle previously installed plugins during the onboarding wizard

* Allow the activate endpoint to activate multiple plugins at once, avoiding a race condition.

* Handle PR feedback

* Add the ability to fetch active plugins via wc-api
This commit is contained in:
Justin Shreve 2019-08-23 08:56:57 -04:00 committed by GitHub
parent 2b188369b3
commit fe585aa2ee
9 changed files with 453 additions and 139 deletions

View File

@ -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 (
<Fragment>
<H className="woocommerce-profile-wizard__header-title">
@ -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 );

View File

@ -0,0 +1,33 @@
/** @format */
/*eslint-disable max-len*/
export default () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<mask
id="card_mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="2"
y="4"
width="20"
height="16"
>
<path
id="icon/action/payment_24px_2"
fillRule="evenodd"
clipRule="evenodd"
d="M20 4H4C2.89 4 2.01 4.89 2.01 6L2 18C2 19.11 2.89 20 4 20H20C21.11 20 22 19.11 22 18V6C22 4.89 21.11 4 20 4ZM20 18H4V12H20V18ZM4 8H20V6H4V8Z"
fill="white"
/>
</mask>
<g mask="url(#card_mask)">
<g>
<rect width="24" height="24" fill="#50575D" />
</g>
</g>
</g>
</svg>
);
/*eslint-enable max-len*/

View File

@ -0,0 +1,32 @@
/** @format */
/*eslint-disable max-len*/
export default () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g>
<mask
id="print_mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="2"
y="3"
width="20"
height="18"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M19 8H18V3H6V8H5C3.34 8 2 9.34 2 11V17H6V21H18V17H22V11C22 9.34 20.66 8 19 8ZM8 5H16V8H8V5ZM16 19V17V15H8V19H16ZM18 15V13H6V15H4V11C4 10.45 4.45 10 5 10H19C19.55 10 20 10.45 20 11V15H18ZM17 11.5C17 10.9477 17.4477 10.5 18 10.5C18.5523 10.5 19 10.9477 19 11.5C19 12.0523 18.5523 12.5 18 12.5C17.4477 12.5 17 12.0523 17 11.5Z"
fill="white"
/>
</mask>
<g mask="url(#print_mask)">
<g>
<rect width="24" height="24" fill="#50575D" />
</g>
</g>
</g>
</svg>
);
/*eslint-enable max-len*/

View File

@ -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: <SecurityIcon />,
description: __(
'Jetpack automatically blocks brute force attacks to protect your store from unauthorized access.',
'woocommerce-admin'
),
},
{
title: __( 'Sales Tax', 'woocommerce-admin' ),
icon: <SalesTaxIcon />,
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: <SpeedIcon />,
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: <MobileAppIcon />,
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: <SecurityIcon />,
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: <SalesTaxIcon />,
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: <SpeedIcon />,
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: <MobileAppIcon />,
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: <PrintIcon />,
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: <CardIcon />,
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 (
<div className="woocommerce-profile-wizard__benefits">
{ filter( this.getBenefits(), benefit => benefit.visible ).map( benefit =>
this.renderBenefit( benefit )
) }
</div>
);
}
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 {
<p>
{ 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: <strong />,
@ -163,9 +224,7 @@ class Start extends Component {
</p>
<Card>
<div className="woocommerce-profile-wizard__benefits">
{ benefits.map( benefit => this.renderBenefit( benefit ) ) }
</div>
{ this.renderBenefits() }
<div className="woocommerce-profile-wizard__tracking">
<CheckboxControl
@ -195,7 +254,7 @@ class Start extends Component {
<p>
<Button isLink className="woocommerce-profile-wizard__skip" onClick={ this.skipWizard }>
{ __( 'Proceed without Jetpack or WooCommerce Services', 'woocommerce-admin' ) }
{ sprintf( __( 'Proceed without %s', 'woocommerce-admin' ), pluginNames ) }
</Button>
</p>
</Fragment>
@ -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' );

View File

@ -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;
}
}
}

View File

@ -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 [];

View File

@ -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,

View File

@ -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 );
}

View File

@ -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.
*