Implement API for Premium plugin installation and activation
[MAILPOET-2431]
This commit is contained in:
committed by
Jack Kitterhing
parent
757d09ac19
commit
4e75bcd296
@ -7,15 +7,6 @@ import MssMessages from 'settings/premium_tab/messages/mss_messages.jsx';
|
|||||||
import { PremiumStatus, PremiumMessages } from 'settings/premium_tab/messages/premium_messages.jsx';
|
import { PremiumStatus, PremiumMessages } from 'settings/premium_tab/messages/premium_messages.jsx';
|
||||||
import { PremiumInstallationStatus } from 'settings/premium_tab/messages/premium_installation_messages.jsx';
|
import { PremiumInstallationStatus } from 'settings/premium_tab/messages/premium_installation_messages.jsx';
|
||||||
|
|
||||||
const request = async (url) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(url);
|
|
||||||
return response.ok;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestServicesApi = async (key, action) => MailPoet.Ajax.post({
|
const requestServicesApi = async (key, action) => MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: 'services',
|
endpoint: 'services',
|
||||||
@ -23,6 +14,12 @@ const requestServicesApi = async (key, action) => MailPoet.Ajax.post({
|
|||||||
data: { key },
|
data: { key },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const requestPremiumApi = async (action) => MailPoet.Ajax.post({
|
||||||
|
api_version: window.mailpoet_api_version,
|
||||||
|
endpoint: 'premium',
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
|
||||||
const activateMss = async (key) => MailPoet.Ajax.post({
|
const activateMss = async (key) => MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: 'settings',
|
endpoint: 'settings',
|
||||||
@ -47,8 +44,6 @@ const PremiumTab = (props) => {
|
|||||||
const [mssKeyValid, setMssKeyValid] = useState(key ? props.mssKeyValid : null);
|
const [mssKeyValid, setMssKeyValid] = useState(key ? props.mssKeyValid : null);
|
||||||
const [mssKeyMessage, setMssKeyMessage] = useState(null);
|
const [mssKeyMessage, setMssKeyMessage] = useState(null);
|
||||||
|
|
||||||
let premiumActivateUrl = props.premiumActivateUrl;
|
|
||||||
|
|
||||||
// key is considered valid if either Premium or MSS check passes
|
// key is considered valid if either Premium or MSS check passes
|
||||||
const keyValid = useMemo(() => {
|
const keyValid = useMemo(() => {
|
||||||
if (premiumStatus > PremiumStatus.KEY_INVALID || mssKeyValid) {
|
if (premiumStatus > PremiumStatus.KEY_INVALID || mssKeyValid) {
|
||||||
@ -64,7 +59,9 @@ const PremiumTab = (props) => {
|
|||||||
const errorStatus = isAfterInstall ? status.INSTALL_ACTIVATING_ERROR : status.ACTIVATE_ERROR;
|
const errorStatus = isAfterInstall ? status.INSTALL_ACTIVATING_ERROR : status.ACTIVATE_ERROR;
|
||||||
|
|
||||||
setPremiumInstallationStatus(activateStatus);
|
setPremiumInstallationStatus(activateStatus);
|
||||||
if (!await request(premiumActivateUrl)) {
|
try {
|
||||||
|
await requestPremiumApi('activatePlugin');
|
||||||
|
} catch (error) {
|
||||||
setPremiumInstallationStatus(errorStatus);
|
setPremiumInstallationStatus(errorStatus);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -73,15 +70,8 @@ const PremiumTab = (props) => {
|
|||||||
|
|
||||||
const installPremiumPlugin = async () => {
|
const installPremiumPlugin = async () => {
|
||||||
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING);
|
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING);
|
||||||
if (!await request(props.premiumInstallUrl)) {
|
|
||||||
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// refetch 'plugin_activate_url' since it's only set after installation
|
|
||||||
try {
|
try {
|
||||||
const response = await requestServicesApi(key, 'checkPremiumKey');
|
await requestPremiumApi('installPlugin');
|
||||||
premiumActivateUrl = response.meta.premium_activate_url;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING_ERROR);
|
setPremiumInstallationStatus(PremiumInstallationStatus.INSTALL_INSTALLING_ERROR);
|
||||||
return;
|
return;
|
||||||
@ -228,13 +218,10 @@ PremiumTab.propTypes = {
|
|||||||
premiumStatus: PropTypes.number.isRequired,
|
premiumStatus: PropTypes.number.isRequired,
|
||||||
mssKeyValid: PropTypes.bool.isRequired,
|
mssKeyValid: PropTypes.bool.isRequired,
|
||||||
premiumPluginActive: PropTypes.bool.isRequired,
|
premiumPluginActive: PropTypes.bool.isRequired,
|
||||||
premiumInstallUrl: PropTypes.string.isRequired,
|
|
||||||
premiumActivateUrl: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PremiumTab.defaultProps = {
|
PremiumTab.defaultProps = {
|
||||||
activationKey: null,
|
activationKey: null,
|
||||||
premiumActivateUrl: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const container = document.getElementById('settings-premium-tab');
|
const container = document.getElementById('settings-premium-tab');
|
||||||
@ -260,8 +247,6 @@ if (container) {
|
|||||||
premiumStatus={getPremiumStatus()}
|
premiumStatus={getPremiumStatus()}
|
||||||
mssKeyValid={window.mailpoet_mss_key_valid}
|
mssKeyValid={window.mailpoet_mss_key_valid}
|
||||||
premiumPluginActive={!!window.mailpoet_premium_version}
|
premiumPluginActive={!!window.mailpoet_premium_version}
|
||||||
premiumInstallUrl={window.mailpoet_premium_install_url}
|
|
||||||
premiumActivateUrl={window.mailpoet_premium_activate_url || null}
|
|
||||||
/>,
|
/>,
|
||||||
container
|
container
|
||||||
);
|
);
|
||||||
|
74
lib/API/JSON/v1/Premium.php
Normal file
74
lib/API/JSON/v1/Premium.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\API\JSON\v1;
|
||||||
|
|
||||||
|
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||||
|
use MailPoet\API\JSON\Error as APIError;
|
||||||
|
use MailPoet\Config\AccessControl;
|
||||||
|
use MailPoet\Config\ServicesChecker;
|
||||||
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
class Premium extends APIEndpoint {
|
||||||
|
const PREMIUM_PLUGIN_SLUG = 'mailpoet-premium';
|
||||||
|
const PREMIUM_PLUGIN_PATH = 'mailpoet-premium/mailpoet-premium.php';
|
||||||
|
|
||||||
|
public $permissions = [
|
||||||
|
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var ServicesChecker */
|
||||||
|
private $services_checker;
|
||||||
|
|
||||||
|
/** @var WPFunctions */
|
||||||
|
private $wp;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ServicesChecker $services_checker,
|
||||||
|
WPFunctions $wp
|
||||||
|
) {
|
||||||
|
$this->services_checker = $services_checker;
|
||||||
|
$this->wp = $wp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function installPlugin() {
|
||||||
|
$premium_key_valid = $this->services_checker->isPremiumKeyValid(false);
|
||||||
|
if (!$premium_key_valid) {
|
||||||
|
return $this->error($this->wp->__('Premium key is not valid.', 'mailpoet'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin_info = $this->wp->pluginsApi('plugin_information', [
|
||||||
|
'slug' => self::PREMIUM_PLUGIN_SLUG,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$plugin_info || $plugin_info instanceof WP_Error) {
|
||||||
|
return $this->error($this->wp->__('Error when installing MailPoet Premium plugin.', 'mailpoet'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin_info = (array)$plugin_info;
|
||||||
|
$result = $this->wp->installPlugin($plugin_info['download_link']);
|
||||||
|
if ($result !== true) {
|
||||||
|
return $this->error($this->wp->__('Error when installing MailPoet Premium plugin.', 'mailpoet'));
|
||||||
|
}
|
||||||
|
return $this->successResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activatePlugin() {
|
||||||
|
$premium_key_valid = $this->services_checker->isPremiumKeyValid(false);
|
||||||
|
if (!$premium_key_valid) {
|
||||||
|
return $this->error($this->wp->__('Premium key is not valid.', 'mailpoet'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->wp->activatePlugin(self::PREMIUM_PLUGIN_PATH);
|
||||||
|
if ($result !== null) {
|
||||||
|
return $this->error($this->wp->__('Error when activating MailPoet Premium plugin.', 'mailpoet'));
|
||||||
|
}
|
||||||
|
return $this->successResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function error($message) {
|
||||||
|
return $this->badRequest([
|
||||||
|
APIError::BAD_REQUEST => $message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -76,6 +76,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
|||||||
$container->autowire(\MailPoet\API\JSON\v1\Newsletters::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\Newsletters::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\API\JSON\v1\NewsletterLinks::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\NewsletterLinks::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\API\JSON\v1\NewsletterTemplates::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\NewsletterTemplates::class)->setPublic(true);
|
||||||
|
$container->autowire(\MailPoet\API\JSON\v1\Premium::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\API\JSON\v1\Segments::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\Segments::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\API\JSON\v1\SendingQueue::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\SendingQueue::class)->setPublic(true);
|
||||||
$container->autowire(\MailPoet\API\JSON\v1\Services::class)->setPublic(true);
|
$container->autowire(\MailPoet\API\JSON\v1\Services::class)->setPublic(true);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace MailPoet\WP;
|
namespace MailPoet\WP;
|
||||||
|
|
||||||
|
use Plugin_Upgrader;
|
||||||
|
use WP_Ajax_Upgrader_Skin;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
class Functions {
|
class Functions {
|
||||||
@ -562,6 +564,40 @@ class Functions {
|
|||||||
return wpautop($pee, $br);
|
return wpautop($pee, $br);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $action
|
||||||
|
* @param array|object $args
|
||||||
|
* @return object|array|WP_Error
|
||||||
|
*/
|
||||||
|
public function pluginsApi($action, $args = []) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||||
|
return plugins_api($action, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $package
|
||||||
|
* @param array $args
|
||||||
|
* @return bool|WP_Error
|
||||||
|
*/
|
||||||
|
public function installPlugin($package, $args = []) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
|
||||||
|
$upgrader = new Plugin_Upgrader(new WP_Ajax_Upgrader_Skin());
|
||||||
|
return $upgrader->install($package, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $plugin
|
||||||
|
* @param string $redirect
|
||||||
|
* @param bool $network_wide
|
||||||
|
* @param bool $silent
|
||||||
|
* @return WP_Error|null
|
||||||
|
*/
|
||||||
|
public function activatePlugin($plugin, $redirect = '', $network_wide = false, $silent = false) {
|
||||||
|
return activate_plugin($plugin, $redirect, $network_wide, $silent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $host
|
* @param string $host
|
||||||
* @return array|bool
|
* @return array|bool
|
||||||
|
@ -16,3 +16,38 @@ if (!class_exists(WooCommerce::class)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/function-stubs.php';
|
require_once __DIR__ . '/function-stubs.php';
|
||||||
|
|
||||||
|
// methods & classes for Premium plugin installation are required
|
||||||
|
// only when needed so we need to let PHPStan know about them
|
||||||
|
if (!function_exists('plugins_api')) {
|
||||||
|
/**
|
||||||
|
* @param string $action
|
||||||
|
* @param array|object $args
|
||||||
|
* @return object|array|WP_Error
|
||||||
|
*/
|
||||||
|
function plugins_api($action, $args) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(WP_Ajax_Upgrader_Skin::class)) {
|
||||||
|
// phpcs:ignore
|
||||||
|
class WP_Ajax_Upgrader_Skin {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(Plugin_Upgrader::class)) {
|
||||||
|
// phpcs:ignore
|
||||||
|
class Plugin_Upgrader {
|
||||||
|
public function __construct($skin = null) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $package
|
||||||
|
* @param array $args
|
||||||
|
* @return bool|WP_Error
|
||||||
|
*/
|
||||||
|
public function install($package, $args = []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
134
tests/unit/API/JSON/PremiumTest.php
Normal file
134
tests/unit/API/JSON/PremiumTest.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MailPoet\Test\API\JSON;
|
||||||
|
|
||||||
|
use Codeception\Stub\Expected;
|
||||||
|
use MailPoet\API\JSON\ErrorResponse;
|
||||||
|
use MailPoet\API\JSON\SuccessResponse;
|
||||||
|
use MailPoet\API\JSON\v1\Premium;
|
||||||
|
use MailPoet\Config\ServicesChecker;
|
||||||
|
use MailPoet\WP\Functions as WPFunctions;
|
||||||
|
|
||||||
|
class PremiumTest extends \MailPoetUnitTest {
|
||||||
|
public function testItInstallsPlugin() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'pluginsApi' => Expected::once([
|
||||||
|
'download_link' => 'https://some-download-link',
|
||||||
|
]),
|
||||||
|
'installPlugin' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->installPlugin();
|
||||||
|
expect($response)->isInstanceOf(SuccessResponse::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallationFailsWhenKeyInvalid() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'pluginsApi' => Expected::never(),
|
||||||
|
'installPlugin' => Expected::never(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->installPlugin();
|
||||||
|
expect($response)->isInstanceOf(ErrorResponse::class);
|
||||||
|
expect($response->getData()['errors'][0])->same([
|
||||||
|
'error' => 'bad_request',
|
||||||
|
'message' => 'Premium key is not valid.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallationFailsWhenNoPluginInfo() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'pluginsApi' => Expected::once(null),
|
||||||
|
'installPlugin' => Expected::never(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->installPlugin();
|
||||||
|
expect($response)->isInstanceOf(ErrorResponse::class);
|
||||||
|
expect($response->getData()['errors'][0])->same([
|
||||||
|
'error' => 'bad_request',
|
||||||
|
'message' => 'Error when installing MailPoet Premium plugin.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallationFailsOnError() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'pluginsApi' => Expected::once([
|
||||||
|
'download_link' => 'https://some-download-link',
|
||||||
|
]),
|
||||||
|
'installPlugin' => Expected::once(false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->installPlugin();
|
||||||
|
expect($response)->isInstanceOf(ErrorResponse::class);
|
||||||
|
expect($response->getData()['errors'][0])->same([
|
||||||
|
'error' => 'bad_request',
|
||||||
|
'message' => 'Error when installing MailPoet Premium plugin.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItActivatesPlugin() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'activatePlugin' => Expected::once(null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->activatePlugin();
|
||||||
|
expect($response)->isInstanceOf(SuccessResponse::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testActivationFailsWhenKeyInvalid() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, new WPFunctions());
|
||||||
|
$response = $premium->activatePlugin();
|
||||||
|
expect($response)->isInstanceOf(ErrorResponse::class);
|
||||||
|
expect($response->getData()['errors'][0])->same([
|
||||||
|
'error' => 'bad_request',
|
||||||
|
'message' => 'Premium key is not valid.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testActivationFailsOnError() {
|
||||||
|
$services_checker = $this->makeEmpty(ServicesChecker::class, [
|
||||||
|
'isPremiumKeyValid' => Expected::once(true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$wp = $this->make(WPFunctions::class, [
|
||||||
|
'activatePlugin' => Expected::once('error'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$premium = new Premium($services_checker, $wp);
|
||||||
|
$response = $premium->activatePlugin();
|
||||||
|
expect($response)->isInstanceOf(ErrorResponse::class);
|
||||||
|
expect($response->getData()['errors'][0])->same([
|
||||||
|
'error' => 'bad_request',
|
||||||
|
'message' => 'Error when activating MailPoet Premium plugin.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,6 @@
|
|||||||
var mailpoet_premium_key_valid = <%= json_encode(premium_key_valid) %>;
|
var mailpoet_premium_key_valid = <%= json_encode(premium_key_valid) %>;
|
||||||
var mailpoet_premium_plugin_installed = <%= json_encode(premium_plugin_installed) %>;
|
var mailpoet_premium_plugin_installed = <%= json_encode(premium_plugin_installed) %>;
|
||||||
var mailpoet_mss_key_valid = <%= json_encode(mss_key_valid) %>;
|
var mailpoet_mss_key_valid = <%= json_encode(mss_key_valid) %>;
|
||||||
var mailpoet_premium_install_url = <%= json_encode(premium_install_url) %>;
|
|
||||||
var mailpoet_premium_activate_url = <%= json_encode(premium_activate_url) %>;
|
|
||||||
<% endautoescape %>
|
<% endautoescape %>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user