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
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { Button } from 'newspack-components';
|
import { Button } from 'newspack-components';
|
||||||
import { Component, Fragment } from '@wordpress/element';
|
import { Component, Fragment } from '@wordpress/element';
|
||||||
import { compose } from '@wordpress/compose';
|
import { compose } from '@wordpress/compose';
|
||||||
import { difference } from 'lodash';
|
import { difference, get } from 'lodash';
|
||||||
import { withDispatch } from '@wordpress/data';
|
import { withDispatch } from '@wordpress/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WooCommerce depdencies
|
||||||
|
*/
|
||||||
|
import { H, Stepper, Card } from '@woocommerce/components';
|
||||||
|
import { updateQueryString } from '@woocommerce/navigation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal depdencies
|
* Internal depdencies
|
||||||
*/
|
*/
|
||||||
import { H, Stepper, Card } from '@woocommerce/components';
|
|
||||||
import { recordEvent } from 'lib/tracks';
|
import { recordEvent } from 'lib/tracks';
|
||||||
import withSelect from 'wc-api/with-select';
|
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 {
|
class Plugins extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -30,11 +41,20 @@ class Plugins extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if ( 0 === plugins.length ) {
|
||||||
|
return updateQueryString( { step: 'store-details' } );
|
||||||
|
}
|
||||||
this.props.installPlugins( plugins );
|
this.props.installPlugins( plugins );
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate( prevProps ) {
|
componentDidUpdate( prevProps ) {
|
||||||
const { createNotice, errors, installedPlugins, jetpackConnectUrl } = this.props;
|
const {
|
||||||
|
createNotice,
|
||||||
|
errors,
|
||||||
|
installedPlugins,
|
||||||
|
activatedPlugins,
|
||||||
|
jetpackConnectUrl,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if ( jetpackConnectUrl ) {
|
if ( jetpackConnectUrl ) {
|
||||||
window.location = jetpackConnectUrl;
|
window.location = jetpackConnectUrl;
|
||||||
|
@ -51,6 +71,17 @@ class Plugins extends Component {
|
||||||
this.setState( { step: 'activate' } );
|
this.setState( { step: 'activate' } );
|
||||||
/* eslint-enable react/no-did-update-set-state */
|
/* 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 ) {
|
async activatePlugins( event ) {
|
||||||
|
@ -71,6 +102,10 @@ class Plugins extends Component {
|
||||||
const { hasErrors, isRequesting } = this.props;
|
const { hasErrors, isRequesting } = this.props;
|
||||||
const { step } = this.state;
|
const { step } = this.state;
|
||||||
|
|
||||||
|
const pluginLabel = plugins.includes( 'jetpack' )
|
||||||
|
? Object.values( pluginNames ).join( ' & ' )
|
||||||
|
: pluginNames[ 'woocommerce-services' ];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<H className="woocommerce-profile-wizard__header-title">
|
<H className="woocommerce-profile-wizard__header-title">
|
||||||
|
@ -84,11 +119,11 @@ class Plugins extends Component {
|
||||||
isPending={ isRequesting && ! hasErrors }
|
isPending={ isRequesting && ! hasErrors }
|
||||||
steps={ [
|
steps={ [
|
||||||
{
|
{
|
||||||
label: __( 'Install Jetpack and WooCommerce Services', 'woocommerce-admin' ),
|
label: sprintf( __( 'Install %s', 'woocommerce-admin' ), pluginLabel ),
|
||||||
key: 'install',
|
key: 'install',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __( 'Activate Jetpack and WooCommerce Services', 'woocommerce-admin' ),
|
label: sprintf( __( 'Activate %s', 'woocommerce-admin' ), pluginLabel ),
|
||||||
key: 'activate',
|
key: 'activate',
|
||||||
},
|
},
|
||||||
] }
|
] }
|
||||||
|
@ -150,7 +185,7 @@ export default compose(
|
||||||
errors.push( installationErrors[ plugin ].message )
|
errors.push( installationErrors[ plugin ].message )
|
||||||
);
|
);
|
||||||
if ( jetpackConnectUrlError ) {
|
if ( jetpackConnectUrlError ) {
|
||||||
errors.push( jetpackConnectUrlError );
|
errors.push( jetpackConnectUrlError.message );
|
||||||
}
|
}
|
||||||
const hasErrors = Boolean( errors.length );
|
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
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import { FormToggle } from '@wordpress/components';
|
import { FormToggle } from '@wordpress/components';
|
||||||
import { Button, CheckboxControl } from 'newspack-components';
|
import { Button, CheckboxControl } from 'newspack-components';
|
||||||
import { Component, Fragment } from '@wordpress/element';
|
import { Component, Fragment } from '@wordpress/element';
|
||||||
import { compose } from '@wordpress/compose';
|
import { compose } from '@wordpress/compose';
|
||||||
import interpolateComponents from 'interpolate-components';
|
import interpolateComponents from 'interpolate-components';
|
||||||
import { withDispatch } from '@wordpress/data';
|
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
|
* Internal depdencies
|
||||||
*/
|
*/
|
||||||
import { Card, H, Link } from '@woocommerce/components';
|
import CardIcon from './images/card';
|
||||||
import SecurityIcon from './images/security';
|
import SecurityIcon from './images/security';
|
||||||
import SalesTaxIcon from './images/local_atm';
|
import SalesTaxIcon from './images/local_atm';
|
||||||
import SpeedIcon from './images/flash_on';
|
import SpeedIcon from './images/flash_on';
|
||||||
import MobileAppIcon from './images/phone_android';
|
import MobileAppIcon from './images/phone_android';
|
||||||
|
import PrintIcon from './images/print';
|
||||||
import withSelect from 'wc-api/with-select';
|
import withSelect from 'wc-api/with-select';
|
||||||
import { recordEvent } from 'lib/tracks';
|
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 {
|
class Start extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super( ...arguments );
|
super( ...arguments );
|
||||||
|
@ -69,6 +42,15 @@ class Start extends Component {
|
||||||
this.skipWizard = this.skipWizard.bind( this );
|
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() {
|
async updateTracking() {
|
||||||
const { updateSettings } = this.props;
|
const { updateSettings } = this.props;
|
||||||
const allowTracking = this.state.allowTracking ? 'yes' : 'no';
|
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() {
|
render() {
|
||||||
const { allowTracking } = this.state;
|
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( {
|
const trackingLabel = interpolateComponents( {
|
||||||
mixedString: __(
|
mixedString: __(
|
||||||
|
@ -151,10 +209,13 @@ class Start extends Component {
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{ interpolateComponents( {
|
{ interpolateComponents( {
|
||||||
mixedString: __(
|
mixedString: sprintf(
|
||||||
'Simplify and enhance the setup of your store with the free features and benefits offered by ' +
|
__(
|
||||||
'{{strong}}Jetpack & WooCommerce Services{{/strong}}.',
|
'Simplify and enhance the setup of your store with the free features and benefits offered by ' +
|
||||||
'woocommerce-admin'
|
'{{strong}}%s{{/strong}}.',
|
||||||
|
'woocommerce-admin'
|
||||||
|
),
|
||||||
|
pluginNames
|
||||||
),
|
),
|
||||||
components: {
|
components: {
|
||||||
strong: <strong />,
|
strong: <strong />,
|
||||||
|
@ -163,9 +224,7 @@ class Start extends Component {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<div className="woocommerce-profile-wizard__benefits">
|
{ this.renderBenefits() }
|
||||||
{ benefits.map( benefit => this.renderBenefit( benefit ) ) }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="woocommerce-profile-wizard__tracking">
|
<div className="woocommerce-profile-wizard__tracking">
|
||||||
<CheckboxControl
|
<CheckboxControl
|
||||||
|
@ -195,7 +254,7 @@ class Start extends Component {
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<Button isLink className="woocommerce-profile-wizard__skip" onClick={ this.skipWizard }>
|
<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>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -205,15 +264,27 @@ class Start extends Component {
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSelect( select => {
|
withSelect( select => {
|
||||||
const { getProfileItemsError, getSettings, getSettingsError, isGetSettingsRequesting } = select(
|
const {
|
||||||
'wc-api'
|
getProfileItemsError,
|
||||||
);
|
getSettings,
|
||||||
|
getSettingsError,
|
||||||
|
isGetSettingsRequesting,
|
||||||
|
getActivePlugins,
|
||||||
|
} = select( 'wc-api' );
|
||||||
|
|
||||||
const isSettingsError = Boolean( getSettingsError( 'advanced' ) );
|
const isSettingsError = Boolean( getSettingsError( 'advanced' ) );
|
||||||
const isSettingsRequesting = isGetSettingsRequesting( 'advanced' );
|
const isSettingsRequesting = isGetSettingsRequesting( 'advanced' );
|
||||||
const isProfileItemsError = Boolean( getProfileItemsError() );
|
const isProfileItemsError = Boolean( getProfileItemsError() );
|
||||||
|
|
||||||
return { getSettings, isSettingsError, isProfileItemsError, isSettingsRequesting };
|
const activePlugins = getActivePlugins();
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSettings,
|
||||||
|
isSettingsError,
|
||||||
|
isProfileItemsError,
|
||||||
|
isSettingsRequesting,
|
||||||
|
activePlugins,
|
||||||
|
};
|
||||||
} ),
|
} ),
|
||||||
withDispatch( dispatch => {
|
withDispatch( dispatch => {
|
||||||
const { updateProfileItems, updateSettings } = dispatch( 'wc-api' );
|
const { updateProfileItems, updateSettings } = dispatch( 'wc-api' );
|
||||||
|
|
|
@ -263,7 +263,7 @@
|
||||||
.woocommerce-profile-wizard__plugins-card {
|
.woocommerce-profile-wizard__plugins-card {
|
||||||
.woocommerce-profile-wizard__plugins-actions {
|
.woocommerce-profile-wizard__plugins-actions {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin-left: 64px;
|
margin-left: 44px;
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
|
|
||||||
button.muriel-button {
|
button.muriel-button {
|
||||||
|
@ -271,6 +271,7 @@
|
||||||
height: 40px;
|
height: 40px;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
display: initial;
|
display: initial;
|
||||||
|
margin-right: $gap-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { NAMESPACE, pluginNames } from './constants';
|
||||||
|
|
||||||
function read( resourceNames, fetch = apiFetch ) {
|
function read( resourceNames, fetch = apiFetch ) {
|
||||||
return [
|
return [
|
||||||
|
...readActivePlugins( resourceNames, fetch ),
|
||||||
...readProfileItems( resourceNames, fetch ),
|
...readProfileItems( resourceNames, fetch ),
|
||||||
...readJetpackConnectUrl( resourceNames, fetch ),
|
...readJetpackConnectUrl( resourceNames, fetch ),
|
||||||
];
|
];
|
||||||
|
@ -98,6 +99,77 @@ function profileItemToResource( items ) {
|
||||||
return resources;
|
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 ) {
|
function readJetpackConnectUrl( resourceNames, fetch ) {
|
||||||
const resourceName = 'jetpack-connect-url';
|
const resourceName = 'jetpack-connect-url';
|
||||||
|
|
||||||
|
@ -112,7 +184,7 @@ function readJetpackConnectUrl( resourceNames, fetch ) {
|
||||||
return { [ resourceName ]: { data: response.connectAction } };
|
return { [ resourceName ]: { data: response.connectAction } };
|
||||||
} )
|
} )
|
||||||
.catch( error => {
|
.catch( error => {
|
||||||
error.message = getPluginErrorMessage( 'activate', 'jetpack' );
|
error.message = getPluginErrorMessage( 'connect', 'jetpack' );
|
||||||
return { [ resourceName ]: { error } };
|
return { [ resourceName ]: { error } };
|
||||||
} ),
|
} ),
|
||||||
];
|
];
|
||||||
|
@ -121,64 +193,55 @@ function readJetpackConnectUrl( resourceNames, fetch ) {
|
||||||
return [];
|
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 ) {
|
function getPluginErrorMessage( action, plugin ) {
|
||||||
const pluginName = pluginNames[ plugin ] || plugin;
|
const pluginName = pluginNames[ plugin ] || plugin;
|
||||||
|
switch ( action ) {
|
||||||
return 'install' === action
|
case 'install':
|
||||||
? sprintf(
|
return sprintf(
|
||||||
__( 'There was an error installing %s. Please try again.', 'woocommerce-admin' ),
|
__( 'There was an error installing %s. Please try again.', 'woocommerce-admin' ),
|
||||||
pluginName
|
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' ),
|
__( 'There was an error activating %s. Please try again.', 'woocommerce-admin' ),
|
||||||
pluginName
|
pluginName
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function installPlugins( resourceNames, data, fetch ) {
|
function installPlugins( resourceNames, data, fetch ) {
|
||||||
const resourceName = 'plugin-install';
|
const resourceName = 'plugin-install';
|
||||||
|
|
||||||
if ( resourceNames.includes( resourceName ) ) {
|
if ( resourceNames.includes( resourceName ) ) {
|
||||||
const plugins = data[ resourceName ];
|
const plugins = data[ resourceName ];
|
||||||
return doPluginActions( fetch, 'install', plugins );
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
return plugins.map( async plugin => {
|
||||||
}
|
return fetch( {
|
||||||
|
path: `${ NAMESPACE }/onboarding/plugins/install`,
|
||||||
function activatePlugins( resourceNames, data, fetch ) {
|
method: 'POST',
|
||||||
const resourceName = 'plugin-activate';
|
data: {
|
||||||
|
plugin,
|
||||||
if ( resourceNames.includes( resourceName ) ) {
|
},
|
||||||
const plugins = data[ resourceName ];
|
} )
|
||||||
return doPluginActions( fetch, 'activate', plugins );
|
.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 [];
|
return [];
|
||||||
|
|
|
@ -77,6 +77,32 @@ const getPluginInstallations = getResource => plugins => {
|
||||||
return installations;
|
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 getPluginActivations = getResource => plugins => {
|
||||||
const resourceName = 'plugin-activate';
|
const resourceName = 'plugin-activate';
|
||||||
|
|
||||||
|
@ -146,6 +172,9 @@ export default {
|
||||||
getJetpackConnectUrl,
|
getJetpackConnectUrl,
|
||||||
getJetpackConnectUrlError,
|
getJetpackConnectUrlError,
|
||||||
isGetJetpackConnectUrlRequesting,
|
isGetJetpackConnectUrlRequesting,
|
||||||
|
getActivePlugins,
|
||||||
|
getActivePluginsError,
|
||||||
|
isGetActivePluginsRequesting,
|
||||||
getPluginActivations,
|
getPluginActivations,
|
||||||
getPluginInstallations,
|
getPluginInstallations,
|
||||||
getPluginInstallationErrors,
|
getPluginInstallationErrors,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Admin\API;
|
namespace Automattic\WooCommerce\Admin\API;
|
||||||
|
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||||
|
|
||||||
defined( 'ABSPATH' ) || exit;
|
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(
|
register_rest_route(
|
||||||
$this->namespace,
|
$this->namespace,
|
||||||
'/' . $this->rest_base . '/activate',
|
'/' . $this->rest_base . '/activate',
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
'methods' => \WP_REST_Server::EDITABLE,
|
'methods' => \WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( $this, 'activate_plugin' ),
|
'callback' => array( $this, 'activate_plugins' ),
|
||||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||||
),
|
),
|
||||||
'schema' => array( $this, 'get_item_schema' ),
|
'schema' => array( $this, 'get_item_schema' ),
|
||||||
|
@ -115,20 +129,6 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
|
||||||
return true;
|
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.
|
* Installs the requested plugin.
|
||||||
*
|
*
|
||||||
|
@ -136,7 +136,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
|
||||||
* @return array Plugin Status
|
* @return array Plugin Status
|
||||||
*/
|
*/
|
||||||
public function install_plugin( $request ) {
|
public function install_plugin( $request ) {
|
||||||
$allowed_plugins = $this->get_allowed_plugins();
|
$allowed_plugins = Onboarding::get_allowed_plugins();
|
||||||
$plugin = sanitize_title_with_dashes( $request['plugin'] );
|
$plugin = sanitize_title_with_dashes( $request['plugin'] );
|
||||||
if ( ! in_array( $plugin, array_keys( $allowed_plugins ), true ) ) {
|
if ( ! in_array( $plugin, array_keys( $allowed_plugins ), true ) ) {
|
||||||
return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 );
|
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.
|
* Activate the requested plugin.
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Full details about the request.
|
* @param WP_REST_Request $request Full details about the request.
|
||||||
* @return array Plugin Status
|
* @return array Plugin Status
|
||||||
*/
|
*/
|
||||||
public function activate_plugin( $request ) {
|
public function activate_plugins( $request ) {
|
||||||
$allowed_plugins = $this->get_allowed_plugins();
|
$allowed_plugins = Onboarding::get_allowed_plugins();
|
||||||
$plugin = sanitize_title_with_dashes( $request['plugin'] );
|
$_plugins = explode( ',', $request['plugins'] );
|
||||||
if ( ! in_array( $plugin, array_keys( $allowed_plugins ), true ) ) {
|
$plugins = array_intersect( array_keys( $allowed_plugins ), $_plugins );
|
||||||
return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 );
|
|
||||||
|
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';
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
|
|
||||||
$slug = $plugin;
|
foreach( $plugins as $plugin ) {
|
||||||
$path = $allowed_plugins[ $slug ];
|
$slug = $plugin;
|
||||||
$installed_plugins = get_plugins();
|
$path = $allowed_plugins[ $slug ];
|
||||||
|
$installed_plugins = get_plugins();
|
||||||
|
|
||||||
if ( ! in_array( $path, array_keys( $installed_plugins ), true ) ) {
|
if ( ! in_array( $path, array_keys( $installed_plugins ), true ) ) {
|
||||||
return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'Invalid plugin.', 'woocommerce-admin' ), 404 );
|
return new \WP_Error( 'woocommerce_rest_invalid_plugin', sprintf( __( 'Invalid plugin %s.', 'woocommerce-admin' ), $slug ), 404 );
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = activate_plugin( $path );
|
$result = activate_plugin( $path );
|
||||||
if ( ! is_null( $result ) ) {
|
if ( ! is_null( $result ) ) {
|
||||||
return new \WP_Error( 'woocommerce_rest_invalid_plugin', __( 'The requested plugin could not be activated.', 'woocommerce-admin' ), 500 );
|
return new \WP_Error( 'woocommerce_rest_invalid_plugin', sprintf( __( 'The requested plugins could not be activated.', 'woocommerce-admin' ), $slug ), 500 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return( array(
|
return( array(
|
||||||
'slug' => $slug,
|
'activatedPlugins' => array_values( $plugins ),
|
||||||
'name' => $installed_plugins[ $path ]['Name'],
|
'active' => Onboarding::get_active_plugins(),
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
|
@ -231,7 +248,7 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
|
||||||
* @return array Connection URL for Jetpack
|
* @return array Connection URL for Jetpack
|
||||||
*/
|
*/
|
||||||
public function connect_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 );
|
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.
|
// Only fetch if the onboarding wizard is incomplete.
|
||||||
if ( $this->should_show_profiler() ) {
|
if ( $this->should_show_profiler() ) {
|
||||||
$settings['onboarding']['productTypes'] = self::get_allowed_product_types();
|
$settings['onboarding']['productTypes'] = self::get_allowed_product_types();
|
||||||
$settings['onboarding']['themes'] = self::get_themes();
|
$settings['onboarding']['themes'] = self::get_themes();
|
||||||
$settings['onboarding']['activeTheme'] = get_option( 'stylesheet' );
|
$settings['onboarding']['activeTheme'] = get_option( 'stylesheet' );
|
||||||
|
$settings['onboarding']['activePlugins'] = self::get_active_plugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $settings;
|
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.
|
* 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