diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx
index 0dac115b94..a21fa7ec95 100644
--- a/assets/js/src/listing/listing.jsx
+++ b/assets/js/src/listing/listing.jsx
@@ -676,6 +676,12 @@ define(
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
+ // columns
+ var columns = this.props.columns || [];
+ columns = columns.filter(function(column) {
+ return (column.display === undefined || !!(column.display) === true);
+ });
+
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
@@ -761,7 +767,7 @@ define(
selection={ this.state.selection }
sort_by={ sort_by }
sort_order={ sort_order }
- columns={ this.props.columns }
+ columns={ columns }
is_selectable={ bulk_actions.length > 0 } />
@@ -771,7 +777,7 @@ define(
onRestoreItem={ this.handleRestoreItem }
onTrashItem={ this.handleTrashItem }
onRefreshItems={ this.handleRefreshItems }
- columns={ this.props.columns }
+ columns={ columns }
is_selectable={ bulk_actions.length > 0 }
onSelectItem={ this.handleSelectItem }
onSelectAll={ this.handleSelectAll }
@@ -791,7 +797,7 @@ define(
selection={ this.state.selection }
sort_by={ sort_by }
sort_order={ sort_order }
- columns={ this.props.columns }
+ columns={ columns }
is_selectable={ bulk_actions.length > 0 } />
diff --git a/assets/js/src/newsletters/listings/notification.jsx b/assets/js/src/newsletters/listings/notification.jsx
index 69f51ed4da..381ab0c869 100644
--- a/assets/js/src/newsletters/listings/notification.jsx
+++ b/assets/js/src/newsletters/listings/notification.jsx
@@ -68,17 +68,12 @@ var columns = [
label: MailPoet.I18n.t('status')
},
{
- name: 'segments',
- label: MailPoet.I18n.t('lists')
+ name: 'settings',
+ label: MailPoet.I18n.t('settings')
},
{
- name: 'statistics',
- label: MailPoet.I18n.t('statistics')
- },
- {
- name: 'created_at',
- label: MailPoet.I18n.t('createdOn'),
- sortable: true
+ name: 'history',
+ label: MailPoet.I18n.t('history')
},
{
name: 'updated_at',
@@ -96,6 +91,16 @@ var bulk_actions = [
];
var newsletter_actions = [
+ {
+ name: 'view',
+ link: function(newsletter) {
+ return (
+
+ {MailPoet.I18n.t('preview')}
+
+ );
+ }
+ },
{
name: 'edit',
link: function(newsletter) {
@@ -106,6 +111,26 @@ var newsletter_actions = [
);
}
},
+ {
+ name: 'duplicate',
+ label: MailPoet.I18n.t('duplicate'),
+ onClick: function(newsletter, refresh) {
+ return MailPoet.Ajax.post({
+ endpoint: 'newsletters',
+ action: 'duplicate',
+ data: newsletter.id
+ }).done(function(response) {
+ if (response !== false && response.subject !== undefined) {
+ MailPoet.Notice.success(
+ (MailPoet.I18n.t('newsletterDuplicated')).replace(
+ '%$1s', response.subject
+ )
+ );
+ }
+ refresh();
+ });
+ }
+ },
{
name: 'trash'
}
@@ -158,31 +183,6 @@ const NewsletterListNotification = React.createClass({
);
},
- renderStatistics: function(newsletter) {
- if (!newsletter.statistics || !newsletter.queue || newsletter.queue.count_processed == 0 || newsletter.queue.status === 'scheduled') {
- return (
-
- {MailPoet.I18n.t('notSentYet')}
-
- );
- }
-
- var percentage_clicked = Math.round(
- (newsletter.statistics.clicked * 100) / (newsletter.queue.count_processed)
- );
- var percentage_opened = Math.round(
- (newsletter.statistics.opened * 100) / (newsletter.queue.count_processed)
- );
- var percentage_unsubscribed = Math.round(
- (newsletter.statistics.unsubscribed * 100) / (newsletter.queue.count_processed)
- );
-
- return (
-
- { percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
-
- );
- },
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
@@ -190,10 +190,6 @@ const NewsletterListNotification = React.createClass({
'has-row-actions'
);
- var segments = newsletter.segments.map(function(segment) {
- return segment.name
- }).join(', ');
-
return (
@@ -207,14 +203,11 @@ const NewsletterListNotification = React.createClass({
|
{ this.renderStatus(newsletter) }
|
-
- { segments }
+ |
+ { this.renderSettings(newsletter) }
|
-
- { this.renderStatistics(newsletter) }
- |
-
- { MailPoet.Date.format(newsletter.created_at) }
+ |
+ { MailPoet.I18n.t('viewHistory') }
|
{ MailPoet.Date.format(newsletter.updated_at) }
diff --git a/assets/js/src/newsletters/listings/standard.jsx b/assets/js/src/newsletters/listings/standard.jsx
index 78ad5201c8..98e2f8b985 100644
--- a/assets/js/src/newsletters/listings/standard.jsx
+++ b/assets/js/src/newsletters/listings/standard.jsx
@@ -9,6 +9,8 @@ import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
+const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
+
const messages = {
onTrash(response) {
const count = ~~response;
@@ -73,7 +75,8 @@ var columns = [
},
{
name: 'statistics',
- label: MailPoet.I18n.t('statistics')
+ label: MailPoet.I18n.t('statistics'),
+ display: mailpoet_tracking_enabled
},
{
name: 'updated_at',
@@ -82,6 +85,7 @@ var columns = [
}
];
+
var bulk_actions = [
{
name: 'trash',
@@ -231,30 +235,33 @@ const NewsletterListStandard = React.createClass({
);
}
},
- renderStatistics: function(item) {
- if(!item.statistics || !item.queue || item.queue.count_processed == 0 || item.queue.status === 'scheduled') {
- return (
-
- {MailPoet.I18n.t('notSentYet')}
-
- );
+ renderStatistics: function(newsletter) {
+ if (mailpoet_tracking_enabled === false) {
+ return;
}
- var percentage_clicked = Math.round(
- (item.statistics.clicked * 100) / (item.queue.count_processed)
- );
- var percentage_opened = Math.round(
- (item.statistics.opened * 100) / (item.queue.count_processed)
- );
- var percentage_unsubscribed = Math.round(
- (item.statistics.unsubscribed * 100) / (item.queue.count_processed)
- );
+ if(newsletter.statistics && newsletter.queue && newsletter.queue.status !== 'scheduled') {
+ const total_sent = ~~(newsletter.queue.count_processed);
+ const percentage_clicked = Math.round(
+ (~~(newsletter.statistics.clicked) * 100) / total_sent
+ );
+ const percentage_opened = Math.round(
+ (~~(newsletter.statistics.opened) * 100) / total_sent
+ );
+ const percentage_unsubscribed = Math.round(
+ (~~(newsletter.statistics.unsubscribed) * 100) / total_sent
+ );
- return (
-
- { percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
-
- );
+ return (
+
+ { percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
+
+ );
+ } else {
+ return (
+ {MailPoet.I18n.t('notSentYet')}
+ );
+ }
},
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
@@ -283,9 +290,11 @@ const NewsletterListStandard = React.createClass({
|
{ segments }
|
-
- { this.renderStatistics(newsletter) }
- |
+ {(mailpoet_tracking_enabled === true) ? (
+
+ { this.renderStatistics(newsletter) }
+ |
+ ) : null }
{ MailPoet.Date.format(newsletter.updated_at) }
|
diff --git a/assets/js/src/newsletters/listings/welcome.jsx b/assets/js/src/newsletters/listings/welcome.jsx
index a8ed7aee96..0c53af1e96 100644
--- a/assets/js/src/newsletters/listings/welcome.jsx
+++ b/assets/js/src/newsletters/listings/welcome.jsx
@@ -8,6 +8,7 @@ import ListingTabs from 'newsletters/listings/tabs.jsx'
import classNames from 'classnames'
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
+import _ from 'underscore'
const messages = {
onTrash(response) {
@@ -165,21 +166,48 @@ const NewsletterListWelcome = React.createClass({
}.bind(this));
},
renderStatus: function(newsletter) {
+ let total_sent;
+ total_sent = (
+ MailPoet.I18n.t('sentToXSubscribers')
+ .replace('%$1d', newsletter.total_sent)
+ );
+
return (
-
+
+
+
{ total_sent }
+
);
},
renderSettings: function(newsletter) {
+ let settings;
+
+ switch (newsletter.options.event) {
+ case 'user':
+ // WP User
+ settings = MailPoet.I18n.t('onWordpressUserRegistration');
+ break;
+
+ case 'segment':
+ // get segment
+ const segment = _.find(mailpoet_segments, function(segment) {
+ return (~~(segment.id) === ~~(newsletter.options.segment));
+ });
+
+ settings = MailPoet.I18n.t('onSubscriptionToList') + ' ' +segment.name;
+ break;
+ }
+
return (
- Settings...
+ { settings }
);
},
diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php
index 79bc7dafc8..d9f2361ca3 100644
--- a/lib/Config/Menu.php
+++ b/lib/Config/Menu.php
@@ -413,6 +413,8 @@ class Menu {
24
);
+ $data['tracking_enabled'] = Setting::getValue('tracking.enabled');
+
wp_enqueue_script('jquery-ui');
wp_enqueue_script('jquery-ui-datepicker');
diff --git a/lib/Cron/Workers/SendingQueue.php b/lib/Cron/Workers/SendingQueue.php
index 819a9bf519..6f52ebfba1 100644
--- a/lib/Cron/Workers/SendingQueue.php
+++ b/lib/Cron/Workers/SendingQueue.php
@@ -4,6 +4,7 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
+use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Models\NewsletterPost;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsNewsletters;
@@ -23,7 +24,6 @@ class SendingQueue {
private $timer;
const BATCH_SIZE = 50;
const DIVIDER = '***MailPoet***';
- const STATUS_COMPLETED = 'completed';
function __construct($timer = false) {
$this->mta_config = $this->getMailerConfig();
@@ -295,7 +295,7 @@ class SendingQueue {
}
function getQueues() {
- return \MailPoet\Models\SendingQueue::orderByDesc('priority')
+ return SendingQueueModel::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
@@ -321,7 +321,7 @@ class SendingQueue {
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = current_time('mysql');
- $queue->status = self::STATUS_COMPLETED;
+ $queue->status = SendingQueueModel::STATUS_COMPLETED;
}
$queue->subscribers = serialize((array) $queue->subscribers);
$queue->save();
diff --git a/lib/Models/Newsletter.php b/lib/Models/Newsletter.php
index 4f4d6a2653..6732d0e0d1 100644
--- a/lib/Models/Newsletter.php
+++ b/lib/Models/Newsletter.php
@@ -1,5 +1,6 @@
asArray();
- unset($data['id']);
+ // get current newsletter's data as an array
+ $newsletter_data = $this->asArray();
- $duplicate = self::create();
+ // remove id so that it creates a new record
+ unset($newsletter_data['id']);
+
+ // merge data with newsletter data (allows override)
+ $data = array_merge($newsletter_data, $data);
+
+ $duplicate = self::create();
$duplicate->hydrate($data);
+
+ // reset timestamps
$duplicate->set_expr('created_at', 'NOW()');
$duplicate->set_expr('updated_at', 'NOW()');
$duplicate->set_expr('deleted_at', 'NULL');
@@ -67,15 +76,25 @@ class Newsletter extends Model {
// reset status
$duplicate->set('status', self::STATUS_DRAFT);
- // TODO: duplicate segments linked (if need be)
+ $duplicate->save();
- // TODO: duplicate options (if need be)
+ if($duplicate->getErrors() === false) {
+ // create relationships between duplicate and segments
+ $segments = $this->segments()->findArray();
- if($duplicate->save()) {
- return $duplicate;
- } else {
- return false;
+ if(!empty($segments)) {
+ foreach($segments as $segment) {
+ $relation = NewsletterSegment::create();
+ $relation->segment_id = $segment['id'];
+ $relation->newsletter_id = $duplicate->id();
+ $result = $relation->save();
+ }
+ }
+
+ // TODO: duplicate options (if need be)
}
+
+ return $duplicate;
}
function asArray() {
@@ -133,6 +152,24 @@ class Newsletter extends Model {
return $this;
}
+ function withOptions() {
+ $options = $this->options()->findArray();
+ if(empty($options)) {
+ $this->options = array();
+ } else {
+ $this->options = Helpers::arrayColumn($options, 'value', 'name');
+ }
+ return $this;
+ }
+
+ function withTotalSent() {
+ // total of subscribers who received the email
+ $this->total_sent = (int)SendingQueue::where('newsletter_id', $this->id)
+ ->where('status', SendingQueue::STATUS_COMPLETED)
+ ->sum('count_processed');
+ return $this;
+ }
+
function withStatistics() {
$statistics = $this->getStatistics();
if($statistics === false) {
@@ -140,6 +177,7 @@ class Newsletter extends Model {
} else {
$this->statistics = $statistics->asArray();
}
+
return $this;
}
@@ -149,9 +187,9 @@ class Newsletter extends Model {
}
return SendingQueue::tableAlias('queues')
->selectExpr(
- 'count(DISTINCT(clicks.subscriber_id)) as clicked, ' .
- 'count(DISTINCT(opens.subscriber_id)) as opened, ' .
- 'count(DISTINCT(unsubscribes.subscriber_id)) as unsubscribed '
+ 'COUNT(DISTINCT(clicks.subscriber_id)) as clicked, ' .
+ 'COUNT(DISTINCT(opens.subscriber_id)) as opened, ' .
+ 'COUNT(DISTINCT(unsubscribes.subscriber_id)) as unsubscribed '
)
->leftOuterJoin(
MP_STATISTICS_CLICKS_TABLE,
@@ -299,14 +337,6 @@ class Newsletter extends Model {
case self::TYPE_WELCOME:
case self::TYPE_NOTIFICATION:
$groups = array_merge($groups, array(
- array(
- 'name' => self::STATUS_DRAFT,
- 'label' => __('Not active'),
- 'count' => Newsletter::getPublished()
- ->filter('filterType', $type)
- ->filter('filterStatus', self::STATUS_DRAFT)
- ->count()
- ),
array(
'name' => self::STATUS_ACTIVE,
'label' => __('Active'),
@@ -314,6 +344,14 @@ class Newsletter extends Model {
->filter('filterType', $type)
->filter('filterStatus', self::STATUS_ACTIVE)
->count()
+ ),
+ array(
+ 'name' => self::STATUS_DRAFT,
+ 'label' => __('Not active'),
+ 'count' => Newsletter::getPublished()
+ ->filter('filterType', $type)
+ ->filter('filterStatus', self::STATUS_DRAFT)
+ ->count()
)
));
break;
@@ -379,7 +417,15 @@ class Newsletter extends Model {
}
static function listingQuery($data = array()) {
- return self::filter('filterType', $data['tab'])
+ return self::select(array(
+ 'id',
+ 'subject',
+ 'type',
+ 'status',
+ 'updated_at',
+ 'deleted_at'
+ ))
+ ->filter('filterType', $data['tab'])
->filter('filterBy', $data)
->filter('groupBy', $data)
->filter('search', $data['search']);
diff --git a/lib/Models/SendingQueue.php b/lib/Models/SendingQueue.php
index c6fd1c7978..b5ea965826 100644
--- a/lib/Models/SendingQueue.php
+++ b/lib/Models/SendingQueue.php
@@ -6,6 +6,8 @@ if(!defined('ABSPATH')) exit;
class SendingQueue extends Model {
public static $_table = MP_SENDING_QUEUES_TABLE;
+ const STATUS_COMPLETED = 'completed';
+
function __construct() {
parent::__construct();
}
diff --git a/lib/Router/Newsletters.php b/lib/Router/Newsletters.php
index 18b6610b11..d7b6e1e91e 100644
--- a/lib/Router/Newsletters.php
+++ b/lib/Router/Newsletters.php
@@ -150,9 +150,13 @@ class Newsletters {
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
- $result = $newsletter->duplicate(array(
+ $duplicate = $newsletter->duplicate(array(
'subject' => sprintf(__('Copy of %s'), $newsletter->subject)
- ))->asArray();
+ ));
+
+ if($duplicate !== false && $duplicate->getErrors() === false) {
+ $result = $newsletter->asArray();
+ }
}
return $result;
}
@@ -256,7 +260,13 @@ class Newsletters {
->withStatistics();
} else if($newsletter->type === Newsletter::TYPE_WELCOME) {
$newsletter
+ ->withOptions()
+ ->withTotalSent()
->withStatistics();
+
+ $options = $newsletter->options()->findArray();
+ $newsletter->options = Helpers::arrayColumn($options, 'value', 'name');
+
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
$newsletter
->withSegments()
diff --git a/views/newsletters.html b/views/newsletters.html
index 1ab00011fd..6389603dbc 100644
--- a/views/newsletters.html
+++ b/views/newsletters.html
@@ -14,6 +14,7 @@
var mailpoet_schedule_time_of_day = <%= json_encode(schedule_time_of_day) %>;
var mailpoet_date_display_format = "<%= wp_date_format() %>";
var mailpoet_date_storage_format = "Y-m-d";
+ var mailpoet_tracking_enabled = <%= json_encode(tracking_enabled) %>;
<% endblock %>
@@ -61,6 +62,8 @@
'statistics': __('Opened, Clicked, Unsubscribed'),
'lists': __('Lists'),
'settings': __('Settings'),
+ 'history': __('History'),
+ 'viewHistory': __('View history'),
'createdOn': __('Created on'),
'lastModifiedOn': __('Last modified on'),
'oneNewsletterTrashed': __('1 newsletter was moved to the trash.'),
@@ -79,6 +82,7 @@
'active': __('Active'),
'inactive': __('Not Active'),
'newsletterQueueCompleted': __('Sent to %$1d of %$2d.'),
+ 'sentToXSubscribers': __('Sent to %$1d subscribers.'),
'resume': __('Resume'),
'pause': __('Pause'),
'new': __('New'),
@@ -133,7 +137,7 @@
'selectEventToSendWelcomeEmail': __('Select an event to send this welcome email'),
'onSubscriptionToList': __('When someone subscribes to the list...'),
- 'onWordpressUserRegistration': __('When a new Wordrpess user is added to your site...'),
+ 'onWordpressUserRegistration': __('When a new WordPress user is added to your site...'),
'delayImmediately': __('immediately'),
'delayHoursAfter': __('hour(s) after'),
'delayDaysAfter': __('day(s) after'),