Merge pull request #552 from mailpoet/newsletter_listing
Post notification history listing
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import MailPoet from 'mailpoet'
|
import MailPoet from 'mailpoet'
|
||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import _ from 'underscore'
|
||||||
import { Router, Link } from 'react-router'
|
import { Router, Link } from 'react-router'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import ListingBulkActions from 'listing/bulk_actions.jsx'
|
import ListingBulkActions from 'listing/bulk_actions.jsx'
|
||||||
@ -13,7 +14,7 @@ import ListingFilters from 'listing/filters.jsx'
|
|||||||
const ListingItem = React.createClass({
|
const ListingItem = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
toggled: true
|
expanded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleSelectItem: function(e) {
|
handleSelectItem: function(e) {
|
||||||
@ -34,7 +35,7 @@ const ListingItem = React.createClass({
|
|||||||
this.props.onDeleteItem(id);
|
this.props.onDeleteItem(id);
|
||||||
},
|
},
|
||||||
handleToggleItem: function(id) {
|
handleToggleItem: function(id) {
|
||||||
this.setState({ toggled: !this.state.toggled });
|
this.setState({ expanded: !this.state.expanded });
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var checkbox = false;
|
var checkbox = false;
|
||||||
@ -182,7 +183,7 @@ const ListingItem = React.createClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const row_classes = classNames({ 'is-expanded': !this.state.toggled });
|
const row_classes = classNames({ 'is-expanded': this.state.expanded });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={ row_classes }>
|
<tr className={ row_classes }>
|
||||||
@ -303,13 +304,12 @@ const Listing = React.createClass({
|
|||||||
getParam: function(param) {
|
getParam: function(param) {
|
||||||
const regex = /(.*)\[(.*)\]/;
|
const regex = /(.*)\[(.*)\]/;
|
||||||
const matches = regex.exec(param);
|
const matches = regex.exec(param);
|
||||||
return [matches[1], matches[2]]
|
return [matches[1], matches[2]];
|
||||||
},
|
},
|
||||||
initWithParams: function(params) {
|
initWithParams: function(params) {
|
||||||
let state = this.getInitialState();
|
let state = this.getInitialState();
|
||||||
|
|
||||||
// check for url params
|
// check for url params
|
||||||
if (params.splat !== undefined) {
|
if (params.splat) {
|
||||||
params.splat.split('/').map(param => {
|
params.splat.split('/').map(param => {
|
||||||
let [key, value] = this.getParam(param);
|
let [key, value] = this.getParam(param);
|
||||||
switch(key) {
|
switch(key) {
|
||||||
@ -348,6 +348,17 @@ const Listing = React.createClass({
|
|||||||
this.getItems();
|
this.getItems();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
getParams: function() {
|
||||||
|
// get all route parameters (without the "splat")
|
||||||
|
let params = _.omit(this.props.params, 'splat');
|
||||||
|
// TODO:
|
||||||
|
// find a way to set the "type" in the routes definition
|
||||||
|
// so that it appears in `this.props.params`
|
||||||
|
if (this.props.type) {
|
||||||
|
params.type = this.props.type;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
},
|
||||||
setParams: function() {
|
setParams: function() {
|
||||||
if (this.props.location) {
|
if (this.props.location) {
|
||||||
let params = Object.keys(this.state)
|
let params = Object.keys(this.state)
|
||||||
@ -378,18 +389,38 @@ const Listing = React.createClass({
|
|||||||
.filter(key => { return (key !== undefined) })
|
.filter(key => { return (key !== undefined) })
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
// prepend url with "tab" if specified
|
// set url
|
||||||
if (this.props.tab !== undefined) {
|
let url = this.getUrlWithParams(params);
|
||||||
params = `/${ this.props.tab }/${ params }`;
|
|
||||||
} else {
|
|
||||||
params = `/${ params }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.location.pathname !== params) {
|
if (this.props.location.pathname !== url) {
|
||||||
this.context.router.push(`${params}`);
|
this.context.router.push(`${url}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getUrlWithParams: function(params) {
|
||||||
|
let base_url = (this.props.base_url !== undefined)
|
||||||
|
? this.props.base_url
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (base_url !== null) {
|
||||||
|
base_url = this.setBaseUrlParams(base_url);
|
||||||
|
return `/${ base_url }/${ params }`;
|
||||||
|
} else {
|
||||||
|
return `/${ params }`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBaseUrlParams: function(base_url) {
|
||||||
|
if (base_url.indexOf(':') !== -1) {
|
||||||
|
const params = this.getParams();
|
||||||
|
Object.keys(params).map((key) => {
|
||||||
|
if (base_url.indexOf(':'+key) !== -1) {
|
||||||
|
base_url = base_url.replace(':'+key, params[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return base_url;
|
||||||
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
if (this.isMounted()) {
|
if (this.isMounted()) {
|
||||||
const params = this.props.params || {};
|
const params = this.props.params || {};
|
||||||
@ -416,7 +447,7 @@ const Listing = React.createClass({
|
|||||||
endpoint: this.props.endpoint,
|
endpoint: this.props.endpoint,
|
||||||
action: 'listing',
|
action: 'listing',
|
||||||
data: {
|
data: {
|
||||||
tab: (this.props.tab) ? this.props.tab : '',
|
params: this.getParams(),
|
||||||
offset: (this.state.page - 1) * this.state.limit,
|
offset: (this.state.page - 1) * this.state.limit,
|
||||||
limit: this.state.limit,
|
limit: this.state.limit,
|
||||||
group: this.state.group,
|
group: this.state.group,
|
||||||
@ -531,7 +562,7 @@ const Listing = React.createClass({
|
|||||||
|
|
||||||
var data = params || {};
|
var data = params || {};
|
||||||
data.listing = {
|
data.listing = {
|
||||||
tab: (this.props.tab) ? this.props.tab : '',
|
params: this.getParams(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 0,
|
limit: 0,
|
||||||
filter: this.state.filter,
|
filter: this.state.filter,
|
||||||
|
152
assets/js/src/newsletters/listings/mixins.jsx
Normal file
152
assets/js/src/newsletters/listings/mixins.jsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import MailPoet from 'mailpoet'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import jQuery from 'jquery'
|
||||||
|
|
||||||
|
const _QueueMixin = {
|
||||||
|
pauseSending: function(newsletter) {
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
endpoint: 'sendingQueue',
|
||||||
|
action: 'pause',
|
||||||
|
data: newsletter.id
|
||||||
|
}).done(function() {
|
||||||
|
jQuery('#resume_'+newsletter.id).show();
|
||||||
|
jQuery('#pause_'+newsletter.id).hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resumeSending: function(newsletter) {
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
endpoint: 'sendingQueue',
|
||||||
|
action: 'resume',
|
||||||
|
data: newsletter.id
|
||||||
|
}).done(function() {
|
||||||
|
jQuery('#pause_'+newsletter.id).show();
|
||||||
|
jQuery('#resume_'+newsletter.id).hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderQueueStatus: function(newsletter) {
|
||||||
|
if (!newsletter.queue) {
|
||||||
|
return (
|
||||||
|
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (newsletter.queue.status === 'scheduled') {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{ MailPoet.I18n.t('scheduledFor') } { MailPoet.Date.format(newsletter.queue.scheduled_at) }
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const progressClasses = classNames(
|
||||||
|
'mailpoet_progress',
|
||||||
|
{ 'mailpoet_progress_complete': newsletter.queue.status === 'completed'}
|
||||||
|
);
|
||||||
|
|
||||||
|
// calculate percentage done
|
||||||
|
const percentage = Math.round(
|
||||||
|
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
|
||||||
|
);
|
||||||
|
|
||||||
|
let label;
|
||||||
|
|
||||||
|
if (newsletter.queue.status === 'completed') {
|
||||||
|
label = (
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
MailPoet.I18n.t('newsletterQueueCompleted')
|
||||||
|
.replace(
|
||||||
|
"%$1d",
|
||||||
|
newsletter.queue.count_processed - newsletter.queue.count_failed
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%$2d",
|
||||||
|
newsletter.queue.count_total
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
label = (
|
||||||
|
<span>
|
||||||
|
{ newsletter.queue.count_processed } / { newsletter.queue.count_total }
|
||||||
|
|
||||||
|
<a
|
||||||
|
id={ 'resume_'+newsletter.id }
|
||||||
|
className="button"
|
||||||
|
style={{ display: (newsletter.queue.status === 'paused')
|
||||||
|
? 'inline-block': 'none' }}
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={ this.resumeSending.bind(null, newsletter) }
|
||||||
|
>{MailPoet.I18n.t('resume')}</a>
|
||||||
|
<a
|
||||||
|
id={ 'pause_'+newsletter.id }
|
||||||
|
className="button mailpoet_pause"
|
||||||
|
style={{ display: (newsletter.queue.status === null)
|
||||||
|
? 'inline-block': 'none' }}
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={ this.pauseSending.bind(null, newsletter) }
|
||||||
|
>{MailPoet.I18n.t('pause')}</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ progressClasses }>
|
||||||
|
<span
|
||||||
|
className="mailpoet_progress_bar"
|
||||||
|
style={ { width: percentage + "%"} }
|
||||||
|
></span>
|
||||||
|
<span className="mailpoet_progress_label">
|
||||||
|
{ percentage + "%" }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p style={{ textAlign:'center' }}>
|
||||||
|
{ label }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const _StatisticsMixin = {
|
||||||
|
renderStatistics: function(newsletter) {
|
||||||
|
if (
|
||||||
|
newsletter.statistics
|
||||||
|
&& newsletter.queue
|
||||||
|
&& newsletter.queue.status !== 'scheduled'
|
||||||
|
) {
|
||||||
|
const total_sent = ~~(newsletter.queue.count_processed);
|
||||||
|
|
||||||
|
let percentage_clicked = 0;
|
||||||
|
let percentage_opened = 0;
|
||||||
|
let percentage_unsubscribed = 0;
|
||||||
|
|
||||||
|
if (total_sent > 0) {
|
||||||
|
percentage_clicked = Math.round(
|
||||||
|
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
||||||
|
);
|
||||||
|
percentage_opened = Math.round(
|
||||||
|
(~~(newsletter.statistics.opened) * 100) / total_sent
|
||||||
|
);
|
||||||
|
percentage_unsubscribed = Math.round(
|
||||||
|
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { _QueueMixin as QueueMixin };
|
||||||
|
export { _StatisticsMixin as StatisticsMixin };
|
@ -252,6 +252,20 @@ const NewsletterListNotification = React.createClass({
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
renderHistoryLink: function(newsletter) {
|
||||||
|
const childrenCount = ~~(newsletter.children_count);
|
||||||
|
if (childrenCount === 0) {
|
||||||
|
return (
|
||||||
|
MailPoet.I18n.t('notSentYet')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={ `/notification/history/${ newsletter.id }` }
|
||||||
|
>{ MailPoet.I18n.t('viewHistory') }</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
renderItem: function(newsletter, actions) {
|
renderItem: function(newsletter, actions) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -277,7 +291,7 @@ const NewsletterListNotification = React.createClass({
|
|||||||
{ this.renderSettings(newsletter) }
|
{ this.renderSettings(newsletter) }
|
||||||
</td>
|
</td>
|
||||||
<td className="column" data-colname={ MailPoet.I18n.t('history') }>
|
<td className="column" data-colname={ MailPoet.I18n.t('history') }>
|
||||||
<a href="#TODO">{ MailPoet.I18n.t('viewHistory') }</a>
|
{ this.renderHistoryLink(newsletter) }
|
||||||
</td>
|
</td>
|
||||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||||
@ -299,7 +313,8 @@ const NewsletterListNotification = React.createClass({
|
|||||||
location={ this.props.location }
|
location={ this.props.location }
|
||||||
params={ this.props.params }
|
params={ this.props.params }
|
||||||
endpoint="newsletters"
|
endpoint="newsletters"
|
||||||
tab="notification"
|
type="notification"
|
||||||
|
base_url="notification"
|
||||||
onRenderItem={ this.renderItem }
|
onRenderItem={ this.renderItem }
|
||||||
columns={ columns }
|
columns={ columns }
|
||||||
bulk_actions={ bulk_actions }
|
bulk_actions={ bulk_actions }
|
||||||
|
125
assets/js/src/newsletters/listings/notification_history.jsx
Normal file
125
assets/js/src/newsletters/listings/notification_history.jsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Router, Link } from 'react-router'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import jQuery from 'jquery'
|
||||||
|
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'
|
||||||
|
|
||||||
|
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'subject',
|
||||||
|
label: MailPoet.I18n.t('subject'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: MailPoet.I18n.t('status')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'segments',
|
||||||
|
label: MailPoet.I18n.t('lists')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'statistics',
|
||||||
|
label: MailPoet.I18n.t('statistics'),
|
||||||
|
display: mailpoet_tracking_enabled
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'processed_at',
|
||||||
|
label: MailPoet.I18n.t('sentOn'),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const newsletter_actions = [
|
||||||
|
{
|
||||||
|
name: 'view',
|
||||||
|
link: function(newsletter) {
|
||||||
|
return (
|
||||||
|
<a href={ newsletter.preview_url } target="_blank">
|
||||||
|
{MailPoet.I18n.t('preview')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const NewsletterListNotificationHistory = React.createClass({
|
||||||
|
mixins: [QueueMixin, StatisticsMixin],
|
||||||
|
renderItem: function(newsletter, actions) {
|
||||||
|
const rowClasses = classNames(
|
||||||
|
'manage-column',
|
||||||
|
'column-primary',
|
||||||
|
'has-row-actions'
|
||||||
|
);
|
||||||
|
|
||||||
|
const segments = newsletter.segments.map(function(segment) {
|
||||||
|
return segment.name
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<td className={ rowClasses }>
|
||||||
|
<strong>
|
||||||
|
<a
|
||||||
|
href={ newsletter.preview_url }
|
||||||
|
target="_blank"
|
||||||
|
>{ newsletter.subject }</a>
|
||||||
|
</strong>
|
||||||
|
{ actions }
|
||||||
|
</td>
|
||||||
|
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||||
|
{ this.renderQueueStatus(newsletter) }
|
||||||
|
</td>
|
||||||
|
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
|
||||||
|
{ segments }
|
||||||
|
</td>
|
||||||
|
{ (mailpoet_tracking_enabled === true) ? (
|
||||||
|
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||||
|
{ this.renderStatistics(newsletter) }
|
||||||
|
</td>
|
||||||
|
) : null }
|
||||||
|
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||||
|
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||||
|
</td>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="title">
|
||||||
|
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<ListingTabs tab="notification" />
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className="page-title-action"
|
||||||
|
to="/notification"
|
||||||
|
>{MailPoet.I18n.t('backToPostNotifications')}</Link>
|
||||||
|
|
||||||
|
<Listing
|
||||||
|
limit={ mailpoet_listing_per_page }
|
||||||
|
location={ this.props.location }
|
||||||
|
params={ this.props.params }
|
||||||
|
endpoint="newsletters"
|
||||||
|
type="notification_history"
|
||||||
|
base_url="notification/history/:parent_id"
|
||||||
|
onRenderItem={ this.renderItem }
|
||||||
|
columns={columns}
|
||||||
|
item_actions={ newsletter_actions }
|
||||||
|
auto_refresh={ true }
|
||||||
|
sort_by="updated_at"
|
||||||
|
sort_order="desc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NewsletterListNotificationHistory;
|
@ -7,6 +7,8 @@ import MailPoet from 'mailpoet'
|
|||||||
import Listing from 'listing/listing.jsx'
|
import Listing from 'listing/listing.jsx'
|
||||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||||
|
|
||||||
|
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
|
||||||
|
|
||||||
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
|
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
@ -139,135 +141,7 @@ const newsletter_actions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const NewsletterListStandard = React.createClass({
|
const NewsletterListStandard = React.createClass({
|
||||||
pauseSending: function(newsletter) {
|
mixins: [QueueMixin, StatisticsMixin],
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'sendingQueue',
|
|
||||||
action: 'pause',
|
|
||||||
data: newsletter.id
|
|
||||||
}).done(function() {
|
|
||||||
jQuery('#resume_'+newsletter.id).show();
|
|
||||||
jQuery('#pause_'+newsletter.id).hide();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
resumeSending: function(newsletter) {
|
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'sendingQueue',
|
|
||||||
action: 'resume',
|
|
||||||
data: newsletter.id
|
|
||||||
}).done(function() {
|
|
||||||
jQuery('#pause_'+newsletter.id).show();
|
|
||||||
jQuery('#resume_'+newsletter.id).hide();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderStatus: function(newsletter) {
|
|
||||||
if (!newsletter.queue) {
|
|
||||||
return (
|
|
||||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (newsletter.queue.status === 'scheduled') {
|
|
||||||
return (
|
|
||||||
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(newsletter.queue.scheduled_at) } </span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const progressClasses = classNames(
|
|
||||||
'mailpoet_progress',
|
|
||||||
{ 'mailpoet_progress_complete': newsletter.queue.status === 'completed'}
|
|
||||||
);
|
|
||||||
|
|
||||||
// calculate percentage done
|
|
||||||
const percentage = Math.round(
|
|
||||||
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
|
|
||||||
);
|
|
||||||
|
|
||||||
let label;
|
|
||||||
|
|
||||||
if (newsletter.queue.status === 'completed') {
|
|
||||||
label = (
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
MailPoet.I18n.t('newsletterQueueCompleted')
|
|
||||||
.replace("%$1d", newsletter.queue.count_processed - newsletter.queue.count_failed)
|
|
||||||
.replace("%$2d", newsletter.queue.count_total)
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
label = (
|
|
||||||
<span>
|
|
||||||
{ newsletter.queue.count_processed } / { newsletter.queue.count_total }
|
|
||||||
|
|
||||||
<a
|
|
||||||
id={ 'resume_'+newsletter.id }
|
|
||||||
className="button"
|
|
||||||
style={{ display: (newsletter.queue.status === 'paused') ? 'inline-block': 'none' }}
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={ this.resumeSending.bind(null, newsletter) }
|
|
||||||
>{MailPoet.I18n.t('resume')}</a>
|
|
||||||
<a
|
|
||||||
id={ 'pause_'+newsletter.id }
|
|
||||||
className="button mailpoet_pause"
|
|
||||||
style={{ display: (newsletter.queue.status === null) ? 'inline-block': 'none' }}
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={ this.pauseSending.bind(null, newsletter) }
|
|
||||||
>{MailPoet.I18n.t('pause')}</a>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={ progressClasses }>
|
|
||||||
<span
|
|
||||||
className="mailpoet_progress_bar"
|
|
||||||
style={ { width: percentage + "%"} }
|
|
||||||
></span>
|
|
||||||
<span className="mailpoet_progress_label">
|
|
||||||
{ percentage + "%" }
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p style={{ textAlign:'center' }}>
|
|
||||||
{ label }
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderStatistics: function(newsletter) {
|
|
||||||
if (mailpoet_tracking_enabled === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newsletter.statistics && newsletter.queue && newsletter.queue.status !== 'scheduled') {
|
|
||||||
const total_sent = ~~(newsletter.queue.count_processed);
|
|
||||||
|
|
||||||
let percentage_clicked = 0;
|
|
||||||
let percentage_opened = 0;
|
|
||||||
let percentage_unsubscribed = 0;
|
|
||||||
|
|
||||||
if (total_sent > 0) {
|
|
||||||
percentage_clicked = Math.round(
|
|
||||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
|
||||||
);
|
|
||||||
percentage_opened = Math.round(
|
|
||||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
|
||||||
);
|
|
||||||
percentage_unsubscribed = Math.round(
|
|
||||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderItem: function(newsletter, actions) {
|
renderItem: function(newsletter, actions) {
|
||||||
const rowClasses = classNames(
|
const rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -291,7 +165,7 @@ const NewsletterListStandard = React.createClass({
|
|||||||
{ actions }
|
{ actions }
|
||||||
</td>
|
</td>
|
||||||
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||||
{ this.renderStatus(newsletter) }
|
{ this.renderQueueStatus(newsletter) }
|
||||||
</td>
|
</td>
|
||||||
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
|
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
|
||||||
{ segments }
|
{ segments }
|
||||||
@ -321,7 +195,8 @@ const NewsletterListStandard = React.createClass({
|
|||||||
location={ this.props.location }
|
location={ this.props.location }
|
||||||
params={ this.props.params }
|
params={ this.props.params }
|
||||||
endpoint="newsletters"
|
endpoint="newsletters"
|
||||||
tab="standard"
|
type="standard"
|
||||||
|
base_url="standard"
|
||||||
onRenderItem={this.renderItem}
|
onRenderItem={this.renderItem}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
bulk_actions={ bulk_actions }
|
bulk_actions={ bulk_actions }
|
||||||
|
@ -343,7 +343,8 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
location={ this.props.location }
|
location={ this.props.location }
|
||||||
params={ this.props.params }
|
params={ this.props.params }
|
||||||
endpoint="newsletters"
|
endpoint="newsletters"
|
||||||
tab="welcome"
|
type="welcome"
|
||||||
|
base_url="welcome"
|
||||||
onRenderItem={ this.renderItem }
|
onRenderItem={ this.renderItem }
|
||||||
columns={ columns }
|
columns={ columns }
|
||||||
bulk_actions={ bulk_actions }
|
bulk_actions={ bulk_actions }
|
||||||
|
@ -14,12 +14,13 @@ import NewsletterTypeNotification from 'newsletters/types/notification/notificat
|
|||||||
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
|
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
|
||||||
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
|
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
|
||||||
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
|
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
|
||||||
|
import NewsletterListNotificationHistory from 'newsletters/listings/notification_history.jsx'
|
||||||
|
|
||||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||||
|
|
||||||
const App = React.createClass({
|
const App = React.createClass({
|
||||||
render() {
|
render() {
|
||||||
return this.props.children
|
return this.props.children;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,18 +32,16 @@ if(container) {
|
|||||||
<Route path="/" component={ App }>
|
<Route path="/" component={ App }>
|
||||||
<IndexRedirect to="standard" />
|
<IndexRedirect to="standard" />
|
||||||
{/* Listings */}
|
{/* Listings */}
|
||||||
<Route name="listing/standard" path="standard" component={ NewsletterListStandard } />
|
<Route path="standard(/)**" params={{ tab: 'standard' }} component={ NewsletterListStandard } />
|
||||||
<Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } />
|
<Route path="welcome(/)**" component={ NewsletterListWelcome } />
|
||||||
<Route name="listing/notification" path="notification" component={ NewsletterListNotification } />
|
<Route path="notification/history/:parent_id(/)**" component={ NewsletterListNotificationHistory } />
|
||||||
<Route path="standard/*" component={ NewsletterListStandard } />
|
<Route path="notification(/)**" component={ NewsletterListNotification } />
|
||||||
<Route path="welcome/*" component={ NewsletterListWelcome } />
|
|
||||||
<Route path="notification/*" component={ NewsletterListNotification } />
|
|
||||||
{/* Newsletter: type selection */}
|
{/* Newsletter: type selection */}
|
||||||
<Route path="new" component={ NewsletterTypes } />
|
<Route path="new" component={ NewsletterTypes } />
|
||||||
{/* New newsletter: types */}
|
{/* New newsletter: types */}
|
||||||
<Route name="new/standard" path="new/standard" component={ NewsletterTypeStandard } />
|
<Route path="new/standard" component={ NewsletterTypeStandard } />
|
||||||
<Route name="new/welcome" path="new/welcome" component={ NewsletterTypeWelcome } />
|
<Route path="new/welcome" component={ NewsletterTypeWelcome } />
|
||||||
<Route name="new/notification" path="new/notification" component={ NewsletterTypeNotification } />
|
<Route path="new/notification" component={ NewsletterTypeNotification } />
|
||||||
{/* Template selection */}
|
{/* Template selection */}
|
||||||
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
||||||
{/* Sending options */}
|
{/* Sending options */}
|
||||||
|
@ -32,7 +32,7 @@ class API {
|
|||||||
$endpoint = self::ENDPOINT_NAMESPACE . ucfirst($this->endpoint);
|
$endpoint = self::ENDPOINT_NAMESPACE . ucfirst($this->endpoint);
|
||||||
if(!$this->api_request) return;
|
if(!$this->api_request) return;
|
||||||
if(!$this->endpoint || !class_exists($endpoint)) {
|
if(!$this->endpoint || !class_exists($endpoint)) {
|
||||||
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API endpoint.'));
|
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API endpoint.'));
|
||||||
}
|
}
|
||||||
$this->callEndpoint(
|
$this->callEndpoint(
|
||||||
$endpoint,
|
$endpoint,
|
||||||
@ -43,7 +43,7 @@ class API {
|
|||||||
|
|
||||||
function callEndpoint($endpoint, $action, $data) {
|
function callEndpoint($endpoint, $action, $data) {
|
||||||
if(!method_exists($endpoint, $action)) {
|
if(!method_exists($endpoint, $action)) {
|
||||||
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API action.'));
|
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API action.'));
|
||||||
}
|
}
|
||||||
call_user_func(
|
call_user_func(
|
||||||
array(
|
array(
|
||||||
@ -83,7 +83,7 @@ class API {
|
|||||||
return add_query_arg($params, home_url());
|
return add_query_arg($params, home_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
function terminateRequest($code, $message) {
|
static function terminateRequest($code, $message) {
|
||||||
status_header($code, $message);
|
status_header($code, $message);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ class Initializer {
|
|||||||
\ORM::configure(Env::$db_source_name);
|
\ORM::configure(Env::$db_source_name);
|
||||||
\ORM::configure('username', Env::$db_username);
|
\ORM::configure('username', Env::$db_username);
|
||||||
\ORM::configure('password', Env::$db_password);
|
\ORM::configure('password', Env::$db_password);
|
||||||
|
\ORM::configure('logging', WP_DEBUG);
|
||||||
\ORM::configure('driver_options', array(
|
\ORM::configure('driver_options', array(
|
||||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||||
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
||||||
|
@ -173,6 +173,7 @@ class Migrator {
|
|||||||
function newsletters() {
|
function newsletters() {
|
||||||
$attributes = array(
|
$attributes = array(
|
||||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||||
|
'parent_id mediumint(9) NULL,',
|
||||||
'subject varchar(250) NOT NULL DEFAULT "",',
|
'subject varchar(250) NOT NULL DEFAULT "",',
|
||||||
'type varchar(20) NOT NULL DEFAULT "standard",',
|
'type varchar(20) NOT NULL DEFAULT "standard",',
|
||||||
'sender_address varchar(150) NOT NULL DEFAULT "",',
|
'sender_address varchar(150) NOT NULL DEFAULT "",',
|
||||||
|
@ -67,29 +67,33 @@ class Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processPostNotificationNewsletter($newsletter, $queue) {
|
function processPostNotificationNewsletter($newsletter, $queue) {
|
||||||
|
// ensure that segments exist
|
||||||
$segments = $newsletter->segments()->findArray();
|
$segments = $newsletter->segments()->findArray();
|
||||||
if(empty($segments)) {
|
if(empty($segments)) {
|
||||||
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$segment_ids = array_map(function($segment) {
|
$segment_ids = array_map(function($segment) {
|
||||||
return $segment['id'];
|
return (int)$segment['id'];
|
||||||
}, $segments);
|
}, $segments);
|
||||||
|
|
||||||
|
// ensure that subscribers are in segments
|
||||||
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
|
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
|
||||||
->findArray();
|
->findArray();
|
||||||
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
|
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
|
||||||
$subscribers = array_unique($subscribers);
|
$subscribers = array_unique($subscribers);
|
||||||
|
|
||||||
if(empty($subscribers)) {
|
if(empty($subscribers)) {
|
||||||
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// schedule new queue if the post notification is not destined for immediate delivery
|
|
||||||
if($newsletter->intervalType !== NewsletterScheduler::INTERVAL_IMMEDIATELY) {
|
// create a duplicate newsletter that acts as a history record
|
||||||
$new_queue = SendingQueue::create();
|
$notification_history = $this->createNotificationHistory($newsletter->id);
|
||||||
$new_queue->newsletter_id = $newsletter->id;
|
if(!$notification_history) return;
|
||||||
$new_queue->status = SendingQueue::STATUS_SCHEDULED;
|
|
||||||
self::deleteQueueOrUpdateNextRunDate($new_queue, $newsletter);
|
// queue newsletter for delivery
|
||||||
}
|
$queue->newsletter_id = $notification_history->id;
|
||||||
$queue->subscribers = serialize(
|
$queue->subscribers = serialize(
|
||||||
array(
|
array(
|
||||||
'to_process' => $subscribers
|
'to_process' => $subscribers
|
||||||
@ -164,7 +168,7 @@ class Scheduler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
|
function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
|
||||||
if($newsletter->intervalType === NewsletterScheduler::INTERVAL_IMMEDIATELY) {
|
if($newsletter->intervalType === NewsletterScheduler::INTERVAL_IMMEDIATELY) {
|
||||||
$queue->delete();
|
$queue->delete();
|
||||||
} else {
|
} else {
|
||||||
@ -173,4 +177,12 @@ class Scheduler {
|
|||||||
$queue->save();
|
$queue->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
function createNotificationHistory($newsletter_id) {
|
||||||
|
$newsletter = Newsletter::findOne($newsletter_id);
|
||||||
|
$notification_history = $newsletter->createNotificationHistory();
|
||||||
|
return ($notification_history->getErrors() === false) ?
|
||||||
|
$notification_history :
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
}
|
@ -178,13 +178,14 @@ class SendingQueue {
|
|||||||
if(!$queue->count_to_process) {
|
if(!$queue->count_to_process) {
|
||||||
$queue->processed_at = current_time('mysql');
|
$queue->processed_at = current_time('mysql');
|
||||||
$queue->status = SendingQueueModel::STATUS_COMPLETED;
|
$queue->status = SendingQueueModel::STATUS_COMPLETED;
|
||||||
// set newsletter status to sent
|
// if it's a standard or post notificaiton newsletter, update its status to sent
|
||||||
$newsletter = NewsletterModel::findOne($queue->newsletter_id);
|
$newsletter = NewsletterModel::findOne($queue->newsletter_id);
|
||||||
// if it's a standard newsletter, update its status
|
if($newsletter->type === NewsletterModel::TYPE_STANDARD ||
|
||||||
if($newsletter->type === NewsletterModel::TYPE_STANDARD) {
|
$newsletter->type === NewsletterModel::TYPE_NOTIFICATION_HISTORY
|
||||||
|
) {
|
||||||
$newsletter->setStatus(NewsletterModel::STATUS_SENT);
|
$newsletter->setStatus(NewsletterModel::STATUS_SENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $queue->save();
|
return $queue->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
||||||
|
|
||||||
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\NewsletterPost;
|
use MailPoet\Models\NewsletterPost;
|
||||||
|
|
||||||
if(!defined('ABSPATH')) exit;
|
if(!defined('ABSPATH')) exit;
|
||||||
@ -18,9 +19,12 @@ class Posts {
|
|||||||
if(!count($matched_posts_ids)) {
|
if(!count($matched_posts_ids)) {
|
||||||
return $newsletter;
|
return $newsletter;
|
||||||
}
|
}
|
||||||
|
$newsletter_id = ($newsletter['type'] === Newsletter::TYPE_NOTIFICATION_HISTORY) ?
|
||||||
|
$newsletter['parent_id'] :
|
||||||
|
$newsletter['id'];
|
||||||
foreach($matched_posts_ids as $post_id) {
|
foreach($matched_posts_ids as $post_id) {
|
||||||
$newletter_post = NewsletterPost::create();
|
$newletter_post = NewsletterPost::create();
|
||||||
$newletter_post->newsletter_id = $newsletter['id'];
|
$newletter_post->newsletter_id = $newsletter_id;
|
||||||
$newletter_post->post_id = $post_id;
|
$newletter_post->post_id = $post_id;
|
||||||
$newletter_post->save();
|
$newletter_post->save();
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ class Handler {
|
|||||||
$this->model = \Model::factory($this->model_class);
|
$this->model = \Model::factory($this->model_class);
|
||||||
|
|
||||||
$this->data = array(
|
$this->data = array(
|
||||||
// tabs
|
// extra parameters
|
||||||
'tab' => (isset($data['tab']) ? $data['tab'] : false),
|
'params' => (isset($data['params']) ? $data['params'] : array()),
|
||||||
// pagination
|
// pagination
|
||||||
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
|
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
|
||||||
'limit' => (isset($data['limit'])
|
'limit' => (isset($data['limit'])
|
||||||
@ -121,7 +121,6 @@ class Handler {
|
|||||||
$this->table_name.'.'.$this->data['sort_by']
|
$this->table_name.'.'.$this->data['sort_by']
|
||||||
)
|
)
|
||||||
->findMany();
|
->findMany();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$this->setFilter();
|
$this->setFilter();
|
||||||
$this->setGroup();
|
$this->setGroup();
|
||||||
|
@ -10,6 +10,7 @@ class Newsletter extends Model {
|
|||||||
const TYPE_STANDARD = 'standard';
|
const TYPE_STANDARD = 'standard';
|
||||||
const TYPE_WELCOME = 'welcome';
|
const TYPE_WELCOME = 'welcome';
|
||||||
const TYPE_NOTIFICATION = 'notification';
|
const TYPE_NOTIFICATION = 'notification';
|
||||||
|
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
|
||||||
|
|
||||||
// standard newsletters
|
// standard newsletters
|
||||||
const STATUS_DRAFT = 'draft';
|
const STATUS_DRAFT = 'draft';
|
||||||
@ -19,7 +20,6 @@ class Newsletter extends Model {
|
|||||||
// automatic newsletters status
|
// automatic newsletters status
|
||||||
const STATUS_ACTIVE = 'active';
|
const STATUS_ACTIVE = 'active';
|
||||||
|
|
||||||
|
|
||||||
function __construct() {
|
function __construct() {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
@ -56,7 +56,6 @@ class Newsletter extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function duplicate($data = array()) {
|
function duplicate($data = array()) {
|
||||||
// get current newsletter's data as an array
|
|
||||||
$newsletter_data = $this->asArray();
|
$newsletter_data = $this->asArray();
|
||||||
|
|
||||||
// remove id so that it creates a new record
|
// remove id so that it creates a new record
|
||||||
@ -109,6 +108,43 @@ class Newsletter extends Model {
|
|||||||
return $duplicate;
|
return $duplicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNotificationHistory() {
|
||||||
|
$newsletter_data = $this->asArray();
|
||||||
|
|
||||||
|
// remove id so that it creates a new record
|
||||||
|
unset($newsletter_data['id']);
|
||||||
|
|
||||||
|
$data = array_merge(
|
||||||
|
$newsletter_data,
|
||||||
|
array(
|
||||||
|
'parent_id' => $this->id,
|
||||||
|
'type' => self::TYPE_NOTIFICATION_HISTORY,
|
||||||
|
'status' => self::STATUS_SENDING
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$notification_history = self::create();
|
||||||
|
$notification_history->hydrate($data);
|
||||||
|
|
||||||
|
$notification_history->save();
|
||||||
|
|
||||||
|
if($notification_history->getErrors() === false) {
|
||||||
|
// create relationships between notification history and segments
|
||||||
|
$segments = $this->segments()->findArray();
|
||||||
|
|
||||||
|
if(!empty($segments)) {
|
||||||
|
foreach($segments as $segment) {
|
||||||
|
$relation = NewsletterSegment::create();
|
||||||
|
$relation->segment_id = $segment['id'];
|
||||||
|
$relation->newsletter_id = $notification_history->id;
|
||||||
|
$relation->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $notification_history;
|
||||||
|
}
|
||||||
|
|
||||||
function asArray() {
|
function asArray() {
|
||||||
$model = parent::asArray();
|
$model = parent::asArray();
|
||||||
|
|
||||||
@ -125,6 +161,14 @@ class Newsletter extends Model {
|
|||||||
return parent::delete();
|
return parent::delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function children() {
|
||||||
|
return $this->has_many(
|
||||||
|
__NAMESPACE__.'\Newsletter',
|
||||||
|
'parent_id',
|
||||||
|
'id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function segments() {
|
function segments() {
|
||||||
return $this->has_many_through(
|
return $this->has_many_through(
|
||||||
__NAMESPACE__.'\Segment',
|
__NAMESPACE__.'\Segment',
|
||||||
@ -139,6 +183,11 @@ class Newsletter extends Model {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withChildrenCount() {
|
||||||
|
$this->children_count = $this->children()->count();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
function options() {
|
function options() {
|
||||||
return $this->has_many_through(
|
return $this->has_many_through(
|
||||||
__NAMESPACE__.'\NewsletterOptionField',
|
__NAMESPACE__.'\NewsletterOptionField',
|
||||||
@ -189,15 +238,11 @@ class Newsletter extends Model {
|
|||||||
} else {
|
} else {
|
||||||
$this->statistics = $statistics->asArray();
|
$this->statistics = $statistics->asArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatistics() {
|
function getStatistics() {
|
||||||
if($this->queue === false) {
|
$statistics_query = SendingQueue::tableAlias('queues')
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return SendingQueue::tableAlias('queues')
|
|
||||||
->selectExpr(
|
->selectExpr(
|
||||||
'COUNT(DISTINCT(clicks.subscriber_id)) as clicked, ' .
|
'COUNT(DISTINCT(clicks.subscriber_id)) as clicked, ' .
|
||||||
'COUNT(DISTINCT(opens.subscriber_id)) as opened, ' .
|
'COUNT(DISTINCT(opens.subscriber_id)) as opened, ' .
|
||||||
@ -217,9 +262,22 @@ class Newsletter extends Model {
|
|||||||
MP_STATISTICS_UNSUBSCRIBES_TABLE,
|
MP_STATISTICS_UNSUBSCRIBES_TABLE,
|
||||||
'queues.id = unsubscribes.queue_id',
|
'queues.id = unsubscribes.queue_id',
|
||||||
'unsubscribes'
|
'unsubscribes'
|
||||||
)
|
);
|
||||||
->where('queues.id', $this->queue['id'])
|
|
||||||
->findOne();
|
if($this->type === self::TYPE_WELCOME) {
|
||||||
|
return $statistics_query
|
||||||
|
->where('queues.newsletter_id', $this->id)
|
||||||
|
->where('queues.status', SendingQueue::STATUS_COMPLETED)
|
||||||
|
->findOne();
|
||||||
|
} else {
|
||||||
|
if($this->queue === false) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return $statistics_query
|
||||||
|
->where('queues.id', $this->queue['id'])
|
||||||
|
->findOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static function search($orm, $search = '') {
|
static function search($orm, $search = '') {
|
||||||
@ -230,6 +288,15 @@ class Newsletter extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static function filters($data = array()) {
|
static function filters($data = array()) {
|
||||||
|
$type = isset($data['params']['type']) ? $data['params']['type'] : null;
|
||||||
|
|
||||||
|
// newsletter types without filters
|
||||||
|
if(in_array($type, array(
|
||||||
|
self::TYPE_NOTIFICATION_HISTORY
|
||||||
|
))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$segments = Segment::orderByAsc('name')->findMany();
|
$segments = Segment::orderByAsc('name')->findMany();
|
||||||
$segment_list = array();
|
$segment_list = array();
|
||||||
$segment_list[] = array(
|
$segment_list[] = array(
|
||||||
@ -239,7 +306,7 @@ class Newsletter extends Model {
|
|||||||
|
|
||||||
foreach($segments as $segment) {
|
foreach($segments as $segment) {
|
||||||
$newsletters = $segment->newsletters()
|
$newsletters = $segment->newsletters()
|
||||||
->filter('filterType', $data['tab'])
|
->filter('filterType', $type)
|
||||||
->filter('groupBy', $data);
|
->filter('groupBy', $data);
|
||||||
|
|
||||||
$newsletters_count = $newsletters->count();
|
$newsletters_count = $newsletters->count();
|
||||||
@ -260,18 +327,32 @@ class Newsletter extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static function filterBy($orm, $data = array()) {
|
static function filterBy($orm, $data = array()) {
|
||||||
$type = isset($data['tab']) ? $data['tab'] : null;
|
// apply filters
|
||||||
|
|
||||||
if(!empty($data['filter'])) {
|
if(!empty($data['filter'])) {
|
||||||
foreach($data['filter'] as $key => $value) {
|
foreach($data['filter'] as $key => $value) {
|
||||||
if($key === 'segment') {
|
if($key === 'segment') {
|
||||||
$segment = Segment::findOne($value);
|
$segment = Segment::findOne($value);
|
||||||
if($segment !== false) {
|
if($segment !== false) {
|
||||||
$orm = $segment->newsletters()->filter('filterType', $type);
|
$orm = $segment->newsletters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter by type
|
||||||
|
$type = isset($data['params']['type']) ? $data['params']['type'] : null;
|
||||||
|
if($type !== null) {
|
||||||
|
$orm->filter('filterType', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter by parent id
|
||||||
|
$parent_id = isset($data['params']['parent_id'])
|
||||||
|
? (int)$data['params']['parent_id']
|
||||||
|
: null;
|
||||||
|
if($parent_id !== null) {
|
||||||
|
$orm->where('parent_id', $parent_id);
|
||||||
|
}
|
||||||
|
|
||||||
return $orm;
|
return $orm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +387,14 @@ class Newsletter extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static function groups($data = array()) {
|
static function groups($data = array()) {
|
||||||
$type = isset($data['tab']) ? $data['tab'] : null;
|
$type = isset($data['params']['type']) ? $data['params']['type'] : null;
|
||||||
|
|
||||||
|
// newsletter types without groups
|
||||||
|
if(in_array($type, array(
|
||||||
|
self::TYPE_NOTIFICATION_HISTORY
|
||||||
|
))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$groups = array(
|
$groups = array(
|
||||||
array(
|
array(
|
||||||
@ -431,7 +519,8 @@ class Newsletter extends Model {
|
|||||||
if(in_array($type, array(
|
if(in_array($type, array(
|
||||||
self::TYPE_STANDARD,
|
self::TYPE_STANDARD,
|
||||||
self::TYPE_WELCOME,
|
self::TYPE_WELCOME,
|
||||||
self::TYPE_NOTIFICATION
|
self::TYPE_NOTIFICATION,
|
||||||
|
self::TYPE_NOTIFICATION_HISTORY
|
||||||
))) {
|
))) {
|
||||||
$orm->where('type', $type);
|
$orm->where('type', $type);
|
||||||
}
|
}
|
||||||
@ -447,7 +536,6 @@ class Newsletter extends Model {
|
|||||||
'updated_at',
|
'updated_at',
|
||||||
'deleted_at'
|
'deleted_at'
|
||||||
))
|
))
|
||||||
->filter('filterType', $data['tab'])
|
|
||||||
->filter('filterBy', $data)
|
->filter('filterBy', $data)
|
||||||
->filter('groupBy', $data)
|
->filter('groupBy', $data)
|
||||||
->filter('search', $data['search']);
|
->filter('search', $data['search']);
|
||||||
@ -523,4 +611,4 @@ class Newsletter extends Model {
|
|||||||
->whereIn('options.value', $segments)
|
->whereIn('options.value', $segments)
|
||||||
->findMany();
|
->findMany();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace MailPoet\Newsletter\Renderer\Blocks;
|
namespace MailPoet\Newsletter\Renderer\Blocks;
|
||||||
|
|
||||||
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Newsletter\Renderer\StylesHelper;
|
use MailPoet\Newsletter\Renderer\StylesHelper;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
@ -10,7 +11,10 @@ class Renderer {
|
|||||||
function __construct(array $newsletter, $posts = false) {
|
function __construct(array $newsletter, $posts = false) {
|
||||||
$this->newsletter = $newsletter;
|
$this->newsletter = $newsletter;
|
||||||
$this->posts = array();
|
$this->posts = array();
|
||||||
$this->ALC = new \MailPoet\Newsletter\AutomatedLatestContent($this->newsletter['id']);
|
$newsletter_id = ($newsletter['type'] === Newsletter::TYPE_NOTIFICATION_HISTORY) ?
|
||||||
|
$newsletter['parent_id'] :
|
||||||
|
$newsletter['id'];
|
||||||
|
$this->ALC = new \MailPoet\Newsletter\AutomatedLatestContent($newsletter_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function render($data, $column_count) {
|
function render($data, $column_count) {
|
||||||
@ -45,12 +49,12 @@ class Renderer {
|
|||||||
|
|
||||||
function processAutomatedLatestContent($args, $column_count) {
|
function processAutomatedLatestContent($args, $column_count) {
|
||||||
$posts_to_exclude = $this->getPosts();
|
$posts_to_exclude = $this->getPosts();
|
||||||
$ALCPosts = $this->ALC->getPosts($args, $posts_to_exclude);
|
$ALC_posts = $this->ALC->getPosts($args, $posts_to_exclude);
|
||||||
foreach($ALCPosts as $post) {
|
foreach($ALC_posts as $post) {
|
||||||
$posts_to_exclude[] = $post->ID;
|
$posts_to_exclude[] = $post->ID;
|
||||||
}
|
}
|
||||||
$transformed_posts = array(
|
$transformed_posts = array(
|
||||||
'blocks' => $this->ALC->transformPosts($args, $ALCPosts)
|
'blocks' => $this->ALC->transformPosts($args, $ALC_posts)
|
||||||
);
|
);
|
||||||
$this->setPosts($posts_to_exclude);
|
$this->setPosts($posts_to_exclude);
|
||||||
$transformed_posts = StylesHelper::applyTextAlignment($transformed_posts);
|
$transformed_posts = StylesHelper::applyTextAlignment($transformed_posts);
|
||||||
|
@ -245,6 +245,7 @@ class Newsletters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listing($data = array()) {
|
function listing($data = array()) {
|
||||||
|
|
||||||
$listing = new Listing\Handler(
|
$listing = new Listing\Handler(
|
||||||
'\MailPoet\Models\Newsletter',
|
'\MailPoet\Models\Newsletter',
|
||||||
$data
|
$data
|
||||||
@ -267,6 +268,11 @@ class Newsletters {
|
|||||||
$newsletter
|
$newsletter
|
||||||
->withOptions()
|
->withOptions()
|
||||||
->withSegments()
|
->withSegments()
|
||||||
|
->withChildrenCount();
|
||||||
|
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION_HISTORY) {
|
||||||
|
$newsletter
|
||||||
|
->withSegments()
|
||||||
|
->withSendingQueue()
|
||||||
->withStatistics();
|
->withStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,10 @@ class SendingQueue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($newsletter->type === Newsletter::TYPE_WELCOME) {
|
if($newsletter->type === Newsletter::TYPE_WELCOME ||
|
||||||
// set welcome email active
|
$newsletter->type === Newsletter::TYPE_NOTIFICATION
|
||||||
|
) {
|
||||||
|
// set newsletter status to active
|
||||||
$result = $newsletter->setStatus(Newsletter::STATUS_ACTIVE);
|
$result = $newsletter->setStatus(Newsletter::STATUS_ACTIVE);
|
||||||
$errors = $result->getErrors();
|
$errors = $result->getErrors();
|
||||||
|
|
||||||
@ -48,20 +50,16 @@ class SendingQueue {
|
|||||||
'errors' => $errors
|
'errors' => $errors
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
$message = ($newsletter->type === Newsletter::TYPE_WELCOME) ?
|
||||||
|
__('Your welcome email has been activated') :
|
||||||
|
__('Your post notification has been activated');
|
||||||
return array(
|
return array(
|
||||||
'result' => true,
|
'result' => true,
|
||||||
'data' => array(
|
'data' => array(
|
||||||
'message' => __('Your welcome email has been activated')
|
'message' => $message
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
|
|
||||||
// Post Notifications
|
|
||||||
$newsletter = Scheduler::processPostNotificationSchedule($newsletter->id);
|
|
||||||
Scheduler::createPostNotificationQueue($newsletter);
|
|
||||||
|
|
||||||
// set post notification active
|
|
||||||
$newsletter->setStatus(Newsletter::STATUS_ACTIVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$queue = SendingQueueModel::whereNull('status')
|
$queue = SendingQueueModel::whereNull('status')
|
||||||
@ -83,18 +81,6 @@ class SendingQueue {
|
|||||||
$queue->newsletter_id = $newsletter->id;
|
$queue->newsletter_id = $newsletter->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
|
|
||||||
$queue->scheduled_at = Scheduler::getNextRunDate($newsletter->schedule);
|
|
||||||
$queue->status = SendingQueueModel::STATUS_SCHEDULED;
|
|
||||||
$queue->save();
|
|
||||||
return array(
|
|
||||||
'result' => true,
|
|
||||||
'data' => array(
|
|
||||||
'message' => __('Your post notification has been activated')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if((bool)$newsletter->isScheduled) {
|
if((bool)$newsletter->isScheduled) {
|
||||||
// set newsletter status
|
// set newsletter status
|
||||||
$newsletter->setStatus(Newsletter::STATUS_SCHEDULED);
|
$newsletter->setStatus(Newsletter::STATUS_SCHEDULED);
|
||||||
@ -188,4 +174,4 @@ class SendingQueue {
|
|||||||
'result' => $result
|
'result' => $result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,7 +19,9 @@ class NewsletterRendererTest extends MailPoetTest {
|
|||||||
),
|
),
|
||||||
'id' => 1,
|
'id' => 1,
|
||||||
'subject' => 'Some subject',
|
'subject' => 'Some subject',
|
||||||
'preheader' => 'Some preheader'
|
'preheader' => 'Some preheader',
|
||||||
|
'type' => 'standard',
|
||||||
|
'status' => 'active'
|
||||||
);
|
);
|
||||||
$this->renderer = new Renderer($this->newsletter);
|
$this->renderer = new Renderer($this->newsletter);
|
||||||
$this->column_renderer = new ColumnRenderer();
|
$this->column_renderer = new ColumnRenderer();
|
||||||
|
@ -229,6 +229,9 @@
|
|||||||
'sendNthWeekDay': __('Send every %$1s %$2s of the month at %$3s'),
|
'sendNthWeekDay': __('Send every %$1s %$2s of the month at %$3s'),
|
||||||
'sendImmediately': __('Send immediately'),
|
'sendImmediately': __('Send immediately'),
|
||||||
'ifNewContentToSegments': __("if there's new content to %$1s."),
|
'ifNewContentToSegments': __("if there's new content to %$1s."),
|
||||||
'sendingToSegmentsNotSpecified': __('You need to select a segment to send to.')
|
'sendingToSegmentsNotSpecified': __('You need to select a segment to send to.'),
|
||||||
|
|
||||||
|
'backToPostNotifications': __('Back to Post notifications'),
|
||||||
|
'sentOn': __('Sent on')
|
||||||
}) %>
|
}) %>
|
||||||
<% endblock %>
|
<% endblock %>
|
||||||
|
Reference in New Issue
Block a user