Add Premium key validation [PREMIUM-4]

This commit is contained in:
Alexey Stoletniy
2017-05-03 12:20:13 +03:00
parent 1379bdbbeb
commit 0fbc7fb7eb
12 changed files with 505 additions and 10 deletions

View File

@ -64,4 +64,57 @@ class Services extends APIEndpoint {
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
}
function verifyPremiumKey($data = array()) {
$key = isset($data['key']) ? trim($data['key']) : null;
if(!$key) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
));
}
try {
$result = $this->bridge->checkPremiumKey($key);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
$state = !empty($result['state']) ? $result['state'] : null;
$success_message = null;
if($state == Bridge::PREMIUM_KEY_VALID) {
$success_message = __('Your Premium API key is valid!', 'mailpoet');
} elseif($state == Bridge::PREMIUM_KEY_EXPIRING) {
$success_message = sprintf(
__('Your Premium key expires on %s!', 'mailpoet'),
Carbon::createFromTimestamp(strtotime($result['data']['expire_at']))
->format('Y-m-d')
);
}
if($success_message) {
return $this->successResponse(array('message' => $success_message));
}
switch($state) {
case Bridge::PREMIUM_KEY_INVALID:
$error = __('Your Premium key is invalid!', 'mailpoet');
break;
case Bridge::PREMIUM_KEY_USED:
$error = __('Your Premium key is already used on another site!', 'mailpoet');
break;
default:
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
$error = sprintf(
__('Error validating Premium key, please try again later (code: %s)', 'mailpoet'),
$code
);
break;
}
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
}
}

View File

@ -3,7 +3,6 @@ namespace MailPoet\API\Endpoints\v1;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Mailer\Mailer as MailerConfig;
use MailPoet\Models\Setting;
use MailPoet\Services\Bridge;
@ -24,13 +23,8 @@ class Settings extends APIEndpoint {
foreach($settings as $name => $value) {
Setting::setValue($name, $value);
}
if(!empty($settings[MailerConfig::MAILER_CONFIG_SETTING_NAME]['mailpoet_api_key'])
&& Bridge::isMPSendingServiceEnabled()
) {
$bridge = new Bridge();
$result = $bridge->checkKey($settings[MailerConfig::MAILER_CONFIG_SETTING_NAME]['mailpoet_api_key']);
$bridge->updateSubscriberCount($result);
}
$bridge->onSettingsSave($settings);
return $this->successResponse(Setting::getAll());
}
}

View File

@ -15,6 +15,7 @@ use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
use MailPoet\Util\License\License;
use MailPoet\WP\DateTime;
use MailPoet\WP\Notice as WPNotice;
use MailPoet\WP\Readme;
@ -28,6 +29,7 @@ class Menu {
$subscribers_feature = new SubscribersFeature();
$this->subscribers_over_limit = $subscribers_feature->check();
$this->checkMailPoetAPIKey();
$this->checkPremiumKey();
}
function init() {
@ -308,6 +310,8 @@ class Menu {
'segments' => Segment::getSegmentsWithSubscriberCount(),
'cron_trigger' => CronTrigger::getAvailableMethods(),
'total_subscribers' => Subscriber::getTotalSubscribers(),
'premium_plugin_active' => License::getLicense(),
'premium_key_valid' => isset($this->premium_key_valid) ? $this->premium_key_valid : null,
'pages' => Pages::getAll(),
'flags' => $flags,
'current_user' => wp_get_current_user(),
@ -525,6 +529,15 @@ class Menu {
}
}
function checkPremiumKey(ServicesChecker $checker = null) {
if(self::isOnMailPoetAdminPage()) {
$show_notices = isset($_REQUEST['page'])
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
$checker = $checker ?: new ServicesChecker();
$this->premium_key_valid = $checker->isPremiumKeyValid($show_notices);
}
}
private function getLimitPerPage($model = null) {
if($model === null) {
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;

View File

@ -5,6 +5,7 @@ use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Services\Bridge;
use MailPoet\Util\Helpers;
use MailPoet\Util\License\License;
use MailPoet\WP\Notice as WPNotice;
if(!defined('ABSPATH')) exit;
@ -46,4 +47,44 @@ class ServicesChecker {
return true;
}
function isPremiumKeyValid($display_error_notice = true) {
if(!Bridge::isPremiumKeySpecified()) {
return null;
}
$premium_plugin_active = License::getLicense();
$result = Setting::getValue(Bridge::PREMIUM_KEY_STATE_SETTING_NAME);
if(empty($result['state']) || $result['state'] == Bridge::PREMIUM_KEY_VALID) {
return true;
}
if($result['state'] == Bridge::PREMIUM_KEY_INVALID
|| $result['state'] == Bridge::PREMIUM_KEY_USED
) {
$error = Helpers::replaceLinkTags(
__('Warning! Your License Key is either invalid or expired. [link]Renew your License now[/link] to enjoy automatic updates and Premium support.', 'mailpoet'),
'https://account.mailpoet.com'
);
if($premium_plugin_active && $display_error_notice) {
WPNotice::displayError($error);
}
return false;
} elseif($result['state'] == Bridge::PREMIUM_KEY_EXPIRING
&& !empty($result['data']['expire_at'])
) {
$date = date('Y-m-d', strtotime($result['data']['expire_at']));
$error = Helpers::replaceLinkTags(
__('Your License Key is expiring! Don\'t forget to [link]renew your license[/link] by %s to keep enjoying automatic updates and Premium support.', 'mailpoet'),
'https://account.mailpoet.com'
);
$error = sprintf($error, $date);
if($premium_plugin_active && $display_error_notice) {
WPNotice::displayWarning($error);
}
return true;
}
return false;
}
}

View File

@ -3,6 +3,7 @@ namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
use MailPoet\Cron\Workers\Bounce as BounceWorker;
use MailPoet\Cron\Workers\PremiumKeyCheck as PremiumKeyCheckWorker;
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
if(!defined('ABSPATH')) exit;
@ -50,6 +51,7 @@ class Daemon {
$this->executeScheduleWorker();
$this->executeQueueWorker();
$this->executeSendingServiceKeyCheckWorker();
$this->executePremiumKeyCheckWorker();
$this->executeBounceWorker();
} catch(\Exception $e) {
// continue processing, no need to handle errors
@ -87,6 +89,11 @@ class Daemon {
return $worker->process();
}
function executePremiumKeyCheckWorker() {
$worker = new PremiumKeyCheckWorker($this->timer);
return $worker->process();
}
function executeBounceWorker() {
$bounce = new BounceWorker($this->timer);
return $bounce->process();

View File

@ -5,6 +5,7 @@ use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
use MailPoet\Cron\Workers\Bounce as BounceWorker;
use MailPoet\Cron\Workers\PremiumKeyCheck as PremiumKeyCheckWorker;
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
use MailPoet\Mailer\MailerLog;
use MailPoet\Services\Bridge;
@ -32,12 +33,17 @@ class WordPress {
// sending service key check
$sskeycheck_due_queues = SendingServiceKeyCheckWorker::getAllDueQueues();
$sskeycheck_future_queues = SendingServiceKeyCheckWorker::getFutureQueues();
// premium key check
$premium_key_specified = Bridge::isPremiumKeySpecified();
$premium_keycheck_due_queues = PremiumKeyCheckWorker::getAllDueQueues();
$premium_keycheck_future_queues = PremiumKeyCheckWorker::getFutureQueues();
// check requirements for each worker
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached && !$sending_is_paused);
$bounce_sync_active = ($mp_sending_enabled && ($bounce_due_queues || !$bounce_future_queues));
$sending_service_key_check_active = ($mp_sending_enabled && ($sskeycheck_due_queues || !$sskeycheck_future_queues));
$premium_key_check_active = ($premium_key_specified && ($premium_keycheck_due_queues || !$premium_keycheck_future_queues));
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active);
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active || $premium_key_check_active);
}
static function cleanup() {

View File

@ -0,0 +1,146 @@
<?php
namespace MailPoet\Cron\Workers;
use Carbon\Carbon;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
class PremiumKeyCheck {
const TASK_TYPE = 'premium_key_check';
const UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT = 60;
public $timer;
public $bridge;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
}
function initApi() {
if(!$this->bridge) {
$this->bridge = new Bridge();
}
}
function process() {
if(!Bridge::isPremiumKeySpecified()) {
return false;
}
$this->initApi();
$scheduled_queues = self::getScheduledQueues();
$running_queues = self::getRunningQueues();
if(!$scheduled_queues && !$running_queues) {
self::schedule();
return false;
}
foreach($scheduled_queues as $i => $queue) {
$this->prepareQueue($queue);
}
foreach($running_queues as $i => $queue) {
$this->processQueue($queue);
}
return true;
}
static function schedule() {
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
->findMany();
if($already_scheduled) {
return false;
}
$queue = SendingQueue::create();
$queue->type = self::TASK_TYPE;
$queue->status = SendingQueue::STATUS_SCHEDULED;
$queue->priority = SendingQueue::PRIORITY_LOW;
$queue->scheduled_at = self::getNextRunDate();
$queue->newsletter_id = 0;
$queue->save();
return $queue;
}
function prepareQueue(SendingQueue $queue) {
$queue->status = null;
$queue->save();
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
return true;
}
function processQueue(SendingQueue $queue) {
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
try {
$premium_key = Setting::getValue(Bridge::PREMIUM_KEY_STATE_SETTING_NAME);
$result = $this->bridge->checkPremiumKey($premium_key);
} catch (\Exception $e) {
$result = false;
}
if(empty($result['code']) || $result['code'] == Bridge::CHECK_ERROR_UNAVAILABLE) {
// reschedule the check
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
$queue->scheduled_at = $scheduled_at->addMinutes(
self::UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT
);
$queue->save();
return false;
}
$queue->processed_at = current_time('mysql');
$queue->status = SendingQueue::STATUS_COMPLETED;
$queue->save();
return true;
}
static function getNextRunDate() {
$date = Carbon::createFromTimestamp(current_time('timestamp'));
// Random day of the next week
$date->setISODate($date->format('o'), $date->format('W') + 1, mt_rand(1, 7));
$date->startOfDay();
return $date;
}
static function getScheduledQueues($future = false) {
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
return SendingQueue::where('type', self::TASK_TYPE)
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
->findMany();
}
static function getRunningQueues() {
return SendingQueue::where('type', self::TASK_TYPE)
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->whereNull('deleted_at')
->whereNull('status')
->findMany();
}
static function getAllDueQueues() {
$scheduled_queues = self::getScheduledQueues();
$running_queues = self::getRunningQueues();
return array_merge((array)$scheduled_queues, (array)$running_queues);
}
static function getFutureQueues() {
return self::getScheduledQueues(true);
}
}

View File

@ -9,6 +9,7 @@ if(!defined('ABSPATH')) exit;
class Bridge {
const API_KEY_STATE_SETTING_NAME = 'mta.mailpoet_api_key_state';
const PREMIUM_KEY_STATE_SETTING_NAME = 'premium.premium_key_state';
const MAILPOET_KEY_VALID = 'valid';
const MAILPOET_KEY_INVALID = 'invalid';
@ -16,6 +17,13 @@ class Bridge {
const MAILPOET_KEY_CHECK_ERROR = 'check_error';
const PREMIUM_KEY_VALID = 'valid';
const PREMIUM_KEY_INVALID = 'invalid';
const PREMIUM_KEY_EXPIRING = 'expiring';
const PREMIUM_KEY_USED = 'used';
const PREMIUM_KEY_CHECK_ERROR = 'check_error';
const CHECK_ERROR_UNAVAILABLE = 503;
const CHECK_ERROR_UNKNOWN = 'unknown';
@ -31,6 +39,11 @@ class Bridge {
}
}
static function isPremiumKeySpecified() {
$key = Setting::getValue(self::PREMIUM_KEY_STATE_SETTING_NAME);
return !empty($key);
}
function initApi($api_key) {
if($this->api) {
$this->api->setKey($api_key);
@ -77,6 +90,50 @@ class Bridge {
return $state;
}
function checkPremiumKey($key) {
$this->initApi($key);
$result = $this->api->checkPremiumKey();
return $this->processPremiumKeyCheckResult($result);
}
function processPremiumKeyCheckResult(array $result) {
$state_map = array(
200 => self::PREMIUM_KEY_VALID,
401 => self::PREMIUM_KEY_INVALID,
402 => self::PREMIUM_KEY_USED
);
$update_settings = false;
if(!empty($result['code']) && isset($state_map[$result['code']])) {
if($result['code'] == self::PREMIUM_KEY_VALID
&& !empty($result['data']['expire_at'])
) {
$key_state = self::PREMIUM_KEY_EXPIRING;
} else {
$key_state = $state_map[$result['code']];
}
$update_settings = true;
} else {
$key_state = self::PREMIUM_KEY_CHECK_ERROR;
}
$state = array(
'state' => $key_state,
'data' => !empty($result['data']) ? $result['data'] : null,
'code' => !empty($result['code']) ? $result['code'] : self::CHECK_ERROR_UNKNOWN
);
if($update_settings) {
Setting::setValue(
self::PREMIUM_KEY_STATE_SETTING_NAME,
$state
);
}
return $state;
}
function updateSubscriberCount($result) {
if(!empty($result['state'])
&& ($result['state'] === self::MAILPOET_KEY_VALID
@ -93,4 +150,16 @@ class Bridge {
array('state' => self::MAILPOET_KEY_INVALID)
);
}
function onSettingsSave($settings) {
$api_key_set = !empty($settings[Mailer::MAILER_CONFIG_SETTING_NAME]['mailpoet_api_key']);
$premium_key_set = !empty($settings['premium']['premium_key']);
if($api_key_set && self::isMPSendingServiceEnabled()) {
$result = $this->checkKey($settings[Mailer::MAILER_CONFIG_SETTING_NAME]['mailpoet_api_key']);
$this->updateSubscriberCount($result);
}
if($premium_key_set) {
$this->checkPremiumKey($settings['premium']['premium_key']);
}
}
}

View File

@ -15,6 +15,7 @@ class API {
private $api_key;
public $url_me = 'https://bridge.mailpoet.com/api/v0/me';
public $url_premium = 'https://bridge.mailpoet.com/api/v0/premium';
public $url_messages = 'https://bridge.mailpoet.com/api/v0/messages';
public $url_bounces = 'https://bridge.mailpoet.com/api/v0/bounces/search';
public $url_stats = 'https://bridge.mailpoet.com/api/v0/stats';
@ -46,6 +47,28 @@ class API {
return array('code' => $code, 'data' => $body);
}
function checkPremiumKey() {
$result = $this->request(
$this->url_premium,
array('site' => home_url())
);
$code = wp_remote_retrieve_response_code($result);
switch($code) {
case 200:
if($body = wp_remote_retrieve_body($result)) {
$body = json_decode($body, true);
}
break;
default:
$body = null;
break;
}
return array('code' => $code, 'data' => $body);
}
function sendMessages($message_body) {
$result = $this->request(
$this->url_messages,

View File

@ -13,12 +13,19 @@ class MockAPI {
}
function checkKey() {
// if key begins with these codes, return them
// if a key begins with these codes, return them
$regex = '/^(401|402|503)/';
$code = preg_match($regex, $this->api_key, $m) ? $m[1] : 200;
return $this->processResponse($code);
}
function checkPremiumKey() {
// if a key begins with these codes, return them
$regex = '/^(401|402|503)/';
$code = preg_match($regex, $this->api_key, $m) ? $m[1] : 200;
return $this->processPremiumResponse($code);
}
function updateSubscriberCount($count) {
return true;
}
@ -47,4 +54,22 @@ class MockAPI {
return array('code' => $code, 'data' => $body);
}
private function processPremiumResponse($code) {
switch($code) {
case 200:
$body = array(
'expire_at' => Carbon::createFromTimestamp(current_time('timestamp'))
->addMonth()->format('c')
);
break;
case 401:
case 402:
default:
$body = null;
break;
}
return array('code' => $code, 'data' => $body);
}
}

View File

@ -19,6 +19,7 @@
<a class="nav-tab" href="#signup"><%= __('Sign-up Confirmation') %></a>
<a class="nav-tab" href="#mta"><%= __('Send With...') %></a>
<a class="nav-tab" href="#advanced"><%= __('Advanced') %></a>
<a class="nav-tab" href="#premium"><%= __('Premium') %></a>
</h2>
<!-- basics -->
@ -41,6 +42,11 @@
<% include 'settings/advanced.html' %>
</div>
<!-- premium -->
<div data-tab="premium" class="mailpoet_panel">
<% include 'settings/premium.html' %>
</div>
<p class="submit mailpoet_settings_submit" style="display:none;">
<input
type="submit"

112
views/settings/premium.html Normal file
View File

@ -0,0 +1,112 @@
<table class="form-table">
<tbody>
<!-- premium key -->
<tr>
<th scope="row">
<label for="mailpoet_premium_key">
<%= __('Premium License Key') %>
</label>
<p class="description">
<%= __('This key is used for automatic upgrades of your Premium features and access to support.') %>
</p>
</th>
<td>
<div>
<input
type="text"
class="regular-text"
id="mailpoet_premium_key"
name="premium[premium_key]"
value="<%=- settings.premium.premium_key -%>"
/>
<a
id="mailpoet_premium_key_verify"
class="button-secondary"
><%= __('Verify') %></a>
</div>
<div
class="mailpoet_premium_key_valid mailpoet_success"
<% if not(settings.premium.premium_key) or not(premium_key_valid) %>
style="display: none;"
<% endif %>
>
<%= __('Your license key has been successfully validated.') %>
</div>
<div
class="mailpoet_premium_key_invalid mailpoet_error"
<% if not(settings.premium.premium_key) or premium_key_valid %>
style="display: none;"
<% endif %>
>
<%= __('Your license key is invalid.') %>
</div>
<br/>
<div
<% if premium_plugin_active or not(premium_key_valid) %>
style="display: none;"
<% endif %>
>
<a class="button-primary" href="#"><%= __('Download Premium now.') %></a>
<span>
<%= __("[link]Read guide[/link] on how to install Premium.")
|replace({
'[link]': '<a target="_blank" href="http://beta.docs.mailpoet.com/article/194-instructions-to-install-the-premium-plugin">',
'[/link]': '</a>'
})
|raw
%>
</span>
</div>
</td>
</tr>
</tbody>
</table>
<script type="text/javascript">
jQuery(function($) {
$(function() {
// verifying premium key
$('#mailpoet_premium_key_verify').on('click', function() {
// get premium key
var key = $('#mailpoet_premium_key').val();
if(key.length === 0) {
// validation
return MailPoet.Notice.error(
'<%= __('Please specify a Premium key before validating it.') | escape('js') %>',
{ scroll: true }
);
}
$('.mailpoet_premium_key_valid, .mailpoet_premium_key_invalid').hide();
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'services',
action: 'verifyPremiumKey',
data: {
key: key
}
}).always(function() {
MailPoet.Modal.loading(false);
}).done(function(response) {
// Hide server error notices
$('.mailpoet_notice_server').hide();
MailPoet.Notice.success(
response.data.message,
{ scroll: true }
);
$('.mailpoet_premium_key_valid').show();
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
$('.mailpoet_premium_key_invalid').show();
}
});
});
});
});
</script>