Reorganized new API + added legacy API support + new API

- Updated Settings Router to new standards
- Updated settings.html to reflect API change with better error handling
- Updated Settings API unit tests
This commit is contained in:
Jonathan Labreuille
2016-08-01 16:57:53 +02:00
parent 354d249e1d
commit 9410d4f10a
26 changed files with 216 additions and 93 deletions

View File

@ -8,9 +8,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery,
endpoint: null,
action: null,
token: null,
data: {},
onSuccess: function(data, textStatus, xhr) {},
onError: function(xhr, textStatus, errorThrown) {}
data: {}
},
get: function(options) {
return this.request('get', options);
@ -72,9 +70,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery,
url: this.options.url,
type : 'post',
data: params,
dataType: 'json',
success : this.options.onSuccess,
error : this.options.onError
dataType: 'json'
});
}

View File

@ -35,14 +35,34 @@ class API {
}
function setupAdmin() {
$this->verifyToken();
$this->checkPermissions();
return $this->processRoute();
if($this->checkToken() === false) {
$this->errorResponse(
array('unauthorized' => __('This request is not authorized.')),
array(),
APIResponse::STATUS_UNAUTHORIZED
)->send();
}
if($this->checkPermissions() === false) {
$this->errorResponse(
array('forbidden' => __('You do not have the required permissions.')),
array(),
APIResponse::STATUS_FORBIDDEN
)->send();
}
$this->processRoute();
}
function setupPublic() {
$this->verifyToken();
return $this->processRoute();
if($this->checkToken() === false) {
$response = new ErrorResponse(array(
'unauthorized' => __('This request is not authorized.')
), APIResponse::STATUS_UNAUTHORIZED);
$response->send();
}
$this->processRoute();
}
function processRoute() {
@ -72,9 +92,18 @@ class API {
try {
$endpoint = new $endpoint();
$response = $endpoint->$method($data);
wp_send_json($response);
// TODO: remove this condition once the API unification is complete
if(is_object($response)) {
$response->send();
} else {
// LEGACY API
wp_send_json($response);
}
} catch(\Exception $e) {
exit;
$this->errorResponse(array(
$e->getMessage()
))->send();
}
}
@ -86,18 +115,32 @@ class API {
}
function checkPermissions() {
if(!current_user_can('manage_options')) {
die();
}
return current_user_can('manage_options');
}
function verifyToken() {
if(
empty($_POST['token'])
||
!wp_verify_nonce($_POST['token'], 'mailpoet_token')
) {
die();
}
function checkToken() {
return (
isset($_POST['token'])
&&
wp_verify_nonce($_POST['token'], 'mailpoet_token')
);
}
}
function successResponse(
$data = array(), $meta = array(), $status = APIResponse::STATUS_OK
) {
return new SuccessResponse($data, $meta, $status);
}
function errorResponse(
$errors = array(), $meta = array(), $status = APIResponse::STATUS_NOT_FOUND
) {
return new ErrorResponse($errors, $meta, $status);
}
function badRequest($errors = array(), $meta = array()) {
return new ErrorResponse($errors, $meta, APIResponse::STATUS_BAD_REQUEST);
}
}

88
lib/API/APIResponse.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace MailPoet\API;
abstract class APIResponse {
const STATUS_OK = 200;
const STATUS_BAD_REQUEST = 400;
const STATUS_UNAUTHORIZED = 401;
const STATUS_FORBIDDEN = 403;
const STATUS_NOT_FOUND = 404;
public $status;
public $meta;
function __construct($status, $meta = array()) {
$this->status = $status;
$this->meta = $meta;
}
function send() {
status_header($this->status);
$data = $this->getData();
$response = array();
if(!empty($this->meta)) {
$response['meta'] = $this->meta;
}
if(!empty($data)) {
$response = array_merge($response, $data);
}
if(empty($response)) {
die();
} else {
wp_send_json($response);
}
}
abstract function getData();
}
class SuccessResponse extends APIResponse {
public $data;
function __construct($data = array(), $meta = array(), $status = self::STATUS_OK) {
parent::__construct($status, $meta);
$this->data = $data;
}
function getData() {
if(empty($this->data)) {
return false;
} else {
return array(
'data' => $this->data
);
}
}
}
class ErrorResponse extends APIResponse {
public $errors;
function __construct($errors = array(), $meta = array(), $status = self::STATUS_NOT_FOUND) {
parent::__construct($status, $meta);
$this->errors = $this->formatErrors($errors);
}
function getData() {
if(empty($this->errors)) {
return false;
} else {
return array(
'errors' => $this->errors
);
}
}
function formatErrors($errors = array()) {
$formatted_errors = array();
foreach($errors as $error => $message) {
$formatted_errors[] = array(
'error' => $error,
'message' => $message
);
}
return $formatted_errors;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
if(!defined('ABSPATH')) exit;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Supervisor;

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use \MailPoet\Models\CustomField;

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use \MailPoet\Models\Form;
use \MailPoet\Models\StatisticsForms;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
use MailPoet\Models\CustomField;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
if(!defined('ABSPATH')) exit;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Models\NewsletterTemplate;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Listing;
use MailPoet\Models\Newsletter;

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use \MailPoet\Models\Segment;
use \MailPoet\Models\SubscriberSegment;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;

View File

@ -1,27 +1,26 @@
<?php
namespace MailPoet\API;
use \MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Settings {
class Settings extends API {
function __construct() {
}
function get() {
$settings = Setting::getAll();
return $settings;
return $this->successResponse($settings);
}
function set($settings = array()) {
if(empty($settings)) {
return false;
return $this->badRequest();
} else {
foreach($settings as $name => $value) {
Setting::setValue($name, $value);
}
return true;
return $this->successResponse();
}
}
}

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use \MailPoet\Config\Activator;

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
namespace MailPoet\API
;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;

View File

@ -1,7 +1,6 @@
<?php
use \MailPoet\API
\CustomFields;
use \MailPoet\API\CustomFields;
use \MailPoet\Models\CustomField;
class CustomFieldsTest extends MailPoetTest {

View File

@ -1,6 +1,5 @@
<?php
use \MailPoet\API
\Forms;
use \MailPoet\API\Forms;
use \MailPoet\Models\Form;
use \MailPoet\Models\Segment;

View File

@ -1,6 +1,5 @@
<?php
use \MailPoet\API
\NewsletterTemplates;
use \MailPoet\API\NewsletterTemplates;
use \MailPoet\Models\NewsletterTemplate;
class NewsletterTemplatesTest extends MailPoetTest {

View File

@ -1,6 +1,5 @@
<?php
use \MailPoet\API
\Newsletters;
use \MailPoet\API\Newsletters;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\NewsletterSegment;
use \MailPoet\Models\NewsletterTemplate;

View File

@ -1,6 +1,5 @@
<?php
use \MailPoet\API
\Segments;
use \MailPoet\API\Segments;
use \MailPoet\Models\Segment;
class SegmentsTest extends MailPoetTest {

View File

@ -1,6 +1,6 @@
<?php
use \MailPoet\API
\Settings;
use \MailPoet\API\APIResponse;
use \MailPoet\API\Settings;
use \MailPoet\Models\Setting;
class SettingsTest extends MailPoetTest {
@ -11,13 +11,16 @@ class SettingsTest extends MailPoetTest {
function testItCanGetSettings() {
$router = new Settings();
$settings = $router->get();
expect($settings)->notEmpty();
expect($settings['some']['setting']['key'])->true();
$response = $router->get();
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->notEmpty();
expect($response->data['some']['setting']['key'])->true();
Setting::deleteMany();
$settings = $router->get();
expect($settings)->equals(Setting::getDefaults());
$response = $router->get();
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->equals(Setting::getDefaults());
}
function testItCanSetSettings() {
@ -33,16 +36,16 @@ class SettingsTest extends MailPoetTest {
$router = new Settings();
$response = $router->set(/* missing data */);
expect($response)->false();
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
$response = $router->set($new_settings);
expect($response)->true();
expect($response->status)->equals(APIResponse::STATUS_OK);
$settings = $router->get();
expect($settings['some']['setting'])->hasntKey('key');
expect($settings['some']['setting']['new_key'])->true();
expect($settings['some']['new_setting'])->true();
$response = $router->get();
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data['some']['setting'])->hasntKey('key');
expect($response->data['some']['setting']['new_key'])->true();
expect($response->data['some']['new_setting'])->true();
}
function _after() {

View File

@ -1,6 +1,5 @@
<?php
use \MailPoet\API
\Setup;
use \MailPoet\API\Setup;
use \MailPoet\Models\Setting;
class SetupTest extends MailPoetTest {

View File

@ -1,7 +1,6 @@
<?php
use \MailPoet\API
\Subscribers;
use \MailPoet\API\Subscribers;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\Segment;

View File

@ -77,25 +77,21 @@
endpoint: 'settings',
action: 'set',
data: settings_data
}).done(function(response) {
if(response === true) {
MailPoet.Notice.success(
"<%= __('Settings saved') %>",
{ scroll: true }
);
} else {
}).done(function() {
MailPoet.Notice.success(
"<%= __('Settings saved') %>",
{ scroll: true }
);
MailPoet.Modal.loading(false);
}).error(function(xhr) {
var response = xhr.responseJSON;
if(response.errors !== undefined) {
MailPoet.Notice.error(
"<%= __('Settings could not be saved') %>",
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
MailPoet.Modal.loading(false);
}).error(function(errors) {
MailPoet.Notice.error(
"<%= __('An error occurred. Please check the settings.') %>",
{ scroll: true }
);
MailPoet.Modal.loading(false);
});
}

View File

@ -41,7 +41,8 @@
type="radio"
name="task_scheduler[method]"
value="WordPress"
<% if (settings.task_scheduler.method == 'WordPress') %>
<% if not(settings.task_scheduler.method)
or (settings.task_scheduler.method == 'WordPress') %>
checked="checked"
<% endif %>
/><%= __('Visitors to your website (recommended)') %>