* Initial refactor

* Update payment recommendations to use new endpoint data

* Make sure to run specs

* Fix test

* Delete old PaymentPlugins class

* Update url to point to woocommerce.com

* Remove unused displayable prop

* Move option name

* Fix js tests

* Add changelog
This commit is contained in:
louwie17 2021-11-16 09:57:23 -04:00 committed by GitHub
parent f0aebb8046
commit dc03c6fbb5
10 changed files with 281 additions and 465 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Dev
Update payment method recommendation to new woocommerce.com endpoint. #7913

View File

@ -10,11 +10,9 @@ import { EllipsisMenu, List, Pill } from '@woocommerce/components';
import { Text } from '@woocommerce/experimental';
import {
PLUGINS_STORE_NAME,
SETTINGS_STORE_NAME,
WCDataSelector,
Plugin,
WPDataSelectors,
OPTIONS_STORE_NAME,
PluginsStoreActions,
} from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import ExternalIcon from 'gridicons/dist/external';
@ -24,64 +22,23 @@ import { getAdminLink } from '@woocommerce/wc-admin-settings';
* Internal dependencies
*/
import './payment-recommendations.scss';
import { getCountryCode } from '../dashboard/utils';
import { createNoticesFromResponse } from '../lib/notices';
import { isWCPaySupported } from '~/tasks/fills/PaymentGatewaySuggestions/components/WCPay';
const SEE_MORE_LINK =
'https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=payments_recommendations';
const DISMISS_OPTION = 'woocommerce_setting_payments_recommendations_hidden';
type SettingsSelector = WPDataSelectors & {
getSettings: (
type: string
) => { general: { woocommerce_default_country?: string } };
};
type OptionsSelector = WPDataSelectors & {
getOption: ( option: string ) => boolean | string;
};
export function getPaymentRecommendationData(
select: WCDataSelector
): {
displayable: boolean;
recommendedPlugins?: Plugin[];
isLoading: boolean;
} {
const { getOption, isResolving: isResolvingOption } = select(
OPTIONS_STORE_NAME
) as OptionsSelector;
const { getSettings } = select( SETTINGS_STORE_NAME ) as SettingsSelector;
const { getRecommendedPlugins } = select( PLUGINS_STORE_NAME );
const { general: settings } = getSettings( 'general' );
const hidden = getOption( DISMISS_OPTION );
const countryCode =
settings && settings.woocommerce_default_country
? getCountryCode( settings.woocommerce_default_country )
: null;
const countrySupported = countryCode
? isWCPaySupported( countryCode )
: false;
const isRequestingOptions = isResolvingOption( 'getOption', [
DISMISS_OPTION,
] );
const displayable =
! isRequestingOptions && hidden !== 'yes' && countrySupported;
let plugins = null;
if ( displayable ) {
// don't get recommended plugins until it is displayable.
plugins = getRecommendedPlugins( 'payments' );
}
const isLoading =
isRequestingOptions ||
hidden === undefined ||
settings === undefined ||
plugins === undefined;
const plugins = getRecommendedPlugins( 'payments' );
const isLoading = plugins === undefined;
return {
displayable,
recommendedPlugins: plugins,
isLoading,
};
@ -95,14 +52,18 @@ const PaymentRecommendations: React.FC = () => {
const [ installingPlugin, setInstallingPlugin ] = useState< string | null >(
null
);
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
const { displayable, recommendedPlugins, isLoading } = useSelect(
const {
installAndActivatePlugins,
dismissRecommendedPlugins,
invalidateResolution,
}: PluginsStoreActions = useDispatch( PLUGINS_STORE_NAME );
const { createNotice } = useDispatch( 'core/notices' );
const { recommendedPlugins, isLoading } = useSelect(
getPaymentRecommendationData
);
const triggeredPageViewRef = useRef( false );
const shouldShowRecommendations =
displayable && recommendedPlugins && recommendedPlugins.length > 0;
recommendedPlugins && recommendedPlugins.length > 0;
useEffect( () => {
if (
@ -113,10 +74,10 @@ const PaymentRecommendations: React.FC = () => {
triggeredPageViewRef.current = true;
const eventProps = ( recommendedPlugins || [] ).reduce(
( props, plugin ) => {
if ( plugin.product ) {
if ( plugin.plugins && plugin.plugins.length > 0 ) {
return {
...props,
[ plugin.product.replace( /\-/g, '_' ) +
[ plugin.plugins[ 0 ].replace( /\-/g, '_' ) +
'_displayed' ]: true,
};
}
@ -136,23 +97,31 @@ const PaymentRecommendations: React.FC = () => {
if ( ! shouldShowRecommendations ) {
return null;
}
const dismissPaymentRecommendations = () => {
const dismissPaymentRecommendations = async () => {
recordEvent( 'settings_payments_recommendations_dismiss', {} );
updateOptions( {
[ DISMISS_OPTION ]: 'yes',
} );
const success = await dismissRecommendedPlugins( 'payments' );
if ( success ) {
invalidateResolution( 'getRecommendedPlugins', [ 'payments' ] );
} else {
createNotice(
'error',
__(
'There was a problem hiding the "Recommended ways to get paid" card.',
'woocommerce-admin'
)
);
}
};
const setupPlugin = ( plugin: Plugin ) => {
if ( installingPlugin ) {
return;
}
setInstallingPlugin( plugin.product );
setInstallingPlugin( plugin.id );
recordEvent( 'settings_payments_recommendations_setup', {
extension_selected: plugin.product,
extension_selected: plugin.plugins[ 0 ],
} );
installAndActivatePlugins( [ plugin.product ] )
installAndActivatePlugins( [ plugin.plugins[ 0 ] ] )
.then( () => {
window.location.href = getAdminLink(
plugin[ 'setup-link' ].replace( '/wp-admin/', '' )
@ -167,7 +136,7 @@ const PaymentRecommendations: React.FC = () => {
const pluginsList = ( recommendedPlugins || [] ).map(
( plugin: Plugin ) => {
return {
key: plugin.slug,
key: plugin.id,
title: (
<>
{ plugin.title }
@ -178,18 +147,18 @@ const PaymentRecommendations: React.FC = () => {
) }
</>
),
content: decodeEntities( plugin.copy ),
content: decodeEntities( plugin.content ),
after: (
<Button
isSecondary
onClick={ () => setupPlugin( plugin ) }
isBusy={ installingPlugin === plugin.product }
isBusy={ installingPlugin === plugin.id }
disabled={ !! installingPlugin }
>
{ plugin[ 'button-text' ] }
</Button>
),
before: <img src={ plugin.icon } alt="" />,
before: <img src={ plugin.image } alt="" />,
};
}
);

View File

@ -4,21 +4,12 @@
import { render, fireEvent, waitFor } from '@testing-library/react';
import { useSelect, useDispatch } from '@wordpress/data';
import { recordEvent } from '@woocommerce/tracks';
import {
PLUGINS_STORE_NAME,
SETTINGS_STORE_NAME,
OPTIONS_STORE_NAME,
WCDataStoreName,
Plugin,
} from '@woocommerce/data';
import { getAdminLink } from '@woocommerce/wc-admin-settings';
/**
* Internal dependencies
*/
import PaymentRecommendations, {
getPaymentRecommendationData,
} from '../payment-recommendations';
import PaymentRecommendations from '../payment-recommendations';
import { isWCPaySupported } from '../../tasks/fills/PaymentGatewaySuggestions/components/WCPay';
import { createNoticesFromResponse } from '~/lib/notices';
@ -74,7 +65,6 @@ jest.mock( '../../lib/notices', () => ( {
describe( 'Payment recommendations', () => {
it( 'should render nothing with no recommendedPlugins and country not defined', () => {
( useSelect as jest.Mock ).mockReturnValue( {
displayable: false,
recommendedPlugins: undefined,
} );
const { container } = render( <PaymentRecommendations /> );
@ -82,151 +72,12 @@ describe( 'Payment recommendations', () => {
expect( container.firstChild ).toBe( null );
} );
describe( 'getPaymentRecommendationData', () => {
const plugin = {
title: 'test',
slug: 'test',
product: 'test',
} as Plugin;
const baseSelectValues = {
plugins: undefined,
optionsResolving: false,
country: undefined,
optionValues: {},
};
const recommendedPluginsMock = jest.fn();
const createFakeSelect = ( data: {
optionsResolving?: boolean;
optionValues: Record< string, boolean | string >;
country?: string;
plugins?: Plugin[];
} ) => {
return jest
.fn()
.mockImplementation( ( storeName: WCDataStoreName ) => {
switch ( storeName ) {
case OPTIONS_STORE_NAME:
return {
isResolving: () => data.optionsResolving,
getOption: ( option: string ) =>
data.optionValues[ option ],
};
case SETTINGS_STORE_NAME:
return {
getSettings: () => ( {
general: {
woocommerce_default_country:
data.country,
},
} ),
};
case PLUGINS_STORE_NAME:
return {
getRecommendedPlugins: recommendedPluginsMock.mockReturnValue(
data.plugins
),
};
}
} );
};
it( 'should render nothing if the country is not supported', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( false );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'FR',
plugins: [ plugin ],
} )
);
expect( selectData.displayable ).toBe( false );
} );
it( 'should not call getRecommendedPlugins when displayable is false', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( false );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'FR',
plugins: [ plugin ],
} )
);
expect( selectData.displayable ).toBe( false );
expect( recommendedPluginsMock ).not.toHaveBeenCalled();
} );
it( 'should have displayable as true if country is supported', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'US',
plugins: [ plugin ],
optionValues: {},
} )
);
expect( selectData.displayable ).toBeTruthy();
} );
it( 'should have displayable as false if hidden is set to true', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'US',
plugins: [ plugin ],
optionValues: {
woocommerce_setting_payments_recommendations_hidden:
'yes',
},
} )
);
expect( selectData.displayable ).toBeFalsy();
} );
it( 'should set displayable to true if isHidden is not defined', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'US',
plugins: [ plugin ],
optionValues: {
woocommerce_setting_payments_recommendations_hidden:
'no',
},
} )
);
expect( selectData.displayable ).toBeTruthy();
} );
it( 'have displayable as false if still requesting options', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
const selectData = getPaymentRecommendationData(
createFakeSelect( {
...baseSelectValues,
country: 'US',
plugins: [ plugin ],
optionsResolving: true,
} )
);
expect( selectData.displayable ).toBeFalsy();
} );
} );
it( 'should render the list if displayable is true and has recommendedPlugins', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
( useSelect as jest.Mock ).mockReturnValue( {
displayable: true,
recommendedPlugins: [ { title: 'test', slug: 'test' } ],
recommendedPlugins: [
{ title: 'test', id: 'test', plugins: [ 'test' ] },
],
} );
const { container, getByText } = render( <PaymentRecommendations /> );
@ -252,9 +103,8 @@ describe( 'Payment recommendations', () => {
it( 'should trigger event payments_recommendations_pageview, when first rendered', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
( useSelect as jest.Mock ).mockReturnValue( {
displayable: true,
recommendedPlugins: [
{ title: 'test', slug: 'test', product: 'test' },
{ title: 'test', id: 'test', plugins: [ 'test' ] },
],
} );
const { container } = render( <PaymentRecommendations /> );
@ -272,9 +122,8 @@ describe( 'Payment recommendations', () => {
it( 'should set woocommerce-payments-displayed prop to true if pre install wc pay promotion gateway is displayed', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
( useSelect as jest.Mock ).mockReturnValue( {
displayable: true,
recommendedPlugins: [
{ title: 'test', slug: 'test', product: 'test' },
{ title: 'test', id: 'test', plugins: [ 'test' ] },
],
} );
const { container } = render(
@ -297,7 +146,6 @@ describe( 'Payment recommendations', () => {
it( 'should not render if there are no recommendedPlugins', () => {
( isWCPaySupported as jest.Mock ).mockReturnValue( true );
( useSelect as jest.Mock ).mockReturnValue( {
displayable: true,
recommendedPlugins: [],
} );
const { container } = render( <PaymentRecommendations /> );
@ -321,19 +169,18 @@ describe( 'Payment recommendations', () => {
installAndActivatePlugins: installAndActivateMock,
} );
( useSelect as jest.Mock ).mockReturnValue( {
displayable: true,
recommendedPlugins: [
{
title: 'test',
slug: 'test',
product: 'test-product',
id: 'test',
plugins: [ 'test-product' ],
'button-text': 'install',
'setup-link': '/wp-admin/random-link',
},
{
title: 'another',
slug: 'another',
product: 'another-product',
id: 'another',
plugins: [ 'another-product' ],
'button-text': 'install2',
'setup-link': '/wp-admin/random-link',
},

View File

@ -20,6 +20,7 @@ import {
PaypalOnboardingStatus,
PluginNames,
SelectorKeysWithActions,
RecommendedTypes,
} from './types';
// Can be removed in WP 5.9, wp.data is supported in >5.7.
@ -348,6 +349,36 @@ export function setRecommendedPlugins(
};
}
const SUPPORTED_TYPES = [ 'payments' ];
export function* dismissRecommendedPlugins( type: RecommendedTypes ) {
if ( ! SUPPORTED_TYPES.includes( type ) ) {
return [];
}
const plugins: Plugin[] = yield resolveSelect(
STORE_NAME,
'getRecommendedPlugins',
type
);
yield setRecommendedPlugins( type, [] );
let success: boolean;
try {
const url =
WC_ADMIN_NAMESPACE + '/plugins/recommended-payment-plugins/dismiss';
success = yield apiFetch( {
path: url,
method: 'POST',
} );
} catch ( error ) {
success = false;
}
if ( ! success ) {
// Reset recommended plugins
yield setRecommendedPlugins( type, plugins );
}
return success;
}
export type Actions =
| ReturnType< typeof updateActivePlugins >
| ReturnType< typeof updateInstalledPlugins >
@ -362,4 +393,5 @@ export type Actions =
export type ActionDispatchers = {
installJetpackAndConnect: typeof installJetpackAndConnect;
installAndActivatePlugins: typeof installAndActivatePlugins;
dismissRecommendedPlugins: typeof dismissRecommendedPlugins;
};

View File

@ -8,7 +8,7 @@
namespace Automattic\WooCommerce\Admin\API;
use Automattic\WooCommerce\Admin\Features\Onboarding;
use Automattic\WooCommerce\Admin\PaymentPlugins;
use Automattic\WooCommerce\Admin\PaymentMethodSuggestionsDataSourcePoller;
use Automattic\WooCommerce\Admin\PluginsHelper;
use \Automattic\WooCommerce\Admin\Notes\InstallJPAndWCSPlugins;
@ -103,6 +103,19 @@ class Plugins extends \WC_REST_Data_Controller {
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/recommended-payment-plugins/dismiss',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'dismiss_recommended_payment_plugins' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/connect-jetpack',
@ -435,40 +448,23 @@ class Plugins extends \WC_REST_Data_Controller {
* @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response
*/
public function recommended_payment_plugins( $request ) {
// Default to marketing category (if no category set).
$all_plugins = PaymentPlugins::get_instance()->get_recommended_plugins();
$valid_plugins = [];
$per_page = $request->get_param( 'per_page' );
// We currently only support English suggestions, unless otherwise provided in locale-data.
$locale = get_locale();
$suggestion_locales = array(
'en_AU',
'en_CA',
'en_GB',
'en_NZ',
'en_US',
'en_ZA',
);
foreach ( $all_plugins as $plugin ) {
if ( ! PluginsHelper::is_plugin_active( $plugin['product'] ) ) {
if ( isset( $plugin['locale-data'] ) && isset( $plugin['locale-data'][ $locale ] ) ) {
$locale_plugin = array_merge( $plugin, $plugin['locale-data'][ $locale ] );
unset( $locale_plugin['locale-data'] );
$valid_plugins[] = $locale_plugin;
$suggestion_locales[] = $locale;
} else {
$valid_plugins[] = $plugin;
}
}
}
if ( ! in_array( $locale, $suggestion_locales, true ) ) {
// If not a supported locale we return an empty array.
if ( get_option( PaymentMethodSuggestionsDataSourcePoller::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION, 'no' ) === 'yes' ) {
return rest_ensure_response( array() );
}
$all_plugins = PaymentMethodSuggestionsDataSourcePoller::get_instance()->get_suggestions();
$per_page = $request->get_param( 'per_page' );
return rest_ensure_response( array_slice( $valid_plugins, 0, $per_page ) );
return rest_ensure_response( array_slice( $all_plugins, 0, $per_page ) );
}
/**
* Dismisses recommended payment plugins.
*
* @return \WP_Error|\WP_HTTP_Response|\WP_REST_Response
*/
public function dismiss_recommended_payment_plugins() {
$success = update_option( PaymentMethodSuggestionsDataSourcePoller::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION, 'yes' );
return rest_ensure_response( $success );
}
/**

View File

@ -19,6 +19,11 @@ abstract class DataSourcePoller {
*/
const FILTER_NAME = 'data_source_poller_data_sources';
/**
* Name of data source specs filter.
*/
const FILTER_NAME_SPECS = 'data_source_poller_specs';
/**
* Id of DataSourcePoller.
*
@ -105,6 +110,7 @@ abstract class DataSourcePoller {
$this->read_specs_from_data_sources();
$specs = get_transient( $this->args['transient_name'] );
}
$specs = apply_filters( self::FILTER_NAME_SPECS, $specs, $this->id );
return false !== $specs ? $specs : array();
}

View File

@ -7,9 +7,10 @@ namespace Automattic\WooCommerce\Admin\Features\WcPayPromotion;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\DataSourcePoller;
use Automattic\WooCommerce\Admin\Loader;
use Automattic\WooCommerce\Admin\PaymentPlugins;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
use Automattic\WooCommerce\Admin\PaymentMethodSuggestionsDataSourcePoller;
/**
* WC Pay Promotion engine.
@ -24,7 +25,7 @@ class Init {
include_once __DIR__ . '/WCPaymentGatewayPreInstallWCPayPromotion.php';
add_action( 'change_locale', array( __CLASS__, 'delete_specs_transient' ) );
add_filter( PaymentPlugins::FILTER_NAME, array( __CLASS__, 'possibly_filter_recommended_payment_gateways' ) );
add_filter( DataSourcePoller::FILTER_NAME_SPECS, array( __CLASS__, 'possibly_filter_recommended_payment_gateways' ), 10, 2 );
if ( ! isset( $_GET['page'] ) || 'wc-settings' !== $_GET['page'] || ! isset( $_GET['tab'] ) || 'checkout' !== $_GET['tab'] ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
@ -71,19 +72,20 @@ class Init {
/**
* Possibly filters out woocommerce-payments from recommended payment methods.
*
* @param array $payment_methods list of payment methods.
* @param array $specs list of payment methods.
* @param string $datasource_poller_id id of data source poller.
* @return array list of payment method.
*/
public static function possibly_filter_recommended_payment_gateways( $payment_methods ) {
if ( self::should_register_pre_install_wc_pay_promoted_gateway() ) {
public static function possibly_filter_recommended_payment_gateways( $specs, $datasource_poller_id ) {
if ( PaymentMethodSuggestionsDataSourcePoller::ID === $datasource_poller_id && self::should_register_pre_install_wc_pay_promoted_gateway() ) {
return array_filter(
$payment_methods,
function( $payment_method ) {
return 'woocommerce-payments' !== $payment_method['product'];
$specs,
function( $spec ) {
return 'woocommerce-payments' !== $spec->plugins[0];
}
);
}
return $payment_methods;
return $specs;
}
/**

View File

@ -0,0 +1,91 @@
<?php
namespace Automattic\WooCommerce\Admin;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
/**
* Specs data source poller class for payment gateway suggestions.
*/
class PaymentMethodSuggestionsDataSourcePoller extends DataSourcePoller {
const ID = 'payment_method_suggestions';
/**
* Option name for dismissed payment method suggestions.
*/
const RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION = 'woocommerce_setting_payments_recommendations_hidden';
/**
* Default data sources array.
*/
const DATA_SOURCES = array(
'https://woocommerce.com/wp-json/wccom/payment-gateway-suggestions/1.0/payment-method/suggestions.json',
);
/**
* Class instance.
*
* @var Analytics instance
*/
protected static $instance = null;
/**
* Get class instance.
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self( self::ID, self::DATA_SOURCES );
}
return self::$instance;
}
/**
* Gets the payment method suggestions after validating the specs.
*
* @return array visible specs.
*/
public function get_suggestions() {
if ( ! $this->allow_recommendations() ) {
return array();
}
$suggestions = array();
$specs = $this->get_specs_from_data_sources();
foreach ( $specs as $spec ) {
$suggestion = EvaluateSuggestion::evaluate( $spec );
$suggestions[] = $suggestion;
}
return array_values(
array_filter(
$suggestions,
function( $suggestion ) {
return ! property_exists( $suggestion, 'is_visible' ) || $suggestion->is_visible;
}
)
);
}
/**
* Should recommendations be displayed?
*
* @return bool
*/
private function allow_recommendations() {
// Suggestions are only displayed if user can install plugins.
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
// Suggestions may be disabled via a setting under Accounts & Privacy.
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
return false;
}
// User can disabled all suggestions via filter.
return apply_filters( 'woocommerce_allow_payment_recommendations', true );
}
}

View File

@ -1,119 +0,0 @@
<?php
/**
* WooCommerce Payment methods.
* NOTE: DO NOT edit this file in WooCommerce core, this is generated from woocommerce-admin.
*/
namespace Automattic\WooCommerce\Admin;
/**
* Contains backend logic for retrieving payment plugin recommendations.
*/
class PaymentPlugins {
/**
* Name of recommended plugins filter.
*/
const FILTER_NAME = 'woocommerce_admin_recommended_payment_plugins';
/**
* Name of recommended plugins transient.
*
* @var string
*/
const RECOMMENDED_PLUGINS_TRANSIENT = 'wc_recommended_payment_plugins';
/**
* Class instance.
*
* @var PaymentPlugins instance
*/
protected static $instance = null;
/**
* Get class instance.
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Load recommended payment plugins from WooCommerce.com
*
* @return array
*/
public function get_recommended_plugins() {
if ( ! self::allow_recommendations() ) {
return array();
}
$plugins_data = get_transient( self::RECOMMENDED_PLUGINS_TRANSIENT );
if ( false === $plugins_data ) {
include_once ABSPATH . '/wp-admin/includes/plugin-install.php';
$url = 'https://woocommerce.com/wp-json/wccom/marketplace-suggestions/1.0/payment-suggestions.json';
$request = wp_safe_remote_get( $url );
$plugins = [];
if ( ! is_wp_error( $request ) && 200 === $request['response']['code'] ) {
$plugins = json_decode( $request['body'], true );
}
foreach ( $plugins as $key => $plugin ) {
if ( ! array_key_exists( 'copy', $plugins[ $key ] ) ) {
$api = plugins_api(
'plugin_information',
array(
'slug' => $plugin['product'],
'fields' => array(
'short_description' => true,
),
)
);
if ( is_wp_error( $api ) ) {
continue;
}
$plugins[ $key ]['copy'] = $api->short_description;
}
}
$plugins_data = array(
'recommendations' => $plugins,
'updated' => time(),
);
set_transient(
self::RECOMMENDED_PLUGINS_TRANSIENT,
$plugins_data,
// Expire transient in 15 minutes if remote get failed.
// Cache an empty result to avoid repeated failed requests.
empty( $plugins ) ? 900 : 3 * DAY_IN_SECONDS
);
}
return apply_filters( self::FILTER_NAME, array_values( $plugins_data['recommendations'] ) );
}
/**
* Should recommendations be displayed?
*
* @return bool
*/
private function allow_recommendations() {
// Suggestions are only displayed if user can install plugins.
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
// Suggestions may be disabled via a setting under Accounts & Privacy.
if ( 'no' === get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) ) {
return false;
}
// User can disabled all suggestions via filter.
return apply_filters( 'woocommerce_allow_payment_recommendations', true );
}
}

View File

@ -6,6 +6,7 @@
*/
use \Automattic\WooCommerce\Admin\API\Plugins;
use Automattic\WooCommerce\Admin\PaymentMethodSuggestionsDataSourcePoller;
/**
* WC Tests API Plugins
@ -116,13 +117,11 @@ class WC_Tests_API_Plugins extends WC_REST_Unit_Test_Case {
public function test_get_recommended_payment_plugins() {
wp_set_current_user( $this->user );
set_transient(
\Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT,
'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs',
array(
'recommendations' => array(
array(
'product' => 'plugin',
'title' => 'test',
),
(object) array(
'plugins' => array( 'plugin' ),
'title' => 'test',
),
)
);
@ -132,71 +131,8 @@ class WC_Tests_API_Plugins extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'plugin', $data[0]['product'] );
delete_transient( \Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT );
}
/**
* Test that recommended payment plugins with locale data.
*/
public function test_get_recommended_payment_plugins_with_locale() {
wp_set_current_user( $this->user );
add_filter( 'locale', array( $this, 'set_france_locale' ) );
set_transient(
\Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT,
array(
'recommendations' => array(
array(
'product' => 'plugin',
'title' => 'test',
'locale-data' => array(
'fr_FR' => array(
'title' => 'translated title',
),
),
),
),
)
);
$request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended-payment-plugins' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'plugin', $data[0]['product'] );
$this->assertEquals( 'translated title', $data[0]['title'] );
$this->assertEquals( false, isset( $data[0]['locale-data'] ) );
delete_transient( \Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT );
remove_filter( 'locale', array( $this, 'set_france_locale' ) );
}
/**
* Test that recommended payment plugins with not default supported locale.
*/
public function test_get_recommended_payment_plugins_with_not_supported_locale() {
wp_set_current_user( $this->user );
add_filter( 'locale', array( $this, 'set_france_locale' ) );
set_transient(
\Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT,
array(
'recommendations' => array(
array(
'product' => 'plugin',
'title' => 'test',
),
),
)
);
$request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended-payment-plugins' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
// Return nothing as default is only english locales.
$this->assertEquals( 0, count( $data ) );
delete_transient( \Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT );
remove_filter( 'locale', array( $this, 'set_france_locale' ) );
$this->assertEquals( 'plugin', $data[0]->plugins[0] );
delete_transient( 'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs' );
}
/**
@ -211,14 +147,23 @@ class WC_Tests_API_Plugins extends WC_REST_Unit_Test_Case {
*/
public function test_get_recommended_payment_plugins_that_are_active() {
wp_set_current_user( $this->user );
update_option( 'active_plugins', array( 'facebook-for-woocommerce/facebook-for-woocommerce.php' ) );
update_option( 'active_plugins', array( 'woocommerce-gateway-stripe/woocommerce-gateway-stripe.php' ) );
set_transient(
\Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT,
'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs',
array(
'recommendations' => array(
array(
'product' => 'facebook-for-woocommerce',
'title' => 'test',
(object) array(
'plugins' => array( 'woocommerce-gateway-stripe' ),
'title' => 'test',
'is_visible' => array(
(object) array(
'type' => 'not',
'operand' => array(
(object) array(
'type' => 'plugins_activated',
'plugins' => array( 'woocommerce-gateway-stripe' ),
),
),
),
),
),
)
@ -229,7 +174,50 @@ class WC_Tests_API_Plugins extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 0, count( $data ) );
delete_transient( \Automattic\WooCommerce\Admin\PaymentPlugins::RECOMMENDED_PLUGINS_TRANSIENT );
delete_transient( 'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs' );
delete_option( 'active_plugins' );
}
/**
* Test that recommended payment plugins are not returned when active.
*/
public function test_plugins_when_dismissed_is_set_to_yes() {
wp_set_current_user( $this->user );
update_option( 'woocommerce_setting_payments_recommendations_hidden', 'yes' );
set_transient(
'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs',
array(
(object) array(
'plugins' => array( 'plugin' ),
'title' => 'test',
),
)
);
$request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended-payment-plugins' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 0, count( $data ) );
delete_transient( 'woocommerce_admin_' . PaymentMethodSuggestionsDataSourcePoller::ID . '_specs' );
delete_option( 'woocommerce_setting_payments_recommendations_hidden' );
}
/**
* Test dismissing recommended payment plugins endpoint.
*/
public function test_dismiss_recommended_payment_plugins() {
$this->assertEquals( 'no', get_option( 'woocommerce_setting_payments_recommendations_hidden', 'no' ) );
wp_set_current_user( $this->user );
$request = new WP_REST_Request( 'POST', $this->endpoint . '/recommended-payment-plugins/dismiss' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( true, $data );
$this->assertEquals( 'yes', get_option( 'woocommerce_setting_payments_recommendations_hidden' ) );
delete_option( 'woocommerce_setting_payments_recommendations_hidden' );
}
}