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:
parent
2b188369b3
commit
fe585aa2ee
|
@ -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 );
|
||||
|
||||
|
|
|
@ -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*/
|
|
@ -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*/
|
|
@ -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' );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 [];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue