diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx
index 7f8b1d85e1..d9ef512579 100644
--- a/assets/js/src/listing/listing.jsx
+++ b/assets/js/src/listing/listing.jsx
@@ -298,7 +298,8 @@ const Listing = React.createClass({
filters: {},
filter: {},
selected_ids: [],
- selection: false
+ selection: false,
+ meta: {}
};
},
getParam: function(param) {
@@ -463,15 +464,21 @@ const Listing = React.createClass({
items: response.data || [],
filters: response.meta.filters || {},
groups: response.meta.groups || [],
- count: response.meta.count || 0
+ count: response.meta.count || 0,
+ meta: _.omit(response.meta, ['filters', 'groups', 'count'])
}, () => {
// if viewing an empty trash
if (this.state.group === 'trash' && response.meta.count === 0) {
// redirect to default group
this.handleGroup('all');
}
+
+ // trigger afterGetItems callback if specified
+ if (this.props.afterGetItems !== undefined) {
+ this.props.afterGetItems(this.state);
+ }
});
- }).fail(function(response) {
+ }).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
@@ -711,7 +718,7 @@ const Listing = React.createClass({
}.bind(this));
},
handleRenderItem: function(item, actions) {
- const render = this.props.onRenderItem(item, actions);
+ const render = this.props.onRenderItem(item, actions, this.state.meta);
return render.props.children;
},
handleRefreshItems: function() {
diff --git a/assets/js/src/newsletters/listings/mixins.jsx b/assets/js/src/newsletters/listings/mixins.jsx
index e3aa7857ef..613898153e 100644
--- a/assets/js/src/newsletters/listings/mixins.jsx
+++ b/assets/js/src/newsletters/listings/mixins.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import ReactDOM from 'react-dom'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
import jQuery from 'jquery'
@@ -42,11 +43,15 @@ const _QueueMixin = {
}
});
},
- renderQueueStatus: function(newsletter) {
+ renderQueueStatus: function(newsletter, mailer_log) {
if (!newsletter.queue) {
return (
{MailPoet.I18n.t('notSentYet')}
);
+ } else if (mailer_log.status === 'paused') {
+ return (
+ {MailPoet.I18n.t('paused')}
+ )
} else {
if (newsletter.queue.status === 'scheduled') {
return (
@@ -72,14 +77,8 @@ const _QueueMixin = {
{
MailPoet.I18n.t('newsletterQueueCompleted')
- .replace(
- "%$1d",
- newsletter.queue.count_processed - newsletter.queue.count_failed
- )
- .replace(
- "%$2d",
- newsletter.queue.count_total
- )
+ .replace("%$1d",newsletter.queue.count_processed)
+ .replace("%$2d", newsletter.queue.count_total)
}
);
@@ -175,5 +174,66 @@ const _StatisticsMixin = {
}
}
+const _MailerMixin = {
+ checkMailerStatus: function(state) {
+ if (state.meta.mta_log.error && state.meta.mta_log.status === 'paused') {
+ MailPoet.Notice.error(
+ '',
+ { static: true, id: 'mailpoet_mailer_error' }
+ );
+
+ ReactDOM.render(
+ this.getMailerError(state),
+ jQuery('[data-id="mailpoet_mailer_error"]')[0]
+ );
+ } else {
+ MailPoet.Notice.hide('mailpoet_mailer_error');
+ }
+ },
+ getMailerError(state) {
+ let mailer_error_notice;
+ if (state.meta.mta_log.error.operation === 'send') {
+ mailer_error_notice =
+ MailPoet.I18n.t('mailerSendErrorNotice')
+ .replace('%$1s', state.meta.mta_method)
+ .replace('%$2s', state.meta.mta_log.error.error_message);
+ } else {
+ mailer_error_notice =
+ MailPoet.I18n.t('mailerConnectionErrorNotice')
+ .replace('%$1s', state.meta.mta_log.error.error_message);
+ }
+ return (
+
@@ -73,7 +80,7 @@ const NewsletterListNotificationHistory = React.createClass({
{ actions }
|
- { this.renderQueueStatus(newsletter) }
+ { this.renderQueueStatus(newsletter, mailpoet_mailer_log) }
|
{ segments }
@@ -116,6 +123,7 @@ const NewsletterListNotificationHistory = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
+ afterGetItems={ this.checkMailerStatus }
/>
);
diff --git a/assets/js/src/newsletters/listings/standard.jsx b/assets/js/src/newsletters/listings/standard.jsx
index 870ae70424..08e976df2f 100644
--- a/assets/js/src/newsletters/listings/standard.jsx
+++ b/assets/js/src/newsletters/listings/standard.jsx
@@ -7,9 +7,14 @@ import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
-import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
+import {
+ QueueMixin,
+ StatisticsMixin,
+ MailerMixin
+} from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
+const mailpoet_settings = window.mailpoet_settings || {};
const messages = {
onTrash: (response) => {
@@ -85,7 +90,6 @@ const columns = [
}
];
-
const bulk_actions = [
{
name: 'trash',
@@ -148,8 +152,8 @@ const newsletter_actions = [
];
const NewsletterListStandard = React.createClass({
- mixins: [QueueMixin, StatisticsMixin],
- renderItem: function(newsletter, actions) {
+ mixins: [ QueueMixin, StatisticsMixin, MailerMixin ],
+ renderItem: function(newsletter, actions, meta) {
const rowClasses = classNames(
'manage-column',
'column-primary',
@@ -172,7 +176,7 @@ const NewsletterListStandard = React.createClass({
{ actions }
|
- { this.renderQueueStatus(newsletter) }
+ { this.renderQueueStatus(newsletter, meta.mta_log) }
|
{ segments }
@@ -212,6 +216,7 @@ const NewsletterListStandard = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
+ afterGetItems={ this.checkMailerStatus }
/>
);
diff --git a/assets/js/src/newsletters/listings/welcome.jsx b/assets/js/src/newsletters/listings/welcome.jsx
index 86b97f302b..e3f382da91 100644
--- a/assets/js/src/newsletters/listings/welcome.jsx
+++ b/assets/js/src/newsletters/listings/welcome.jsx
@@ -5,6 +5,8 @@ import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
+import { MailerMixin } from 'newsletters/listings/mixins.jsx'
+
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
@@ -13,6 +15,7 @@ import _ from 'underscore'
const mailpoet_roles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
+const mailpoet_settings = window.mailpoet_settings || {};
const messages = {
onTrash: (response) => {
@@ -151,6 +154,7 @@ const newsletter_actions = [
];
const NewsletterListWelcome = React.createClass({
+ mixins: [ MailerMixin ],
updateStatus: function(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
@@ -358,6 +362,7 @@ const NewsletterListWelcome = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
+ afterGetItems={ this.checkMailerStatus }
/>
);
diff --git a/assets/js/src/newsletters/newsletters.jsx b/assets/js/src/newsletters/newsletters.jsx
index 0ed787054e..fd82973843 100644
--- a/assets/js/src/newsletters/newsletters.jsx
+++ b/assets/js/src/newsletters/newsletters.jsx
@@ -27,7 +27,7 @@ const App = React.createClass({
const container = document.getElementById('newsletters_container');
if(container) {
- ReactDOM.render((
+ const mailpoet_listing = ReactDOM.render((
@@ -49,4 +49,6 @@ if(container) {
), container);
+
+ window.mailpoet_listing = mailpoet_listing;
}
diff --git a/assets/js/src/notice.js b/assets/js/src/notice.js
index 43abfdd3c4..668cbfe496 100644
--- a/assets/js/src/notice.js
+++ b/assets/js/src/notice.js
@@ -5,14 +5,14 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
MailPoet Notice:
description: Handles notices
- version: 0.2
+ version: 1.0
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
- // success message (static: false)
+ // success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
@@ -21,199 +21,206 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
- Examples:
-
- MailPoet.Notice.success('- success #1 -');
- setTimeout(function() {
- MailPoet.Notice.success('- success #2 -');
- setTimeout(function() {
- MailPoet.Notice.error('- error -');
- setTimeout(function() {
- MailPoet.Notice.system('- system -');
-
- setTimeout(function() {
- MailPoet.Notice.hide();
- }, 2500);
- }, 300);
- }, 400);
- }, 500);
-
==================================================================================================*/
MailPoet.Notice = {
- version: 0.2,
- // default options
- defaults: {
- type: 'success',
- message: '',
- static: false,
- hideClose: false,
- id: null,
- positionAfter: false,
- scroll: false,
- timeout: 5000,
- onOpen: null,
- onClose: null
- },
- options: {},
- init: function(options) {
- // set options
- this.options = jQuery.extend({}, this.defaults, options);
+ version: 1.0,
+ // default options
+ defaults: {
+ type: 'success',
+ message: '',
+ static: false,
+ hideClose: false,
+ id: null,
+ positionAfter: false,
+ scroll: false,
+ timeout: 5000,
+ onOpen: null,
+ onClose: null
+ },
+ options: {},
+ init: function(options) {
+ // set options
+ this.options = jQuery.extend({}, this.defaults, options);
- // clone element
- this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
+ return this;
+ },
+ createNotice: function() {
+ // clone element
+ this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
- // add data-id to the element
- if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
-
- // remove id from clone
- this.element.removeAttr('id');
-
- // insert notice after its parent
- var positionAfter;
- if (typeof this.options.positionAfter === 'object') {
- positionAfter = this.options.positionAfter;
- } else if (typeof this.options.positionAfter === 'string') {
- positionAfter = jQuery(this.options.positionAfter);
- } else {
- positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
- }
- positionAfter.after(this.element);
-
- // setup onClose callback
- var onClose = null;
- if(this.options.onClose !== null) {
- onClose = this.options.onClose;
- }
-
- // listen to remove event
- jQuery(this.element).on('close', function() {
- jQuery(this).fadeOut(200, function() {
- // on close callback
- if(onClose !== null) {
- onClose();
- }
- // remove notice
- jQuery(this).remove();
- });
- }.bind(this.element));
-
- // listen to message event
- jQuery(this.element).on('message', function(e, message) {
- MailPoet.Notice.setMessage(message);
- }.bind(this.element));
-
- return this;
- },
- isHTML: function(str) {
- var a = document.createElement('div');
- a.innerHTML = str;
- for(var c = a.childNodes, i = c.length; i--;) {
- if(c[i].nodeType == 1) return true;
- }
- return false;
- },
- setMessage: function(message) {
- // if it's not an html message, let's sugar coat the message with a fancy
- if(this.isHTML(message) === false) {
- message = ' '+message+' ';
- }
- // set message
- return this.element.html(message);
- },
- show: function(options) {
- // initialize
- this.init(options);
-
- // show notice
- this.showNotice();
-
- // return this;
- },
- showNotice: function() {
- // set message
- this.setMessage(this.options.message);
-
- // position notice
- this.element.insertAfter(jQuery('h2.title'));
-
- // set class name
- switch(this.options.type) {
- case 'success':
- this.element.addClass('updated');
- break;
- case 'system':
- this.element.addClass('update-nag');
- break;
- case 'error':
- this.element.addClass('error');
- break;
- }
-
- // make the notice appear
- this.element.fadeIn(200);
-
- // if scroll option is enabled, scroll to the notice
- if(this.options.scroll === true) {
- this.element.get(0).scrollIntoView(false);
- }
-
- // if the notice is not static, it has to disappear after a timeout
- if(this.options.static === false) {
- this.element.delay(this.options.timeout).trigger('close');
- } else if (this.options.hideClose === false) {
- this.element.append('');
- this.element.find('.mailpoet_notice_close').on('click', function() {
- jQuery(this).trigger('close');
- });
- }
-
- // call onOpen callback
- if(this.options.onOpen !== null) {
- this.options.onOpen(this.element);
- }
- },
- hide: function(all) {
- if(all !== undefined && all === true) {
- jQuery('.mailpoet_notice:not([id])').trigger('close');
- } else if (all !== undefined && jQuery.isArray(all)) {
- for (var id in all) {
- jQuery('[data-id="notice_' + all[id] + '"]')
- .trigger('close');
- }
- } if (all !== undefined) {
- jQuery('[data-id="notice_' + all + '"]')
- .trigger('close');
- } else {
- jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
- .trigger('close');
- }
- },
- error: function(message, options) {
- this.show(jQuery.extend({}, {
- type: 'error',
- message: ''+this.formatMessage(message)+' '
- }, options));
- },
- success: function(message, options) {
- this.show(jQuery.extend({}, {
- type: 'success',
- message: ''+this.formatMessage(message)+' '
- }, options));
- },
- system: function(message, options) {
- this.show(jQuery.extend({}, {
- type: 'system',
- static: true,
- message: ''+this.formatMessage(message)+' '
- }, options));
- },
- formatMessage: function(message) {
- if(Array.isArray(message)) {
- return message.join(' ');
- } else {
- return message;
- }
+ // add data-id to the element
+ if (this.options.id) {
+ this.element.attr(
+ 'data-id',
+ this.options.id
+ );
}
+
+ // remove id from clone
+ this.element.removeAttr('id');
+
+ // insert notice after its parent
+ var positionAfter;
+ if (typeof this.options.positionAfter === 'object') {
+ positionAfter = this.options.positionAfter;
+ } else if (typeof this.options.positionAfter === 'string') {
+ positionAfter = jQuery(this.options.positionAfter);
+ } else {
+ positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
+ }
+ positionAfter.after(this.element);
+
+ // setup onClose callback
+ var onClose = null;
+ if (this.options.onClose !== null) {
+ onClose = this.options.onClose;
+ }
+
+ // listen to remove event
+ jQuery(this.element).on('close', function() {
+ jQuery(this).fadeOut(200, function() {
+ // on close callback
+ if (onClose !== null) {
+ onClose();
+ }
+ // remove notice
+ jQuery(this).remove();
+ });
+ }.bind(this.element));
+
+ // listen to message event
+ jQuery(this.element).on('setMessage', function(e, message) {
+ MailPoet.Notice.setMessage(message);
+ }.bind(this.element));
+
+ return this;
+ },
+ updateNotice: function() {
+ // update notice's message
+ jQuery('[data-id="'+this.options.id+'"').first().trigger(
+ 'setMessage', this.options.message
+ );
+ },
+ isHTML: function(str) {
+ var a = document.createElement('div');
+ a.innerHTML = str;
+ for (var c = a.childNodes, i = c.length; i--;) {
+ if (c[i].nodeType == 1) return true;
+ }
+ return false;
+ },
+ setMessage: function(message) {
+ message = this.formatMessage(message);
+
+ // if it's not an html message
+ // let's sugar coat the message with a fancy
+ if (this.isHTML(message) === false) {
+ message = ' '+message+' ';
+ }
+ // set message
+ return this.element.html(message);
+ },
+ formatMessage: function(message) {
+ if (Array.isArray(message)) {
+ return message.join(' ');
+ } else {
+ return message;
+ }
+ },
+ show: function(options) {
+ // initialize
+ this.init(options);
+
+ if (
+ this.options.id !== null
+ &&
+ jQuery('[data-id="'+this.options.id+'"').length > 0
+ ) {
+ this.updateNotice();
+ } else {
+ this.createNotice();
+ }
+ this.showNotice();
+ },
+ showNotice: function() {
+ // set message
+ this.setMessage(this.options.message);
+
+ // position notice
+ this.element.insertAfter(jQuery('h2.title'));
+
+ // set class name
+ switch (this.options.type) {
+ case 'success':
+ this.element.addClass('updated');
+ break;
+ case 'system':
+ this.element.addClass('update-nag');
+ break;
+ case 'error':
+ this.element.addClass('error');
+ break;
+ }
+
+ // make the notice appear
+ this.element.fadeIn(200);
+
+ // if scroll option is enabled, scroll to the notice
+ if (this.options.scroll === true) {
+ this.element.get(0).scrollIntoView(false);
+ }
+
+ // if the notice is not static, it has to disappear after a timeout
+ if (this.options.static === false) {
+ this.element.delay(this.options.timeout).trigger('close');
+ } else if (this.options.hideClose === false) {
+ this.element.append('');
+ this.element.find('.mailpoet_notice_close').on('click', function() {
+ jQuery(this).trigger('close');
+ });
+ }
+
+ // call onOpen callback
+ if (this.options.onOpen !== null) {
+ this.options.onOpen(this.element);
+ }
+ },
+ hide: function(all) {
+ if (all !== undefined && all === true) {
+ // all notices
+ jQuery('.mailpoet_notice:not([id])').trigger('close');
+ } else if (all !== undefined && jQuery.isArray(all)) {
+ // array of ids
+ for (var id in all) {
+ jQuery('[data-id="' + all[id] + '"]').trigger('close');
+ }
+ } if (all !== undefined) {
+ // single id
+ jQuery('[data-id="' + all + '"]').trigger('close');
+ } else {
+ jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
+ .trigger('close');
+ }
+ },
+ error: function(message, options) {
+ this.show(jQuery.extend({}, {
+ type: 'error',
+ message: message
+ }, options));
+ },
+ success: function(message, options) {
+ this.show(jQuery.extend({}, {
+ type: 'success',
+ message: message
+ }, options));
+ },
+ system: function(message, options) {
+ this.show(jQuery.extend({}, {
+ type: 'system',
+ static: true,
+ message: message
+ }, options));
+ }
};
});
diff --git a/lib/API/Endpoints/Mailer.php b/lib/API/Endpoints/Mailer.php
index a920a4e4f2..709e783f18 100644
--- a/lib/API/Endpoints/Mailer.php
+++ b/lib/API/Endpoints/Mailer.php
@@ -2,6 +2,7 @@
namespace MailPoet\API\Endpoints;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
+use MailPoet\Mailer\MailerLog;
if(!defined('ABSPATH')) exit;
@@ -20,12 +21,19 @@ class Mailer extends APIEndpoint {
));
}
- if($result === false) {
- return $this->errorResponse(array(
- APIError::BAD_REQUEST => __("The email could not be sent. Please check your settings.", 'mailpoet')
- ));
+ if($result['response'] === false) {
+ $error = sprintf(
+ __('The email could not be sent: %s', 'mailpoet'),
+ $result['error']
+ );
+ return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
} else {
return $this->successResponse(null);
}
}
+
+ function resumeSending() {
+ MailerLog::resumeSending();
+ return $this->successResponse(null);
+ }
}
\ No newline at end of file
diff --git a/lib/API/Endpoints/Newsletters.php b/lib/API/Endpoints/Newsletters.php
index af40b5e24d..8557c66309 100644
--- a/lib/API/Endpoints/Newsletters.php
+++ b/lib/API/Endpoints/Newsletters.php
@@ -4,6 +4,7 @@ namespace MailPoet\API\Endpoints;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Listing;
+use MailPoet\Models\Setting;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\NewsletterSegment;
@@ -275,10 +276,19 @@ class Newsletters extends APIEndpoint {
$sender = false,
$reply_to = false
);
- $mailer->send($newsletter, $data['subscriber']);
- return $this->successResponse(
- Newsletter::findOne($id)->asArray()
- );
+ $result = $mailer->send($newsletter, $data['subscriber']);
+
+ if($result['response'] === false) {
+ $error = sprintf(
+ __('The email could not be sent: %s', 'mailpoet'),
+ $result['error']
+ );
+ return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
+ } else {
+ return $this->successResponse(
+ Newsletter::findOne($id)->asArray()
+ );
+ }
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
@@ -337,7 +347,9 @@ class Newsletters extends APIEndpoint {
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
- 'groups' => $listing_data['groups']
+ 'groups' => $listing_data['groups'],
+ 'mta_log' => Setting::getValue('mta_log'),
+ 'mta_method' => Setting::getValue('mta.method')
));
}
diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php
index cc8f4223a8..9e9ad103e3 100644
--- a/lib/Config/Migrator.php
+++ b/lib/Config/Migrator.php
@@ -115,7 +115,6 @@ class Migrator {
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
- 'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NULL,',
'processed_at TIMESTAMP NULL,',
'created_at TIMESTAMP NULL,',
diff --git a/lib/Config/Populator.php b/lib/Config/Populator.php
index b0effe5a48..ae281a22a5 100644
--- a/lib/Config/Populator.php
+++ b/lib/Config/Populator.php
@@ -2,6 +2,7 @@
namespace MailPoet\Config;
use MailPoet\Cron\CronTrigger;
+use MailPoet\Mailer\MailerLog;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
@@ -85,26 +86,26 @@ class Populator {
private function createDefaultSettings() {
$current_user = wp_get_current_user();
+ // set cron trigger option to default method
if(!Setting::getValue(CronTrigger::SETTING_NAME)) {
- // disable task scheduler (cron) be default
Setting::setValue(CronTrigger::SETTING_NAME, array(
'method' => CronTrigger::DEFAULT_METHOD
));
}
- // default sender info based on current user
+ // set default sender info based on current user
$sender = array(
'name' => $current_user->display_name,
'address' => $current_user->user_email
);
+ // set default from name & address
if(!Setting::getValue('sender')) {
- // default from name & address
Setting::setValue('sender', $sender);
}
+ // enable signup confirmation by default
if(!Setting::getValue('signup_confirmation')) {
- // enable signup confirmation by default
Setting::setValue('signup_confirmation', array(
'enabled' => true,
'from' => array(
@@ -115,9 +116,13 @@ class Populator {
));
}
+ // set installation date
if(!Setting::getValue('installed_at')) {
Setting::setValue('installed_at', date("Y-m-d H:i:s"));
}
+
+ // reset mailer log
+ MailerLog::resetMailerLog();
}
private function createDefaultSegments() {
diff --git a/lib/Cron/CronHelper.php b/lib/Cron/CronHelper.php
index a45785aea1..9118d54dbd 100644
--- a/lib/Cron/CronHelper.php
+++ b/lib/Cron/CronHelper.php
@@ -9,9 +9,9 @@ use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class CronHelper {
- const DAEMON_EXECUTION_LIMIT = 20;
- const DAEMON_EXECUTION_TIMEOUT = 35;
- const DAEMON_REQUEST_TIMEOUT = 2;
+ const DAEMON_EXECUTION_LIMIT = 20; // seconds
+ const DAEMON_EXECUTION_TIMEOUT = 35; // seconds
+ const DAEMON_REQUEST_TIMEOUT = 2; // seconds
const DAEMON_SETTING = 'cron_daemon';
static function createDaemon($token) {
diff --git a/lib/Cron/Daemon.php b/lib/Cron/Daemon.php
index fa1b11e1a0..d8e13389cb 100644
--- a/lib/Cron/Daemon.php
+++ b/lib/Cron/Daemon.php
@@ -10,7 +10,7 @@ class Daemon {
public $daemon;
public $request_data;
public $timer;
- const REQUEST_TIMEOUT = 5;
+ const REQUEST_TIMEOUT = 5; // seconds
function __construct($request_data = false) {
$this->request_data = $request_data;
diff --git a/lib/Cron/Workers/SendingQueue/SendingQueue.php b/lib/Cron/Workers/SendingQueue/SendingQueue.php
index 83915b1307..b804291122 100644
--- a/lib/Cron/Workers/SendingQueue/SendingQueue.php
+++ b/lib/Cron/Workers/SendingQueue/SendingQueue.php
@@ -21,8 +21,10 @@ class SendingQueue {
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
$this->newsletter_task = ($newsletter_task) ? $newsletter_task : new NewsletterTask();
$this->timer = ($timer) ? $timer : microtime(true);
- // abort if execution or sending limit are reached
+ // abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
+ // abort if mailing is paused or sending limit has been reached
+ MailerLog::enforceExecutionRequirements();
}
function process() {
@@ -70,8 +72,8 @@ class SendingQueue {
if($queue->status === SendingQueueModel::STATUS_COMPLETED) {
$this->newsletter_task->markNewsletterAsSent($newsletter);
}
- // abort if sending limit is reached
- MailerLog::enforceSendingLimit();
+ // abort if sending limit has been reached
+ MailerLog::enforceExecutionRequirements();
}
}
}
@@ -118,8 +120,8 @@ class SendingQueue {
$prepared_subscribers_ids = array();
$statistics = array();
}
- // abort if sending limit is reached
- MailerLog::enforceSendingLimit();
+ // abort if sending limit has been reached
+ MailerLog::enforceExecutionRequirements();
}
if($processing_method === 'bulk') {
$queue = $this->sendNewsletters(
@@ -142,20 +144,22 @@ class SendingQueue {
$prepared_newsletters,
$prepared_subscribers
);
- if(!$send_result) {
- // update failed/to process list
- $queue->updateFailedSubscribers($prepared_subscribers_ids);
- } else {
- // update processed/to process list
- $queue->updateProcessedSubscribers($prepared_subscribers_ids);
- // log statistics
- StatisticsNewslettersModel::createMultiple($statistics);
- // update the sent count
- $this->mailer_task->updateSentCount();
- // enforce sending limit if there are still subscribers left to process
- if($queue->count_to_process) {
- MailerLog::enforceSendingLimit();
- }
+ // log error message and schedule retry/pause sending
+ if($send_result['response'] === false) {
+ MailerLog::processSendingError(
+ $send_result['operation'],
+ $send_result['error_message']
+ );
+ }
+ // update processed/to process list
+ $queue->updateProcessedSubscribers($prepared_subscribers_ids);
+ // log statistics
+ StatisticsNewslettersModel::createMultiple($statistics);
+ // update the sent count
+ $this->mailer_task->updateSentCount();
+ // abort if sending limit has been reached
+ if($queue->count_to_process) {
+ MailerLog::enforceExecutionRequirements();
}
return $queue;
}
diff --git a/lib/Mailer/Mailer.php b/lib/Mailer/Mailer.php
index 80f758ce7f..0f99034edf 100644
--- a/lib/Mailer/Mailer.php
+++ b/lib/Mailer/Mailer.php
@@ -14,8 +14,6 @@ class Mailer {
const MAILER_CONFIG_SETTING_NAME = 'mta';
const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
const METHOD_MAILPOET = 'MailPoet';
- const METHOD_MAILGUN = 'MailGun';
- const METHOD_ELASTICEMAIL = 'ElasticEmail';
const METHOD_AMAZONSES = 'AmazonSES';
const METHOD_SENDGRID = 'SendGrid';
const METHOD_PHPMAIL = 'PHPMail';
@@ -44,21 +42,6 @@ class Mailer {
$this->reply_to
);
break;
- case self::METHOD_ELASTICEMAIL:
- $mailer_instance = new $this->mailer_config['class'](
- $this->mailer_config['api_key'],
- $this->sender,
- $this->reply_to
- );
- break;
- case self::METHOD_MAILGUN:
- $mailer_instance = new $this->mailer_config['class'](
- $this->mailer_config['domain'],
- $this->mailer_config['api_key'],
- $this->sender,
- $this->reply_to
- );
- break;
case self::METHOD_MAILPOET:
$mailer_instance = new $this->mailer_config['class'](
$this->mailer_config['mailpoet_api_key'],
@@ -168,7 +151,29 @@ class Mailer {
function encodeAddressNamePart($name) {
if(mb_detect_encoding($name) === 'ASCII') return $name;
- // bse64_encode non-ASCII string as per RFC 2047 (https://www.ietf.org/rfc/rfc2047.txt)
+ // encode non-ASCII string as per RFC 2047 (https://www.ietf.org/rfc/rfc2047.txt)
return sprintf('=?utf-8?B?%s?=', base64_encode($name));
}
+
+ static function formatMailerConnectionErrorResult($error_message) {
+ return array(
+ 'response' => false,
+ 'operation' => 'connect',
+ 'error_message' => $error_message
+ );
+ }
+
+ static function formatMailerSendErrorResult($error_message) {
+ return array(
+ 'response' => false,
+ 'operation' => 'send',
+ 'error_message' => $error_message
+ );
+ }
+
+ static function formatMailerSendSuccessResult() {
+ return array(
+ 'response' => true
+ );
+ }
}
\ No newline at end of file
diff --git a/lib/Mailer/MailerLog.php b/lib/Mailer/MailerLog.php
index 4ca145979e..c2e3febe9b 100644
--- a/lib/Mailer/MailerLog.php
+++ b/lib/Mailer/MailerLog.php
@@ -7,8 +7,12 @@ if(!defined('ABSPATH')) exit;
class MailerLog {
const SETTING_NAME = 'mta_log';
+ const STATUS_PAUSED = 'paused';
+ const RETRY_ATTEMPTS_LIMIT = 3;
+ const RETRY_INTERVAL = 120; // seconds
- static function getMailerLog() {
+ static function getMailerLog($mailer_log = false) {
+ if($mailer_log) return $mailer_log;
$mailer_log = Setting::getValue(self::SETTING_NAME);
if(!$mailer_log) {
$mailer_log = self::createMailerLog();
@@ -18,8 +22,12 @@ class MailerLog {
static function createMailerLog() {
$mailer_log = array(
- 'sent' => 0,
- 'started' => time()
+ 'sent' => null,
+ 'started' => time(),
+ 'status' => null,
+ 'retry_attempt' => null,
+ 'retry_at' => null,
+ 'error' => null
);
Setting::setValue(self::SETTING_NAME, $mailer_log);
return $mailer_log;
@@ -34,15 +42,73 @@ class MailerLog {
return $mailer_log;
}
+ static function enforceExecutionRequirements($mailer_log = false) {
+ $mailer_log = self::getMailerLog($mailer_log);
+ if($mailer_log['retry_attempt'] === self::RETRY_ATTEMPTS_LIMIT) {
+ $mailer_log = self::pauseSending($mailer_log);
+ }
+ if($mailer_log['status'] === self::STATUS_PAUSED) {
+ throw new \Exception(__('Sending has been paused.', 'mailpoet'));
+ }
+ if(!is_null($mailer_log['retry_at'])) {
+ if(time() <= $mailer_log['retry_at']) {
+ throw new \Exception(__('Sending is waiting to be retried.', 'mailpoet'));
+ } else {
+ $mailer_log['retry_at'] = null;
+ self::updateMailerLog($mailer_log);
+ }
+ }
+ // ensure that sending frequency has not been reached
+ if(self::isSendingLimitReached($mailer_log)) {
+ throw new \Exception(__('Sending frequency limit has been reached.', 'mailpoet'));
+ }
+ }
+
+ static function pauseSending($mailer_log) {
+ $mailer_log['status'] = self::STATUS_PAUSED;
+ $mailer_log['retry_attempt'] = null;
+ $mailer_log['retry_at'] = null;
+ return self::updateMailerLog($mailer_log);
+ }
+
+ static function resumeSending() {
+ return self::resetMailerLog();
+ }
+
+ static function processSendingError($operation, $error_message) {
+ $mailer_log = self::getMailerLog();
+ (int)$mailer_log['retry_attempt']++;
+ $mailer_log['retry_at'] = time() + self::RETRY_INTERVAL;
+ $mailer_log['error'] = array(
+ 'operation' => $operation,
+ 'error_message' => $error_message
+ );
+ self::updateMailerLog($mailer_log);
+ return self::enforceExecutionRequirements();
+ }
+
static function incrementSentCount() {
$mailer_log = self::getMailerLog();
+ // clear previous retry count, errors, etc.
+ if($mailer_log['error']) {
+ $mailer_log = self::clearSendingErrorLog($mailer_log);
+ }
(int)$mailer_log['sent']++;
return self::updateMailerLog($mailer_log);
}
- static function isSendingLimitReached() {
+ static function clearSendingErrorLog($mailer_log) {
+ $mailer_log['retry_attempt'] = null;
+ $mailer_log['retry_at'] = null;
+ $mailer_log['error'] = null;
+ return self::updateMailerLog($mailer_log);
+ }
+
+ static function isSendingLimitReached($mailer_log = false) {
$mailer_config = Mailer::getMailerConfig();
- $mailer_log = self::getMailerLog();
+ // do not enforce sending limit for MailPoet's sending method
+ if($mailer_config['method'] === Mailer::METHOD_MAILPOET) return false;
+ $mailer_log = self::getMailerLog($mailer_log);
$elapsed_time = time() - (int)$mailer_log['started'];
if($mailer_log['sent'] === $mailer_config['frequency_limit']) {
if($elapsed_time <= $mailer_config['frequency_interval']) return true;
@@ -51,10 +117,4 @@ class MailerLog {
}
return false;
}
-
- static function enforceSendingLimit() {
- if(self::isSendingLimitReached()) {
- throw new \Exception(__('Sending frequency limit has been reached.', 'mailpoet'));
- }
- }
}
\ No newline at end of file
diff --git a/lib/Mailer/Methods/AmazonSES.php b/lib/Mailer/Methods/AmazonSES.php
index 72c38dd927..dfbc4e6a5d 100644
--- a/lib/Mailer/Methods/AmazonSES.php
+++ b/lib/Mailer/Methods/AmazonSES.php
@@ -1,6 +1,8 @@
url,
$this->request($newsletter, $subscriber)
);
- return (
- !is_wp_error($result) === true &&
- wp_remote_retrieve_response_code($result) === 200
- );
+ if(is_wp_error($result)) {
+ return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
+ }
+ if(wp_remote_retrieve_response_code($result) !== 200) {
+ $response = simplexml_load_string(wp_remote_retrieve_body($result));
+ $response = ($response) ?
+ $response->Error->Message->__toString() :
+ sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_AMAZONSES);
+ return Mailer::formatMailerSendErrorResult($response);
+ }
+ return Mailer::formatMailerSendSuccessResult();
}
function getBody($newsletter, $subscriber) {
diff --git a/lib/Mailer/Methods/ElasticEmail.php b/lib/Mailer/Methods/ElasticEmail.php
deleted file mode 100644
index 3b731d995c..0000000000
--- a/lib/Mailer/Methods/ElasticEmail.php
+++ /dev/null
@@ -1,56 +0,0 @@
-api_key = $api_key;
- $this->sender = $sender;
- $this->reply_to = $reply_to;
- }
-
- function send($newsletter, $subscriber) {
- $result = wp_remote_post(
- $this->url,
- $this->request($newsletter, $subscriber));
- return (
- !is_wp_error($result) === true &&
- !preg_match('/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/', $result['body']) === false
- );
- }
-
- function getBody($newsletter, $subscriber) {
- $body = array(
- 'api_key' => $this->api_key,
- 'to' => $subscriber,
- 'from' => $this->sender['from_email'],
- 'from_name' => $this->sender['from_name'],
- 'reply_to' => $this->reply_to['reply_to_email'],
- 'reply_to_name' => $this->reply_to['reply_to_name'],
- 'subject' => $newsletter['subject']
- );
- if(!empty($newsletter['body']['html'])) {
- $body['body_html'] = $newsletter['body']['html'];
- }
- if(!empty($newsletter['body']['text'])) {
- $body['body_text'] = $newsletter['body']['text'];
- }
- return $body;
- }
-
- function request($newsletter, $subscriber) {
- $body = $this->getBody($newsletter, $subscriber);
- return array(
- 'timeout' => 10,
- 'httpversion' => '1.0',
- 'method' => 'POST',
- 'body' => urldecode(http_build_query($body))
- );
- }
-}
\ No newline at end of file
diff --git a/lib/Mailer/Methods/MailGun.php b/lib/Mailer/Methods/MailGun.php
deleted file mode 100644
index 8a7ff12c30..0000000000
--- a/lib/Mailer/Methods/MailGun.php
+++ /dev/null
@@ -1,63 +0,0 @@
-url = sprintf('https://api.mailgun.net/v3/%s/messages', $domain);
- $this->api_key = $api_key;
- $this->sender = $sender;
- $this->reply_to = $reply_to;
- }
-
- function send($newsletter, $subscriber) {
- $result = wp_remote_post(
- $this->url,
- $this->request($newsletter, $subscriber)
- );
- return (
- !is_wp_error($result) === true &&
- wp_remote_retrieve_response_code($result) === 200
- );
- }
-
- function getBody($newsletter, $subscriber) {
- $body = array(
- 'to' => $subscriber,
- 'from' => $this->sender['from_name_email'],
- 'h:Reply-To' => $this->reply_to['reply_to_name_email'],
- 'subject' => $newsletter['subject']
- );
- if(!empty($newsletter['body']['html'])) {
- $body['html'] = $newsletter['body']['html'];
- }
- if(!empty($newsletter['body']['text'])) {
- $body['text'] = $newsletter['body']['text'];
- }
- return $body;
- }
-
- function auth() {
- return 'Basic ' . base64_encode('api:' . $this->api_key);
- }
-
- function request($newsletter, $subscriber) {
- $body = $this->getBody($newsletter, $subscriber);
- return array(
- 'timeout' => 10,
- 'httpversion' => '1.0',
- 'method' => 'POST',
- 'headers' => array(
- 'Content-Type' => 'application/x-www-form-urlencoded',
- 'Authorization' => $this->auth()
- ),
- 'body' => urldecode(http_build_query($body))
- );
- }
-}
\ No newline at end of file
diff --git a/lib/Mailer/Methods/MailPoet.php b/lib/Mailer/Methods/MailPoet.php
index 43a4a2b345..228c82dcfe 100644
--- a/lib/Mailer/Methods/MailPoet.php
+++ b/lib/Mailer/Methods/MailPoet.php
@@ -1,6 +1,8 @@
url,
$this->request($message_body)
);
- return (
- !is_wp_error($result) === true &&
- wp_remote_retrieve_response_code($result) === 201
- );
+ if(is_wp_error($result)) {
+ return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
+ }
+ if(wp_remote_retrieve_response_code($result) !== 201) {
+ $response = (wp_remote_retrieve_body($result)) ?
+ wp_remote_retrieve_body($result) :
+ wp_remote_retrieve_response_message($result);
+ return Mailer::formatMailerSendErrorResult($response);
+ }
+ return Mailer::formatMailerSendSuccessResult();
}
function processSubscriber($subscriber) {
@@ -41,7 +49,7 @@ class MailPoet {
}
function getBody($newsletter, $subscriber) {
- $composeBody = function ($newsletter, $subscriber) {
+ $composeBody = function($newsletter, $subscriber) {
$body = array(
'to' => (array(
'address' => $subscriber['email'],
diff --git a/lib/Mailer/Methods/PHPMail.php b/lib/Mailer/Methods/PHPMail.php
index 19df340a52..87be807ee6 100644
--- a/lib/Mailer/Methods/PHPMail.php
+++ b/lib/Mailer/Methods/PHPMail.php
@@ -1,6 +1,8 @@
createMessage($newsletter, $subscriber);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
- $result = false;
+ return Mailer::formatMailerSendErrorResult($e->getMessage());
}
- return ($result === 1);
+ return ($result === 1) ?
+ Mailer::formatMailerSendSuccessResult() :
+ Mailer::formatMailerSendErrorResult(
+ sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_PHPMAIL)
+ );
}
function buildMailer() {
diff --git a/lib/Mailer/Methods/SMTP.php b/lib/Mailer/Methods/SMTP.php
index 7ab318599d..8992f47175 100644
--- a/lib/Mailer/Methods/SMTP.php
+++ b/lib/Mailer/Methods/SMTP.php
@@ -1,6 +1,8 @@
createMessage($newsletter, $subscriber);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
- $result = false;
+ return Mailer::formatMailerSendErrorResult($e->getMessage());
}
- return ($result === 1);
+ return ($result === 1) ?
+ Mailer::formatMailerSendSuccessResult() :
+ Mailer::formatMailerSendErrorResult(
+ sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SMTP)
+ );
}
function buildMailer() {
diff --git a/lib/Mailer/Methods/SendGrid.php b/lib/Mailer/Methods/SendGrid.php
index 9c990b2085..53eb6ccf30 100644
--- a/lib/Mailer/Methods/SendGrid.php
+++ b/lib/Mailer/Methods/SendGrid.php
@@ -1,6 +1,8 @@
url,
$this->request($newsletter, $subscriber)
);
- $result_body = json_decode($result['body'], true);
- return (
- !is_wp_error($result) === true &&
- !preg_match('!invalid!', $result['body']) === true &&
- !isset($result_body['errors']) === true &&
- wp_remote_retrieve_response_code($result) === 200
- );
+ if(is_wp_error($result)) {
+ return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
+ }
+ if(wp_remote_retrieve_response_code($result) !== 200) {
+ $response = json_decode($result['body'], true);
+ $response = (!empty($response['errors'])) ?
+ $response['errors'] :
+ sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SENDGRID);
+ return Mailer::formatMailerSendErrorResult($response);
+ }
+ return Mailer::formatMailerSendSuccessResult();
}
function getBody($newsletter, $subscriber) {
diff --git a/lib/Models/SendingQueue.php b/lib/Models/SendingQueue.php
index f520d4195a..39a9c87a47 100644
--- a/lib/Models/SendingQueue.php
+++ b/lib/Models/SendingQueue.php
@@ -67,9 +67,6 @@ class SendingQueue extends Model {
if(empty($subscribers['processed'])) {
$subscribers['processed'] = array();
}
- if(empty($subscribers['failed'])) {
- $subscribers['failed'] = array();
- }
return $subscribers;
}
@@ -104,22 +101,6 @@ class SendingQueue extends Model {
$this->updateCount();
}
- function updateFailedSubscribers($failed_subscribers) {
- $subscribers = $this->getSubscribers();
- $subscribers['failed'] = array_merge(
- $subscribers['failed'],
- $failed_subscribers
- );
- $subscribers['to_process'] = array_values(
- array_diff(
- $subscribers['to_process'],
- $failed_subscribers
- )
- );
- $this->subscribers = $subscribers;
- $this->updateCount();
- }
-
function updateProcessedSubscribers($processed_subscribers) {
$subscribers = $this->getSubscribers();
$subscribers['processed'] = array_merge(
@@ -138,10 +119,8 @@ class SendingQueue extends Model {
function updateCount() {
$this->subscribers = $this->getSubscribers();
- $this->count_processed =
- count($this->subscribers['processed']) + count($this->subscribers['failed']);
+ $this->count_processed = count($this->subscribers['processed']);
$this->count_to_process = count($this->subscribers['to_process']);
- $this->count_failed = count($this->subscribers['failed']);
$this->count_total = $this->count_processed + $this->count_to_process;
if(!$this->count_to_process) {
$this->processed_at = current_time('mysql');
diff --git a/tests/unit/API/Endpoints/MailerTest.php b/tests/unit/API/Endpoints/MailerTest.php
new file mode 100644
index 0000000000..554c9ebab4
--- /dev/null
+++ b/tests/unit/API/Endpoints/MailerTest.php
@@ -0,0 +1,21 @@
+ MailerLog::STATUS_PAUSED);
+ MailerLog::updateMailerLog($mailer_log);
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->equals(MailerLog::STATUS_PAUSED);
+ // resumeSending() method should clear the mailer log's status
+ $mailer_endpoint = new Mailer();
+ $response = $mailer_endpoint->resumeSending();
+ expect($response->status)->equals(APIResponse::STATUS_OK);
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->null();
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/Cron/Workers/SendingQueue/SendingQueueTest.php b/tests/unit/Cron/Workers/SendingQueue/SendingQueueTest.php
index fbc17ce9e9..07a4e6648e 100644
--- a/tests/unit/Cron/Workers/SendingQueue/SendingQueueTest.php
+++ b/tests/unit/Cron/Workers/SendingQueue/SendingQueueTest.php
@@ -37,8 +37,7 @@ class SendingQueueTest extends MailPoetTest {
$this->queue->subscribers = serialize(
array(
'to_process' => array($this->subscriber->id),
- 'processed' => array(),
- 'failed' => array()
+ 'processed' => array()
)
);
$this->queue->count_total = 1;
@@ -118,18 +117,16 @@ class SendingQueueTest extends MailPoetTest {
$updated_queue = SendingQueue::findOne($this->queue->id);
expect($updated_queue->status)->equals(SendingQueue::STATUS_COMPLETED);
- // queue subscriber processed/failed/to process count is updated
+ // queue subscriber processed/to process count is updated
$updated_queue->subscribers = $updated_queue->getSubscribers();
expect($updated_queue->subscribers)->equals(
array(
'to_process' => array(),
- 'failed' => array(),
'processed' => array($this->subscriber->id)
)
);
expect($updated_queue->count_total)->equals(1);
expect($updated_queue->count_processed)->equals(1);
- expect($updated_queue->count_failed)->equals(0);
expect($updated_queue->count_to_process)->equals(0);
// statistics entry should be created
@@ -166,18 +163,16 @@ class SendingQueueTest extends MailPoetTest {
$updated_queue = SendingQueue::findOne($this->queue->id);
expect($updated_queue->status)->equals(SendingQueue::STATUS_COMPLETED);
- // queue subscriber processed/failed/to process count is updated
+ // queue subscriber processed/to process count is updated
$updated_queue->subscribers = $updated_queue->getSubscribers();
expect($updated_queue->subscribers)->equals(
array(
'to_process' => array(),
- 'failed' => array(),
'processed' => array($this->subscriber->id)
)
);
expect($updated_queue->count_total)->equals(1);
expect($updated_queue->count_processed)->equals(1);
- expect($updated_queue->count_failed)->equals(0);
expect($updated_queue->count_to_process)->equals(0);
// statistics entry should be created
@@ -196,8 +191,7 @@ class SendingQueueTest extends MailPoetTest {
$this->subscriber->id(),
123
),
- 'processed' => array(),
- 'failed' => array()
+ 'processed' => array()
)
);
$queue->count_total = 2;
@@ -210,18 +204,16 @@ class SendingQueueTest extends MailPoetTest {
$sending_queue_worker->process();
$updated_queue = SendingQueue::findOne($queue->id);
- // queue subscriber processed/failed/to process count is updated
+ // queue subscriber processed/to process count is updated
$updated_queue->subscribers = $updated_queue->getSubscribers();
expect($updated_queue->subscribers)->equals(
array(
'to_process' => array(),
- 'failed' => array(),
'processed' => array($this->subscriber->id)
)
);
expect($updated_queue->count_total)->equals(1);
expect($updated_queue->count_processed)->equals(1);
- expect($updated_queue->count_failed)->equals(0);
expect($updated_queue->count_to_process)->equals(0);
// statistics entry should be created only for 1 subscriber
@@ -237,8 +229,7 @@ class SendingQueueTest extends MailPoetTest {
123,
456
),
- 'processed' => array(),
- 'failed' => array()
+ 'processed' => array()
)
);
$queue->count_total = 2;
@@ -251,54 +242,19 @@ class SendingQueueTest extends MailPoetTest {
$sending_queue_worker->process();
$updated_queue = SendingQueue::findOne($queue->id);
- // queue subscriber processed/failed/to process count is updated
+ // queue subscriber processed/to process count is updated
$updated_queue->subscribers = $updated_queue->getSubscribers();
expect($updated_queue->subscribers)->equals(
array(
'to_process' => array(),
- 'failed' => array(),
'processed' => array()
)
);
expect($updated_queue->count_total)->equals(0);
expect($updated_queue->count_processed)->equals(0);
- expect($updated_queue->count_failed)->equals(0);
expect($updated_queue->count_to_process)->equals(0);
}
- function testItUpdatesFailedListWhenSendingFailed() {
- $sending_queue_worker = new SendingQueueWorker(
- $timer = false,
- Stub::make(
- new MailerTask(),
- array('send' => Stub::exactly(1, function($newsletter, $subscriber) { return false; }))
- )
- );
- $sending_queue_worker->process();
-
- // queue subscriber processed/failed/to process count is updated
- $updated_queue = SendingQueue::findOne($this->queue->id);
- $updated_queue->subscribers = $updated_queue->getSubscribers();
- expect($updated_queue->subscribers)->equals(
- array(
- 'to_process' => array(),
- 'failed' => array($this->subscriber->id),
- 'processed' => array()
- )
- );
- expect($updated_queue->count_total)->equals(1);
- expect($updated_queue->count_processed)->equals(1);
- expect($updated_queue->count_failed)->equals(1);
- expect($updated_queue->count_to_process)->equals(0);
-
- // statistics entry should not be created
- $statistics = StatisticsNewsletters::where('newsletter_id', $this->newsletter->id)
- ->where('subscriber_id', $this->subscriber->id)
- ->where('queue_id', $this->queue->id)
- ->findOne();
- expect($statistics)->false();
- }
-
function testItDoesNotSendToTrashedSubscribers() {
$sending_queue_worker = $this->sending_queue_worker;
$sending_queue_worker->mailer_task = Stub::make(
diff --git a/tests/unit/Cron/Workers/SendingQueue/Tasks/MailerTest.php b/tests/unit/Cron/Workers/SendingQueue/Tasks/MailerTest.php
index 4c9ab889ac..59cf56cc78 100644
--- a/tests/unit/Cron/Workers/SendingQueue/Tasks/MailerTest.php
+++ b/tests/unit/Cron/Workers/SendingQueue/Tasks/MailerTest.php
@@ -51,8 +51,7 @@ class MailerTaskTest extends MailPoetTest {
function testItGetsMailerLog() {
$mailer_log = $this->mailer_task->getMailerLog();
- expect(isset($mailer_log['sent']))->true();
- expect(isset($mailer_log['started']))->true();
+ expect(is_array($mailer_log))->true();
}
function testItUpdatesMailerLogSentCount() {
diff --git a/tests/unit/Mailer/MailerLogTest.php b/tests/unit/Mailer/MailerLogTest.php
index d4129e6786..ee7d9cd0e6 100644
--- a/tests/unit/Mailer/MailerLogTest.php
+++ b/tests/unit/Mailer/MailerLogTest.php
@@ -51,7 +51,8 @@ class MailerLogTest extends MailPoetTest {
function testItIncrementsSentCount() {
$mailer_log = array(
'sent' => 1,
- 'started' => time()
+ 'started' => time(),
+ 'error' => null
);
Setting::setValue(MailerLog::SETTING_NAME, $mailer_log);
MailerLog::incrementSentCount();
@@ -107,29 +108,126 @@ class MailerLogTest extends MailPoetTest {
expect($updated_mailer_log['sent'])->equals(0);
}
- function testItCanEnforceSendingLimit() {
+ function testItResumesSending() {
+ // set status to "paused"
+ $mailer_log = array('status' => MailerLog::STATUS_PAUSED);
+ MailerLog::updateMailerLog($mailer_log);
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->equals(MailerLog::STATUS_PAUSED);
+ // status is reset when sending is resumed
+ MailerLog::resumeSending();
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->null();
+ }
+
+ function testItPausesSending() {
+ $mailer_log = array(
+ 'status' => null,
+ 'retry_attempt' => MailerLog::RETRY_ATTEMPTS_LIMIT,
+ 'retry_at' => time() + 20
+ );
+ // status is set to PAUSED, retry attempt and retry at time are cleared
+ MailerLog::pauseSending($mailer_log);
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->equals(MailerLog::STATUS_PAUSED);
+ expect($mailer_log['retry_attempt'])->null();
+ expect($mailer_log['retry_at'])->null();
+ }
+
+ function itProcessesSendingError() {
+ // retry-related mailer values should be null
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['retry_attempt'])->null();
+ expect($mailer_log['retry_at'])->null();
+ expect($mailer_log['error'])->null();
+ // retry attempt should be incremented, error logged, retry attempt scheduled
+ MailerLog::processSendingError($operation = 'send', $error = 'email rejected');
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['retry_attempt'])->equals(1);
+ expect($mailer_log['retry_at'])->greaterThan(time());
+ expect($mailer_log['error'])->equals(
+ array(
+ 'operation' => 'send',
+ 'error_message' => 'email rejected'
+ )
+ );
+ }
+
+ function testItEnforcesSendingLimit() {
$mailer_config = array(
'frequency' => array(
'emails' => 2,
'interval' => 1
)
);
- $mailer_log = array(
- 'sent' => 2,
- 'started' => time()
- );
+ $mailer_log = MailerLog::createMailerLog();
+ $mailer_log['sent'] = 2;
+ $mailer_log['started'] = time();
Setting::setValue(MailerLog::SETTING_NAME, $mailer_log);
Setting::setValue(Mailer::MAILER_CONFIG_SETTING_NAME, $mailer_config);
// exception is thrown when sending limit is reached
try {
- MailerLog::enforceSendingLimit();
+ MailerLog::enforceExecutionRequirements();
self::fail('Sending frequency exception was not thrown.');
} catch(\Exception $e) {
expect($e->getMessage())->equals('Sending frequency limit has been reached.');
}
}
+ function testItEnforcesRetryAtTime() {
+ $mailer_log = MailerLog::createMailerLog();
+ $mailer_log['retry_at'] = time() + 10;
+ // exception is thrown when current time is sooner than 120 seconds
+ try {
+ MailerLog::enforceExecutionRequirements($mailer_log);
+ self::fail('Sending waiting to be retried exception was not thrown.');
+ } catch(\Exception $e) {
+ expect($e->getMessage())->equals('Sending is waiting to be retried.');
+ }
+ }
+
+ function testItEnforcesRetryAttempts() {
+ $mailer_log = MailerLog::createMailerLog();
+ $mailer_log['retry_attempt'] = 2;
+ // allow less than 3 attempts
+ expect(MailerLog::enforceExecutionRequirements($mailer_log))->null();
+ // pase sending and throw exception when more than 3 attempts
+ $mailer_log['retry_attempt'] = MailerLog::RETRY_ATTEMPTS_LIMIT;
+ try {
+ MailerLog::enforceExecutionRequirements($mailer_log);
+ self::fail('Sending paused exception was not thrown.');
+ } catch(\Exception $e) {
+ expect($e->getMessage())->equals('Sending has been paused.');
+ }
+ $mailer_log = MailerLog::getMailerLog();
+ expect($mailer_log['status'])->equals(MailerLog::STATUS_PAUSED);
+ }
+
+ function testItClearsSendingErrorLog() {
+ $mailer_log = MailerLog::createMailerLog();
+ $mailer_log['retry_attempt'] = 1;
+ $mailer_log['retry_at'] = 1;
+ $mailer_log['error'] = 1;
+ $mailer_log['status'] = 'status';
+ $mailer_log = MailerLog::clearSendingErrorLog($mailer_log);
+ expect($mailer_log['retry_attempt'])->null();
+ expect($mailer_log['retry_at'])->null();
+ expect($mailer_log['error'])->null();
+ expect($mailer_log['status'])->equals('status');
+ }
+
+ function testItEnforcesPuasedStatus() {
+ $mailer_log = MailerLog::createMailerLog();
+ $mailer_log['status'] = MailerLog::STATUS_PAUSED;
+ try {
+ MailerLog::enforceExecutionRequirements($mailer_log);
+ self::fail('Sending paused exception was not thrown.');
+ } catch(\Exception $e) {
+ expect($e->getMessage())->equals('Sending has been paused.');
+ }
+ }
+
function _after() {
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
}
diff --git a/tests/unit/Mailer/MailerTest.php b/tests/unit/Mailer/MailerTest.php
index f6359b8d0f..61b5d51c00 100644
--- a/tests/unit/Mailer/MailerTest.php
+++ b/tests/unit/Mailer/MailerTest.php
@@ -11,15 +11,6 @@ class MailerTest extends MailPoetTest {
'access_key' => '1234567890',
'secret_key' => 'abcdefghijk',
),
- array(
- 'method' => 'ElasticEmail',
- 'api_key' => 'abcdefghijk'
- ),
- array(
- 'method' => 'MailGun',
- 'domain' => 'example.com',
- 'api_key' => 'abcdefghijk'
- ),
array(
'method' => 'MailPoet',
'mailpoet_api_key' => 'abcdefghijk'
diff --git a/tests/unit/Mailer/Methods/ElasticEmailTest.php b/tests/unit/Mailer/Methods/ElasticEmailTest.php
deleted file mode 100644
index 1dd098628a..0000000000
--- a/tests/unit/Mailer/Methods/ElasticEmailTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-settings = array(
- 'method' => 'ElasticEmail',
- 'api_key' => getenv('WP_TEST_MAILER_ELASTICEMAIL_API') ?
- getenv('WP_TEST_MAILER_ELASTICEMAIL_API') :
- '1234567890'
- );
- $this->sender = array(
- 'from_name' => 'Sender',
- 'from_email' => 'staff@mailpoet.com',
- 'from_name_email' => 'Sender '
- );
- $this->reply_to = array(
- 'reply_to_name' => 'Reply To',
- 'reply_to_email' => 'reply-to@mailpoet.com',
- 'reply_to_name_email' => 'Reply To '
- );
- $this->mailer = new ElasticEmail(
- $this->settings['api_key'],
- $this->sender,
- $this->reply_to
- );
- $this->subscriber = 'Recipient ';
- $this->newsletter = array(
- 'subject' => 'testing ElasticEmail',
- 'body' => array(
- 'html' => 'HTML body',
- 'text' => 'TEXT body'
- )
- );
- }
-
- function testItCanGenerateBody() {
- $body = $this->mailer->getBody($this->newsletter, $this->subscriber);
- expect($body['api_key'])->equals($this->settings['api_key']);
- expect($body['from'])->equals($this->sender['from_email']);
- expect($body['from_name'])->equals($this->sender['from_name']);
- expect($body['reply_to'])->equals($this->reply_to['reply_to_email']);
- expect($body['reply_to_name'])->equals($this->reply_to['reply_to_name']);
- expect($body['to'])->contains($this->subscriber);
- expect($body['subject'])->equals($this->newsletter['subject']);
- expect($body['body_html'])->equals($this->newsletter['body']['html']);
- expect($body['body_text'])->equals($this->newsletter['body']['text']);
- }
-
- function testItCanCreateRequest() {
- $request = $this->mailer->request($this->newsletter, $this->subscriber);
- $body = $this->mailer->getBody($this->newsletter, $this->subscriber);
- expect($request['timeout'])->equals(10);
- expect($request['httpversion'])->equals('1.0');
- expect($request['method'])->equals('POST');
- expect($request['body'])->equals(urldecode(http_build_query($body)));
- }
-
- function testItCannotSendWithoutProperApiKey() {
- if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
- $this->mailer->api_key = 'someapi';
- $result = $this->mailer->send(
- $this->newsletter,
- $this->subscriber
- );
- expect($result)->false();
- }
-
- function testItCanSend() {
- if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
- $result = $this->mailer->send(
- $this->newsletter,
- $this->subscriber
- );
- expect($result)->true();
- }
-}
\ No newline at end of file
diff --git a/tests/unit/Mailer/Methods/MailGunTest.php b/tests/unit/Mailer/Methods/MailGunTest.php
deleted file mode 100644
index f0ee563971..0000000000
--- a/tests/unit/Mailer/Methods/MailGunTest.php
+++ /dev/null
@@ -1,99 +0,0 @@
-settings = array(
- 'method' => 'MailGun',
- 'api_key' => getenv('WP_TEST_MAILER_MAILGUN_API') ?
- getenv('WP_TEST_MAILER_MAILGUN_API') :
- '1234567890',
- 'domain' => getenv('WP_TEST_MAILER_MAILGUN_DOMAIN') ?
- getenv('WP_TEST_MAILER_MAILGUN_DOMAIN') :
- 'example.com'
- );
- $this->sender = array(
- 'from_name' => 'Sender',
- 'from_email' => 'staff@mailpoet.com',
- 'from_name_email' => 'Sender '
- );
- $this->reply_to = array(
- 'reply_to_name' => 'Reply To',
- 'reply_to_email' => 'reply-to@mailpoet.com',
- 'reply_to_name_email' => 'Reply To '
- );
- $this->mailer = new MailGun(
- $this->settings['domain'],
- $this->settings['api_key'],
- $this->sender,
- $this->reply_to
- );
- $this->subscriber = 'Recipient ';
- $this->newsletter = array(
- 'subject' => 'testing MailGun',
- 'body' => array(
- 'html' => 'HTML body',
- 'text' => 'TEXT body'
- )
- );
- }
-
- function testItCanGenerateBody() {
- $body = $this->mailer->getBody($this->newsletter, $this->subscriber);
- expect($body['from'])->equals($this->sender['from_name_email']);
- expect($body['h:Reply-To'])->equals($this->reply_to['reply_to_name_email']);
- expect($body['to'])->equals($this->subscriber);
- expect($body['subject'])->equals($this->newsletter['subject']);
- expect($body['html'])->equals($this->newsletter['body']['html']);
- expect($body['text'])->equals($this->newsletter['body']['text']);
- }
-
- function testItCanDoBasicAuth() {
- expect($this->mailer->auth())
- ->equals('Basic ' . base64_encode('api:' . $this->settings['api_key']));
- }
-
- function testItCanCreateRequest() {
- $request = $this->mailer->request($this->newsletter, $this->subscriber);
- $body = $this->mailer->getBody($this->newsletter, $this->subscriber);
- expect($request['timeout'])->equals(10);
- expect($request['httpversion'])->equals('1.0');
- expect($request['method'])->equals('POST');
- expect($request['headers']['Content-Type'])
- ->equals('application/x-www-form-urlencoded');
- expect($request['headers']['Authorization'])
- ->equals('Basic ' . base64_encode('api:' . $this->settings['api_key']));
- expect($request['body'])->equals(urldecode(http_build_query($body)));
- }
-
- function testItCannotSendWithoutProperApiKey() {
- if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
- $this->mailer->api_key = 'someapi';
- $result = $this->mailer->send(
- $this->newsletter,
- $this->subscriber
- );
- expect($result)->false();
- }
-
- function testItCannotSendWithoutProperDomain() {
- if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
- $this->mailer->url =
- str_replace($this->settings['domain'], 'somedomain', $this->mailer->url);
- $result = $this->mailer->send(
- $this->newsletter,
- $this->subscriber
- );
- expect($result)->false();
- }
-
- function testItCanSend() {
- if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
- $result = $this->mailer->send(
- $this->newsletter,
- $this->subscriber
- );
- expect($result)->true();
- }
-}
\ No newline at end of file
diff --git a/views/newsletters.html b/views/newsletters.html
index a46a72f832..3814005bf4 100644
--- a/views/newsletters.html
+++ b/views/newsletters.html
@@ -85,6 +85,7 @@
'sentToXSubscribers': __('Sent to %$1d subscribers'),
'resume': __('Resume'),
'pause': __('Pause'),
+ 'paused': __('Paused'),
'new': __('Add New'),
'templateFileMalformedError': __('This template file appears to be damaged. Please try another one.'),
@@ -232,6 +233,12 @@
'backToPostNotifications': __('Back to Post notifications'),
'sentOn': __('Sent on'),
- 'noSubscribers': __('No subscribers!')
+ 'noSubscribers': __('No subscribers!'),
+
+ 'mailerSendErrorNotice': __("We've detected an issue with the %$1s sending method that prevents us from delivering the remaining emails: %$2s"),
+ 'mailerConnectionErrorNotice': __("We've detected a connection issue that prevents us from delivering the remaining emails: %$1s"),
+ 'mailerResumeSendingNotice': __('As a result, all sending has been paused. Please resolve the issue before continuing.'),
+ 'mailerResumeSendingButton': __('Resume sending'),
+ 'mailerSendingResumedNotice': __("Sending has been resumed.")
}) %>
-<% endblock %>
+<% endblock %>
\ No newline at end of file
|