diff --git a/assets/js/src/ajax.js b/assets/js/src/ajax.js index 3878a3582a..e76f3ad76d 100644 --- a/assets/js/src/ajax.js +++ b/assets/js/src/ajax.js @@ -5,6 +5,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, options: {}, defaults: { url: null, + api_version: null, endpoint: null, action: null, token: null, @@ -30,6 +31,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, getParams: function() { return { action: 'mailpoet', + api_version: this.options.api_version, token: this.options.token, endpoint: this.options.endpoint, method: this.options.action, diff --git a/assets/js/src/form/form.jsx b/assets/js/src/form/form.jsx index ca61b30d8c..bfffe4eb86 100644 --- a/assets/js/src/form/form.jsx +++ b/assets/js/src/form/form.jsx @@ -64,6 +64,7 @@ define( this.setState({ loading: true }); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'get', data: { @@ -112,6 +113,7 @@ define( } MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'save', data: item diff --git a/assets/js/src/forms/list.jsx b/assets/js/src/forms/list.jsx index 43d3c7f261..979374c58e 100644 --- a/assets/js/src/forms/list.jsx +++ b/assets/js/src/forms/list.jsx @@ -97,6 +97,7 @@ const item_actions = [ label: MailPoet.I18n.t('duplicate'), onClick: function(item, refresh) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'forms', action: 'duplicate', data: { @@ -125,6 +126,7 @@ const item_actions = [ const FormList = React.createClass({ createForm() { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'forms', action: 'create' }).done((response) => { diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx index 0af6825e7f..2c28bf5cbb 100644 --- a/assets/js/src/listing/listing.jsx +++ b/assets/js/src/listing/listing.jsx @@ -445,6 +445,7 @@ const Listing = React.createClass({ this.clearSelection(); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'listing', data: { @@ -495,6 +496,7 @@ const Listing = React.createClass({ }); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'restore', data: { @@ -522,6 +524,7 @@ const Listing = React.createClass({ }); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'trash', data: { @@ -549,6 +552,7 @@ const Listing = React.createClass({ }); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'delete', data: { @@ -611,6 +615,7 @@ const Listing = React.createClass({ } return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: this.props.endpoint, action: 'bulkAction', data: data diff --git a/assets/js/src/newsletter_editor/components/communication.js b/assets/js/src/newsletter_editor/components/communication.js index 56368103dc..2b765b8144 100644 --- a/assets/js/src/newsletter_editor/components/communication.js +++ b/assets/js/src/newsletter_editor/components/communication.js @@ -9,6 +9,7 @@ define([ Module._query = function(args) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'automatedLatestContent', action: args.action, data: args.options || {} @@ -81,6 +82,7 @@ define([ Module.saveNewsletter = function(options) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'save', data: options || {} @@ -89,6 +91,7 @@ define([ Module.previewNewsletter = function(options) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'sendPreview', data: options || {} diff --git a/assets/js/src/newsletter_editor/components/save.js b/assets/js/src/newsletter_editor/components/save.js index e09d592455..2c45fa0ba2 100644 --- a/assets/js/src/newsletter_editor/components/save.js +++ b/assets/js/src/newsletter_editor/components/save.js @@ -109,6 +109,7 @@ define([ }); return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletterTemplates', action: 'save', data: data diff --git a/assets/js/src/newsletter_editor/components/sidebar.js b/assets/js/src/newsletter_editor/components/sidebar.js index 4419b7bf05..cbc57def62 100644 --- a/assets/js/src/newsletter_editor/components/sidebar.js +++ b/assets/js/src/newsletter_editor/components/sidebar.js @@ -243,6 +243,7 @@ define([ MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'showPreview', data: json, diff --git a/assets/js/src/newsletters/listings/mixins.jsx b/assets/js/src/newsletters/listings/mixins.jsx index 3973ff9e65..98555a8815 100644 --- a/assets/js/src/newsletters/listings/mixins.jsx +++ b/assets/js/src/newsletters/listings/mixins.jsx @@ -8,6 +8,7 @@ import jQuery from 'jquery' const _QueueMixin = { pauseSending: function(newsletter) { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'sendingQueue', action: 'pause', data: { @@ -27,6 +28,7 @@ const _QueueMixin = { }, resumeSending: function(newsletter) { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'sendingQueue', action: 'resume', data: { @@ -225,6 +227,7 @@ const _MailerMixin = { }, resumeMailerSending() { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'mailer', action: 'resumeSending' }).done(function() { diff --git a/assets/js/src/newsletters/listings/notification.jsx b/assets/js/src/newsletters/listings/notification.jsx index 1f59fc36d4..256bcf4046 100644 --- a/assets/js/src/newsletters/listings/notification.jsx +++ b/assets/js/src/newsletters/listings/notification.jsx @@ -129,6 +129,7 @@ const newsletter_actions = [ label: MailPoet.I18n.t('duplicate'), onClick: function(newsletter, refresh) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'duplicate', data: { @@ -164,6 +165,7 @@ const NewsletterListNotification = React.createClass({ e.persist(); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'setStatus', data: { diff --git a/assets/js/src/newsletters/listings/standard.jsx b/assets/js/src/newsletters/listings/standard.jsx index 1c56229585..f399df617e 100644 --- a/assets/js/src/newsletters/listings/standard.jsx +++ b/assets/js/src/newsletters/listings/standard.jsx @@ -124,6 +124,7 @@ const newsletter_actions = [ label: MailPoet.I18n.t('duplicate'), onClick: function(newsletter, refresh) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'duplicate', data: { diff --git a/assets/js/src/newsletters/listings/welcome.jsx b/assets/js/src/newsletters/listings/welcome.jsx index bae199461f..ac4d269089 100644 --- a/assets/js/src/newsletters/listings/welcome.jsx +++ b/assets/js/src/newsletters/listings/welcome.jsx @@ -126,6 +126,7 @@ const newsletter_actions = [ label: MailPoet.I18n.t('duplicate'), onClick: function(newsletter, refresh) { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'duplicate', data: { @@ -161,6 +162,7 @@ const NewsletterListWelcome = React.createClass({ e.persist(); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'setStatus', data: { diff --git a/assets/js/src/newsletters/send.jsx b/assets/js/src/newsletters/send.jsx index 1874b12513..7ccc781b94 100644 --- a/assets/js/src/newsletters/send.jsx +++ b/assets/js/src/newsletters/send.jsx @@ -64,6 +64,7 @@ define( this.setState({ loading: true }); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'get', data: { @@ -97,6 +98,7 @@ define( case 'notification': case 'welcome': return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'setStatus', data: { @@ -120,6 +122,7 @@ define( }).fail(this._showError); default: return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'sendingQueue', action: 'add', data: { @@ -184,6 +187,7 @@ define( ); return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'save', data: newsletterData, diff --git a/assets/js/src/newsletters/send/notification.jsx b/assets/js/src/newsletters/send/notification.jsx index 539616778c..d66f637f4a 100644 --- a/assets/js/src/newsletters/send/notification.jsx +++ b/assets/js/src/newsletters/send/notification.jsx @@ -37,8 +37,9 @@ define( tip: MailPoet.I18n.t('segmentsTip'), type: 'selection', placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'), - id: "mailpoet_segments", - endpoint: "segments", + id: 'mailpoet_segments', + api_version: window.mailpoet_api_version, + endpoint: 'segments', multiple: true, filter: function(segment) { return !!(!segment.deleted_at); diff --git a/assets/js/src/newsletters/send/standard.jsx b/assets/js/src/newsletters/send/standard.jsx index fcf5ece48b..a8187521b8 100644 --- a/assets/js/src/newsletters/send/standard.jsx +++ b/assets/js/src/newsletters/send/standard.jsx @@ -341,8 +341,9 @@ define( tip: MailPoet.I18n.t('segmentsTip'), type: 'selection', placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'), - id: "mailpoet_segments", - endpoint: "segments", + id: 'mailpoet_segments', + api_version: window.mailpoet_api_version, + endpoint: 'segments', multiple: true, filter: function(segment) { return !!(!segment.deleted_at); diff --git a/assets/js/src/newsletters/templates.jsx b/assets/js/src/newsletters/templates.jsx index 5ef22ff2a6..f292c5a0ff 100644 --- a/assets/js/src/newsletters/templates.jsx +++ b/assets/js/src/newsletters/templates.jsx @@ -27,6 +27,7 @@ define( MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletterTemplates', action: 'save', data: template @@ -97,6 +98,7 @@ define( MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletterTemplates', action: 'getAll', }).always(() => { @@ -130,6 +132,7 @@ define( } MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'save', data: { @@ -158,6 +161,7 @@ define( ) ) { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletterTemplates', action: 'delete', data: { diff --git a/assets/js/src/newsletters/types.jsx b/assets/js/src/newsletters/types.jsx index 65cadff270..dca520ac69 100644 --- a/assets/js/src/newsletters/types.jsx +++ b/assets/js/src/newsletters/types.jsx @@ -22,6 +22,7 @@ define( }, createNewsletter: function(type) { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'create', data: { diff --git a/assets/js/src/newsletters/types/notification/notification.jsx b/assets/js/src/newsletters/types/notification/notification.jsx index 591eea0955..10210b7a02 100644 --- a/assets/js/src/newsletters/types/notification/notification.jsx +++ b/assets/js/src/newsletters/types/notification/notification.jsx @@ -44,6 +44,7 @@ define( }, handleNext: function() { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'create', data: _.extend({}, this.state, { diff --git a/assets/js/src/newsletters/types/standard.jsx b/assets/js/src/newsletters/types/standard.jsx index 5762902826..9d776a0c66 100644 --- a/assets/js/src/newsletters/types/standard.jsx +++ b/assets/js/src/newsletters/types/standard.jsx @@ -22,6 +22,7 @@ define( componentDidMount: function() { // No options for this type, create a newsletter upon mounting MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'create', data: { diff --git a/assets/js/src/newsletters/types/welcome/scheduling.jsx b/assets/js/src/newsletters/types/welcome/scheduling.jsx index c7e1fa2418..d43593514d 100644 --- a/assets/js/src/newsletters/types/welcome/scheduling.jsx +++ b/assets/js/src/newsletters/types/welcome/scheduling.jsx @@ -104,6 +104,7 @@ const WelcomeScheduling = React.createClass({ }, handleNext: function() { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'create', data: { diff --git a/assets/js/src/newsletters/types/welcome/welcome.jsx b/assets/js/src/newsletters/types/welcome/welcome.jsx index 18bb917065..78facf4009 100644 --- a/assets/js/src/newsletters/types/welcome/welcome.jsx +++ b/assets/js/src/newsletters/types/welcome/welcome.jsx @@ -52,6 +52,7 @@ define( }, handleNext: function() { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'create', data: _.extend({}, this.state, { diff --git a/assets/js/src/public.js b/assets/js/src/public.js index ca97653ed9..4b3d570603 100644 --- a/assets/js/src/public.js +++ b/assets/js/src/public.js @@ -31,7 +31,7 @@ function( }); form.parsley().on('form:submit', function(parsley) { - var data = form.serializeObject() || {}; + var form_data = form.serializeObject() || {}; // check if we're on the same domain if(isSameDomain(MailPoetForm.ajax_url) === false) { // non ajax post request @@ -40,10 +40,11 @@ function( // ajax request MailPoet.Ajax.post({ url: MailPoetForm.ajax_url, - token: data.token, + token: form_data.token, + api_version: form_data.api_version, endpoint: 'subscribers', action: 'subscribe', - data: data + data: form_data.data }).fail(function(response) { form.find('.mailpoet_validate_error').html( response.errors.map(function(error) { diff --git a/assets/js/src/segments/list.jsx b/assets/js/src/segments/list.jsx index cfc15bc4f1..f27ebfacef 100644 --- a/assets/js/src/segments/list.jsx +++ b/assets/js/src/segments/list.jsx @@ -112,6 +112,7 @@ const item_actions = [ label: MailPoet.I18n.t('duplicate'), onClick: (item, refresh) => { return MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'segments', action: 'duplicate', data: { @@ -153,6 +154,7 @@ const item_actions = [ onClick: function(item, refresh) { MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'segments', action: 'synchronize' }).done(function(response) { diff --git a/assets/js/src/subscribers/form.jsx b/assets/js/src/subscribers/form.jsx index cdccd7e310..7e5964b798 100644 --- a/assets/js/src/subscribers/form.jsx +++ b/assets/js/src/subscribers/form.jsx @@ -60,7 +60,8 @@ define( label: MailPoet.I18n.t('lists'), type: 'selection', placeholder: MailPoet.I18n.t('selectList'), - endpoint: "segments", + api_version: window.mailpoet_api_version, + endpoint: 'segments', multiple: true, selected: function(subscriber) { if (Array.isArray(subscriber.subscriptions) === false) { diff --git a/assets/js/src/subscribers/importExport/export.js b/assets/js/src/subscribers/importExport/export.js index 07989cec42..f17c78273a 100644 --- a/assets/js/src/subscribers/importExport/export.js +++ b/assets/js/src/subscribers/importExport/export.js @@ -138,6 +138,7 @@ define( } MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'ImportExport', action: 'processExport', data: JSON.stringify({ diff --git a/assets/js/src/subscribers/importExport/import.js b/assets/js/src/subscribers/importExport/import.js index d2ae4da01d..e263ed90be 100644 --- a/assets/js/src/subscribers/importExport/import.js +++ b/assets/js/src/subscribers/importExport/import.js @@ -190,6 +190,7 @@ define( mailChimpKeyVerifyButtonElement.click(function () { MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'importExport', action: 'getMailChimpLists', data: { @@ -225,6 +226,7 @@ define( } MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'importExport', action: 'getMailChimpSubscribers', data: { @@ -572,6 +574,7 @@ define( var segmentDescription = jQuery('#new_segment_description').val().trim(); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'ImportExport', action: 'addSegment', data: { @@ -729,6 +732,7 @@ define( // save custom field MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'customFields', action: 'save', data: data @@ -990,6 +994,7 @@ define( queue.add(function(queue) { queue.pause(); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'ImportExport', action: 'processImport', data: JSON.stringify({ diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx index 7f3d1dd279..cb263a5501 100644 --- a/assets/js/src/subscribers/list.jsx +++ b/assets/js/src/subscribers/list.jsx @@ -91,6 +91,7 @@ const bulk_actions = [ onSelect: function() { let field = { id: 'move_to_segment', + api_version: window.mailpoet_api_version, endpoint: 'segments', filter: function(segment) { return !!( @@ -122,6 +123,7 @@ const bulk_actions = [ onSelect: function() { let field = { id: 'add_to_segment', + api_version: window.mailpoet_api_version, endpoint: 'segments', filter: function(segment) { return !!( @@ -153,6 +155,7 @@ const bulk_actions = [ onSelect: function() { let field = { id: 'remove_from_segment', + api_version: window.mailpoet_api_version, endpoint: 'segments', filter: function(segment) { return !!( @@ -255,7 +258,7 @@ const SubscriberList = React.createClass({ case 'unsubscribed': status = MailPoet.I18n.t('unsubscribed'); break; - + case 'bounced': status = MailPoet.I18n.t('bounced'); break; diff --git a/lib/API/API.php b/lib/API/API.php index 030ffdd408..d03247684e 100644 --- a/lib/API/API.php +++ b/lib/API/API.php @@ -1,6 +1,7 @@ addEndpointNamespace(__NAMESPACE__ . "\\Endpoints"); + foreach($this->_available_api_versions as $available_api_version) { + $this->addEndpointNamespace( + sprintf('%s\%s\%s', __NAMESPACE__, self::ENDPOINTS_LOCATION, $available_api_version), + $available_api_version + ); + } } function init() { - // Admin Security token + // admin security token and API version add_action( 'admin_head', - array($this, 'setToken') + array($this, 'setTokenAndAPIVersion') ); // ajax (logged in users) @@ -42,73 +53,67 @@ class API { function setupAjax() { Hooks::doAction('mailpoet_api_setup', array($this)); - - $this->getRequestData($_POST); + $this->setRequestData($_POST); if($this->checkToken() === false) { - $error_response = new ErrorResponse( - array( - Error::UNAUTHORIZED => __('Invalid request', 'mailpoet') - ), - array(), - Response::STATUS_UNAUTHORIZED - ); - $error_response->send(); + $error_message = __('Invalid API request.', 'mailpoet'); + $error_response = $this->createErrorResponse(Error::UNAUTHORIZED, $error_message, Response::STATUS_UNAUTHORIZED); + return $error_response->send(); } $response = $this->processRoute(); $response->send(); } - function getRequestData($data) { - $this->_endpoint = isset($data['endpoint']) + function setRequestData($data) { + $this->_request_api_version = !empty($data['api_version']) ? $data['api_version']: false; + + $this->_request_endpoint = isset($data['endpoint']) ? Helpers::underscoreToCamelCase(trim($data['endpoint'])) : null; // JS part of /wp-admin/customize.php does not like a 'method' field in a form widget $method_param_name = isset($data['mailpoet_method']) ? 'mailpoet_method' : 'method'; - $this->_method = isset($data[$method_param_name]) + $this->_request_method = isset($data[$method_param_name]) ? Helpers::underscoreToCamelCase(trim($data[$method_param_name])) : null; - $this->_token = isset($data['token']) + $this->_request_token = isset($data['token']) ? trim($data['token']) : null; - if(!$this->_endpoint || !$this->_method) { - // throw exception bad request - $error_response = new ErrorResponse( - array( - Error::BAD_REQUEST => __('Invalid request', 'mailpoet') - ), - array(), - Response::STATUS_BAD_REQUEST - ); - $error_response->send(); - } else { - foreach($this->_endpoint_namespaces as $namespace) { - $class_name = $namespace . "\\" . ucfirst($this->_endpoint); - if(class_exists($class_name)) { - $this->_endpoint_class = $class_name; + if(!$this->_request_endpoint || !$this->_request_method || !$this->_request_api_version) { + $error_message = __('Invalid API request.', 'mailpoet'); + $error_response = $this->createErrorResponse(Error::BAD_REQUEST, $error_message, Response::STATUS_BAD_REQUEST); + return $error_response; + } else if(!empty($this->_endpoint_namespaces[$this->_request_api_version])) { + foreach($this->_endpoint_namespaces[$this->_request_api_version] as $namespace) { + $endpoint_class = sprintf( + '%s\%s', + $namespace, + ucfirst($this->_request_endpoint) + ); + if(class_exists($endpoint_class)) { + $this->_request_endpoint_class = $endpoint_class; } } - - $this->_data = isset($data['data']) + $this->_request_data = isset($data['data']) ? stripslashes_deep($data['data']) : array(); // remove reserved keywords from data - if(is_array($this->_data) && !empty($this->_data)) { + if(is_array($this->_request_data) && !empty($this->_request_data)) { // filter out reserved keywords from data $reserved_keywords = array( 'token', 'endpoint', 'method', + 'api_version', 'mailpoet_method', // alias of 'method' 'mailpoet_redirect' ); - $this->_data = array_diff_key( - $this->_data, + $this->_request_data = array_diff_key( + $this->_request_data, array_flip($reserved_keywords) ); } @@ -117,67 +122,79 @@ class API { function processRoute() { try { - if(empty($this->_endpoint_class)) { - throw new \Exception('Invalid endpoint'); + if(empty($this->_request_endpoint_class)) { + throw new \Exception(__('Invalid API endpoint.', 'mailpoet')); } - $endpoint = new $this->_endpoint_class(); + $endpoint = new $this->_request_endpoint_class(); // check the accessibility of the requested endpoint's action // by default, an endpoint's action is considered "private" $permissions = $endpoint->permissions; - if( - array_key_exists($this->_method, $permissions) === false - || - $permissions[$this->_method] !== Access::ALL + if(array_key_exists($this->_request_method, $permissions) === false || + $permissions[$this->_request_method] !== Access::ALL ) { if($this->checkPermissions() === false) { - $error_response = new ErrorResponse( - array( - Error::FORBIDDEN => __( - 'You do not have the required permissions.', - 'mailpoet' - ) - ), - array(), - Response::STATUS_FORBIDDEN - ); + $error_message = __('You do not have the required permissions.', 'mailpoet'); + $error_response = $this->createErrorResponse(Error::FORBIDDEN, $error_message, Response::STATUS_FORBIDDEN); return $error_response; } } - $response = $endpoint->{$this->_method}($this->_data); + $response = $endpoint->{$this->_request_method}($this->_request_data); return $response; } catch(\Exception $e) { - $error_response = new ErrorResponse( - array($e->getCode() => $e->getMessage()) - ); + $error_message = $e->getMessage(); + $error_response = $this->createErrorResponse(Error::BAD_REQUEST, $error_message, Response::STATUS_BAD_REQUEST); return $error_response; } } function checkPermissions() { - return current_user_can('manage_options'); + return current_user_can(Env::$required_permission); } function checkToken() { - return wp_verify_nonce($this->_token, 'mailpoet_token'); + return wp_verify_nonce($this->_request_token, 'mailpoet_token'); } - function setToken() { + function setTokenAndAPIVersion() { $global = ''; - echo $global; + echo sprintf( + $global, + Security::generateToken(), + self::CURRENT_VERSION + ); } - function addEndpointNamespace($namespace) { - $this->_endpoint_namespaces[] = $namespace; + function addEndpointNamespace($namespace, $version) { + if(!empty($this->_endpoint_namespaces[$version][$namespace])) return; + $this->_endpoint_namespaces[$version][] = $namespace; } function getEndpointNamespaces() { return $this->_endpoint_namespaces; } -} + + function getRequestedEndpointClass() { + return $this->_request_endpoint_class; + } + + function getRequestedAPIVersion() { + return $this->_request_api_version; + } + + function createErrorResponse($error_type, $error_message, $response_status) { + $error_response = new ErrorResponse( + array( + $error_type => $error_message + ), + array(), + $response_status + ); + return $error_response; + } +} \ No newline at end of file diff --git a/lib/API/Endpoints/AutomatedLatestContent.php b/lib/API/Endpoints/v1/AutomatedLatestContent.php similarity index 98% rename from lib/API/Endpoints/AutomatedLatestContent.php rename to lib/API/Endpoints/v1/AutomatedLatestContent.php index f45a59b90f..510e4c2c04 100644 --- a/lib/API/Endpoints/AutomatedLatestContent.php +++ b/lib/API/Endpoints/v1/AutomatedLatestContent.php @@ -1,5 +1,5 @@ subscribe($data); - + $api = new API(); + $api->setRequestData($_REQUEST); + $form_id = (!empty($_REQUEST['data']['form_id'])) ? (int)$_REQUEST['data']['form_id'] : false; + $response = $api->processRoute(); if($response->status !== APIResponse::STATUS_OK) { - Url::redirectBack(array( - 'mailpoet_error' => isset($data['form_id']) ? $data['form_id'] : true, - 'mailpoet_success' => null - )); + Url::redirectBack( + array( + 'mailpoet_error' => ($form_id) ? $form_id : true, + 'mailpoet_success' => null + ) + ); } else { - if(isset($response->meta['redirect_url'])) { - Url::redirectTo($response->meta['redirect_url']); - } else { - Url::redirectBack(array( - 'mailpoet_success' => $form_id, - 'mailpoet_error' => null - )); - } + (isset($response->meta['redirect_url'])) ? + Url::redirectTo($response->meta['redirect_url']) : + Url::redirectBack( + array( + 'mailpoet_success' => $form_id, + 'mailpoet_error' => null + ) + ); } } } \ No newline at end of file diff --git a/tests/unit/API/APITest.php b/tests/unit/API/APITest.php index 694f639464..81559d1956 100644 --- a/tests/unit/API/APITest.php +++ b/tests/unit/API/APITest.php @@ -1,11 +1,14 @@ api->getEndpointNamespaces())->count(1); - $namespace = "MailPoet\\Dummy\\Name\\Space"; - $this->api->addEndpointNamespace($namespace); + $namespace = array( + 'name' => 'MailPoet\\Dummy\\Name\\Space', + 'version' => 'v2' + ); + $this->api->addEndpointNamespace($namespace['name'], $namespace['version']); $namespaces = $this->api->getEndpointNamespaces(); expect($namespaces)->count(2); - expect($namespaces[1])->equals($namespace); + expect($namespaces[$namespace['version']][0])->equals($namespace['name']); } - function testItCanCallAddedEndpoints() { - $namespace = "MailPoet\\Some\\Name\\Space\\Endpoints"; - $this->api->addEndpointNamespace($namespace); + function testItReturns400ErrorWhenAPIVersionIsNotSpecified() { + $data = array( + 'endpoint' => 'namespaced_endpoint_stub', + 'method' => 'test' + ); + + $response = $this->api->setRequestData($data); + expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST); + } + + function testItAcceptsAndProcessesAPIVersion() { + $namespace = array( + 'name' => 'MailPoet\API\Endpoints\v2', + 'version' => 'v2' + ); + $this->api->addEndpointNamespace($namespace['name'], $namespace['version']); + + $data = array( + 'endpoint' => 'namespaced_endpoint_stub', + 'api_version' => 'v2', + 'method' => 'test' + ); + $this->api->setRequestData($data); + + expect($this->api->getRequestedAPIVersion())->equals('v2'); + expect($this->api->getRequestedEndpointClass())->equals( + 'MailPoet\API\Endpoints\v2\NamespacedEndpointStub' + ); + } + + function testItCallsAddedEndpoints() { + $namespace = array( + 'name' => 'MailPoet\API\Endpoints\v1', + 'version' => 'v1' + ); + $this->api->addEndpointNamespace($namespace['name'], $namespace['version']); $data = array( 'endpoint' => 'namespaced_endpoint_stub', 'method' => 'test', + 'api_version' => 'v1', 'data' => array('test' => 'data') ); - $this->api->getRequestData($data); + $this->api->setRequestData($data); $response = $this->api->processRoute(); expect($response->getData()['data'])->equals($data['data']); } + function testItCallsAddedEndpointsForSpecificAPIVersion() { + $namespace = array( + 'name' => 'MailPoet\API\Endpoints\v2', + 'version' => 'v2' + ); + $this->api->addEndpointNamespace($namespace['name'], $namespace['version']); + + $data = array( + 'endpoint' => 'namespaced_endpoint_stub', + 'api_version' => 'v2', + 'method' => 'testVersion' + ); + $this->api->setRequestData($data); + $response = $this->api->processRoute(); + + expect($response->getData()['data'])->equals($data['api_version']); + } + function _after() { wp_delete_user($this->wp_user_id); } diff --git a/tests/unit/API/APITestNamespacedEndpointStub.php b/tests/unit/API/APITestNamespacedEndpointStubV1.php similarity index 87% rename from tests/unit/API/APITestNamespacedEndpointStub.php rename to tests/unit/API/APITestNamespacedEndpointStubV1.php index 02b196f451..724922755e 100644 --- a/tests/unit/API/APITestNamespacedEndpointStub.php +++ b/tests/unit/API/APITestNamespacedEndpointStubV1.php @@ -1,6 +1,6 @@ APIAccess::ALL + ); + + function testVersion() { + return $this->successResponse('v2'); + } +} diff --git a/tests/unit/API/Endpoints/CustomFieldsTest.php b/tests/unit/API/Endpoints/v1/CustomFieldsTest.php similarity index 98% rename from tests/unit/API/Endpoints/CustomFieldsTest.php rename to tests/unit/API/Endpoints/v1/CustomFieldsTest.php index f1bba2aef0..a5a07298af 100644 --- a/tests/unit/API/Endpoints/CustomFieldsTest.php +++ b/tests/unit/API/Endpoints/v1/CustomFieldsTest.php @@ -1,6 +1,6 @@ " )) { MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'customFields', action: 'delete', data: { diff --git a/views/form/templates/settings/field_form.hbs b/views/form/templates/settings/field_form.hbs index 90717a4729..61d6927d4e 100644 --- a/views/form/templates/settings/field_form.hbs +++ b/views/form/templates/settings/field_form.hbs @@ -74,6 +74,7 @@ // save custom field MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'customFields', action: 'save', data: data diff --git a/views/form/widget.html b/views/form/widget.html index 4a8a8f24c4..bbe0062345 100644 --- a/views/form/widget.html +++ b/views/form/widget.html @@ -13,8 +13,9 @@ class="mailpoet_form mailpoet_form_<%= form_type %>" novalidate > - + + diff --git a/views/newsletter/editor.html b/views/newsletter/editor.html index a982925cea..176d3f3a79 100644 --- a/views/newsletter/editor.html +++ b/views/newsletter/editor.html @@ -1201,6 +1201,7 @@ }; MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'newsletters', action: 'get', data: { diff --git a/views/settings.html b/views/settings.html index a3b4f9a93e..82f379e056 100644 --- a/views/settings.html +++ b/views/settings.html @@ -74,6 +74,7 @@ MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'settings', action: 'set', data: settings_data diff --git a/views/settings/advanced.html b/views/settings/advanced.html index 62dc2152ac..8dc8d47ab6 100644 --- a/views/settings/advanced.html +++ b/views/settings/advanced.html @@ -175,6 +175,7 @@ )) { MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + 'api_version': window.mailpoet_api_version, 'endpoint': 'setup', 'action': 'reset' }).always(function() { diff --git a/views/settings/mta.html b/views/settings/mta.html index f464c43649..1ed9a8e999 100644 --- a/views/settings/mta.html +++ b/views/settings/mta.html @@ -707,6 +707,7 @@ MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'mailer', action: 'send', data: { @@ -751,6 +752,7 @@ MailPoet.Modal.loading(true); MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: 'services', action: 'verifyMailPoetKey', data: { diff --git a/views/welcome.html b/views/welcome.html index cc301df2d2..3257a095a9 100644 --- a/views/welcome.html +++ b/views/welcome.html @@ -118,6 +118,7 @@ jQuery(function($) { $("#mailpoet_analytics_enabled").on("click", function() { var is_enabled = $(this).is(":checked") ? true : ""; MailPoet.Ajax.post({ + api_version: window.mailpoet_api_version, endpoint: "settings", action: "set", data: {