Compare commits

..

42 Commits

Author SHA1 Message Date
6aadd1fdc4 Bump up release version to 0.0.37 2016-07-22 23:17:45 +03:00
13589a4660 Merge pull request #552 from mailpoet/newsletter_listing
Post notification history listing
2016-07-22 16:20:00 +03:00
5f124659d0 - Fixes PHP static standards error 2016-07-22 08:45:46 -04:00
d3ebc9706c - Updates unit test 2016-07-22 08:45:32 -04:00
e83d01ff28 - Avoids sending duplicate posts 2016-07-21 20:54:32 -04:00
9600e4f220 Merge pull request #569 from mailpoet/manage_subscription_improved
Manage Subscriptions fixes
2016-07-21 17:31:45 +03:00
3e746d1545 fixed API data decoding issue
- added missing features from issue #419
- removed isMailPoetPage() as the logic was flawed
2016-07-21 15:10:25 +02:00
3cc43aa302 Merge pull request #566 from mailpoet/wp_users_fix
Deleting a WP user
2016-07-20 14:32:36 +03:00
c610d87e85 Merge pull request #567 from mailpoet/form_subscription_signups
Number of signups in forms listing
2016-07-20 13:01:59 +03:00
362ee49ce4 Let the statisticsForms model return the total signups instead of the form model
- added unit test for getTotalSignups() method
2016-07-19 17:38:45 +02:00
515515ba9f updated class names of table columns in listing 2016-07-19 17:24:56 +02:00
ed7da1a8fe Deleting a WP user unlinks the subscriber and removes his subscription to the WP Segment 2016-07-19 17:13:20 +02:00
12c036dbef refactored Models/Newsletter::getStatistics method to avoid duplication
- replaced "TO REFACTOR" with more conventional "TODO"
2016-07-19 15:34:14 +02:00
1dd4ade04d added signups to forms listing 2016-07-19 13:44:32 +02:00
0706450f9a Add children() method to Newsletter model to get child newsletters (history in case of post notif)
- added conditional display of "view history" link in Notification listing
- fixed indentation in duplicatePostNotif method according to code sniffer report
2016-07-18 16:47:12 +02:00
b837a153d1 merged post_notification_update 2016-07-18 16:06:04 +02:00
6d22a85fd7 use mixins to render regular newsletters queue status & statistics 2016-07-18 16:01:47 +02:00
3d706414b7 Renamed tab to type
- renamed getExtraParams to getParams
- fixed issue with String.contains by replacing it with indexOf
- removed useless break; statement
2016-07-18 16:01:47 +02:00
ef0cbb3e9f Added "params" to the $data in Listing Handler
- moved "tab" to params
- improved url generation in listing.jsx to allow more flexibility
- added "parent_id" filter in newsletter model to get children of a given newsletter id
2016-07-18 16:01:47 +02:00
f5552847a3 Added parent_id to Newsletters table
- added NOTIFICATION_HISTORY Newsletter's type
- implement basic UI for notification_history
- TODO: implement passing extra parameters in order to handle the :id part
2016-07-18 16:01:47 +02:00
101ef0cff4 Fixed stats for welcome emails
- re-added ORM logging in order to debug queries
- fixed subscription confirmation / unsubscribe due to API refactor
2016-07-18 16:01:47 +02:00
9e70ba5e6e - Reverts back the duplicate method
- Updates post notification creation method with logic to duplicate the
  original newsletter
2016-07-15 13:07:00 -04:00
8f1a7ed3de - Sets notification history status to "sent" upon completion
- Implements #548
2016-07-15 13:06:40 -04:00
6aa976ba1f - Creates a new notification history when processing the queue 2016-07-15 13:06:40 -04:00
db85604f18 - Removes scheduling of post notification during newsletter activation stage 2016-07-15 13:06:40 -04:00
7605fc71ac - Adds a method to create a notification history newsletter 2016-07-15 13:00:47 -04:00
2c98270084 - Adds new column to the newsletter table
- Adds new newsletter notification history type
2016-07-15 13:00:47 -04:00
a5300624c2 - Bump up release version to 0.0.36;
- Fix translation strings in unit tests
2016-07-15 15:59:40 +03:00
2714c7fa9a Merge pull request #563 from mailpoet/copy-edit
Copy edit - July 15 2016
2016-07-15 15:31:50 +03:00
49b65729db update 14 July 2016 2016-07-14 20:58:12 +02:00
e053b62a70 Merge pull request #558 from mailpoet/task_scheduler_option
Adds task scheduler option to settings
2016-07-14 16:47:15 +03:00
88113cf22e - Modifes task scheduler setting to include method type 2016-07-14 09:44:16 -04:00
5bf352e9fc Merge pull request #559 from mailpoet/ajax_update
Removes object keys with null values when doing an ajax request
2016-07-14 13:44:43 +03:00
5a7d5ac3f0 - Removes object keys with null values when doing an ajax request 2016-07-13 21:06:13 -04:00
05848ce7aa - Adds task scheduler option to settings
- Closes #553
2016-07-13 19:49:18 -04:00
b58d996ac7 Merge pull request #551 from mailpoet/newsletter_archive
Newsletter archive: link to browser version
2016-07-12 17:19:27 +02:00
16fa4491a5 Merge pull request #550 from mailpoet/empty_newsletter
Fix notices and warnings in newsletter preview when there's no template
2016-07-12 17:05:09 +02:00
456deede14 Change newsletter archive to link to "View in brower" newsletter
versions
2016-07-12 17:55:36 +03:00
4ef50ca551 Fix notices and warnings in newsletter preview when there's no template 2016-07-12 17:11:13 +03:00
5ff7d98b00 Merge pull request #547 from mailpoet/post_notification_fix
Fixes post notifications not being sent
2016-07-12 11:15:37 +02:00
3018dff1ff Edit July 12 2016 2016-07-12 11:07:36 +02:00
9386fe8328 - Fixes post notifications not being sent 2016-07-11 18:56:01 -04:00
86 changed files with 939 additions and 522 deletions

View File

@ -1,4 +1,4 @@
define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
'use strict'; 'use strict';
MailPoet.Ajax = { MailPoet.Ajax = {
version: 0.5, version: 0.5,
@ -52,6 +52,13 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
var params = this.getParams(); var params = this.getParams();
var jqXHR; var jqXHR;
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function(value) {
return (value !== null)
})
}
// make ajax request depending on method // make ajax request depending on method
if(method === 'get') { if(method === 'get') {
jqXHR = jQuery.get( jqXHR = jQuery.get(

View File

@ -15,6 +15,10 @@ const columns = [
name: 'segments', name: 'segments',
label: MailPoet.I18n.t('segments') label: MailPoet.I18n.t('segments')
}, },
{
name: 'signups',
label: MailPoet.I18n.t('signups')
},
{ {
name: 'created_at', name: 'created_at',
label: MailPoet.I18n.t('createdOn'), label: MailPoet.I18n.t('createdOn'),
@ -146,9 +150,12 @@ const FormList = React.createClass({
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}> <td className="column" data-colname={MailPoet.I18n.t('segments')}>
{ segments } { segments }
</td> </td>
<td className="column" data-colname={MailPoet.I18n.t('signups')}>
{ form.signups }
</td>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}> <td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.format(form.created_at) }</abbr> <abbr>{ MailPoet.Date.format(form.created_at) }</abbr>
</td> </td>

View File

@ -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,

View 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 }
&nbsp;&nbsp;
<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 };

View File

@ -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 }

View 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;

View File

@ -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 }
&nbsp;&nbsp;
<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 }

View File

@ -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 }

View File

@ -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 */}

View File

@ -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(
@ -56,9 +56,16 @@ class API {
static function decodeRequestData($data) { static function decodeRequestData($data) {
$data = base64_decode($data); $data = base64_decode($data);
return (is_serialized($data)) ?
unserialize($data) : if(is_serialized($data)) {
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API data format.')); $data = unserialize($data);
}
if(!is_array($data)) {
$data = array();
}
return $data;
} }
static function encodeRequestData($data) { static function encodeRequestData($data) {
@ -76,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;
} }

View File

@ -10,6 +10,7 @@ class Subscription {
static function confirm($data) { static function confirm($data) {
$subscription = new UserSubscription\Pages('confirm', $data); $subscription = new UserSubscription\Pages('confirm', $data);
$subscription->confirm();
} }
static function manage($data) { static function manage($data) {
@ -18,5 +19,6 @@ class Subscription {
static function unsubscribe($data) { static function unsubscribe($data) {
$subscription = new UserSubscription\Pages('unsubscribe', $data); $subscription = new UserSubscription\Pages('unsubscribe', $data);
$subscription->unsubscribe();
} }
} }

View File

@ -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 =>

View File

@ -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 "",',

View File

@ -69,6 +69,13 @@ class Populator {
private function createDefaultSettings() { private function createDefaultSettings() {
$current_user = wp_get_current_user(); $current_user = wp_get_current_user();
if(!Setting::getValue('task_scheduler')) {
// disable task scheduler (cron) be default
Setting::setValue('task_scheduler', array(
'method' => 'WordPress'
));
}
// default sender info based on current user // default sender info based on current user
$sender = array( $sender = array(
'name' => $current_user->display_name, 'name' => $current_user->display_name,
@ -106,7 +113,7 @@ class Populator {
$default_segment->hydrate(array( $default_segment->hydrate(array(
'name' => __('My First List'), 'name' => __('My First List'),
'description' => 'description' =>
__('The list is automatically created when you install MailPoet') __('This list is automatically created when you install MailPoet')
)); ));
$default_segment->save(); $default_segment->save();
} }

View File

@ -254,7 +254,7 @@ class FranksRoastHouseTemplate {
), ),
array( array(
"type" => "text", "type" => "text",
"text" => __("<h2>New and Improved Hours!</h2>\n<p></p>\n<p>Frank's is now open even later, so you can get your caffeine fix all day long! Here's our new opening hours:</p>\n<p></p>\n<ul>\n<li>Monday - Thursday: 6am - 12am</li>\n<li>Friday - Saturday: 6am - 1:30am</li>\n<li>Sunday: 7:30am - 11pm</li>\n</ul>") "text" => __("<h2>New and Improved Hours!</h2>\n<p></p>\n<p>Frank's is now open even later, so you can get your caffeine fix all day (and night) long! Here's our new opening hours:</p>\n<p></p>\n<ul>\n<li>Monday - Thursday: 6am - 12am</li>\n<li>Friday - Saturday: 6am - 1:30am</li>\n<li>Sunday: 7:30am - 11pm</li>\n</ul>")
) )
) )
) )

View File

@ -13,8 +13,8 @@ class PostNotificationsBlankTemplate {
function get() { function get() {
return array( return array(
'name' => __("Post Notifications Blank Template"), 'name' => __("Blank Post Notifications Template"),
'description' => __("A simple and plain post notifications template for you to create your own email from."), 'description' => __("A simple post notifications template. Customize it and make it your own!"),
'readonly' => 0, 'readonly' => 0,
'thumbnail' => $this->getThumbnail(), 'thumbnail' => $this->getThumbnail(),
'body' => json_encode($this->getBody()), 'body' => json_encode($this->getBody()),
@ -72,7 +72,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "text", "type" => "text",
"text" => __("<p style=\"text-align: right;\">Your&nbsp;posts below are generated using our <strong>Automated Latest Content widget</strong> in the right sidebar.</p>") "text" => __("<p style=\"text-align: right;\">Your&nbsp;posts below are generated using our <strong>Automated Latest Content widget</strong> on the right sidebar.</p>")
) )
) )
), ),

View File

@ -14,7 +14,7 @@ class WelcomeTemplate {
function get() { function get() {
return array( return array(
'name' => __("Welcome Email Example"), 'name' => __("Welcome Email Example"),
'description' => __("A really simple welcome email idea to help you say hello your new subscribers."), 'description' => __("A simple welcome email idea to help welcome your new subscribers."),
'readonly' => 0, 'readonly' => 0,
'thumbnail' => $this->getThumbnail(), 'thumbnail' => $this->getThumbnail(),
'body' => json_encode($this->getBody()), 'body' => json_encode($this->getBody()),
@ -101,7 +101,7 @@ class WelcomeTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "text", "type" => "text",
"text" => __("<h1 style=\"text-align: center;\"><strong>What's in a Welcome Email?&nbsp;</strong></h1>\n<p>How about thanking the person and telling them what they can expect from signing up as a subscriber,&nbsp;for example, how frequently they might receive newsletters from you and what type of content. If you also write a blog, why not share a few of your most recent posts using Automated Latest Content?</p>\n<p>Get help with your welcome email in our blog post: <a href=\"http://www.mailpoet.com/improve-your-signup-welcome-email/\">http://www.mailpoet.com/improve-your-signup-welcome-email/</a></p>\n<p></p>") "text" => __("<h1 style=\"text-align: center;\"><strong>What's should you put in a Welcome Email?&nbsp;</strong></h1>\n<p>We recommend thanking the person (for subscribing!) and telling them what they can expect in the future. &nbsp; For example, how the type of content you'll be sending (and how often you'll be sending it.) If you have a blog, why not share a few of your recent posts using the Automated Latest Content widget?</p>\n<p>Get some more tips on your Welcome Email in our blog post: <a href=\"http://www.mailpoet.com/improve-your-signup-welcome-email/\">http://www.mailpoet.com/improve-your-signup-welcome-email/</a></p>\n<p></p>")
) )
) )
) )

View File

@ -4,6 +4,7 @@ use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber; use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment; use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Subscription; use \MailPoet\Subscription;
use MailPoet\Newsletter\Url as NewsletterUrl;
class Shortcodes { class Shortcodes {
function __construct() { function __construct() {
@ -113,10 +114,12 @@ class Shortcodes {
} }
function renderArchiveSubject($newsletter) { function renderArchiveSubject($newsletter) {
return '<a href="TODO" target="_blank" title="' $preview_url = NewsletterUrl::getViewInBrowserUrl($newsletter);
return '<a href="'.esc_attr($preview_url).'" target="_blank" title="'
.esc_attr(__('Preview in a new tab')).'">' .esc_attr(__('Preview in a new tab')).'">'
.esc_attr($newsletter->subject). .esc_attr($newsletter->subject).
'</a>'; '</a>';
} }
} }

View File

@ -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 = NewsletterScheduler::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;
}
}

View File

@ -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();
} }
} }

View File

@ -43,7 +43,7 @@ class Mailer {
function getMailerConfig() { function getMailerConfig() {
$mta_config = Setting::getValue('mta'); $mta_config = Setting::getValue('mta');
if(!$mta_config) { if(!$mta_config) {
throw new \Exception(__('Mailer is not configured.')); throw new \Exception(__('Mailer is not configured'));
} }
return $mta_config; return $mta_config;
} }
@ -90,7 +90,7 @@ class Mailer {
if($this->mta_log['sent'] === $frequency_limit && if($this->mta_log['sent'] === $frequency_limit &&
$elapsed_time <= $frequency_interval $elapsed_time <= $frequency_interval
) { ) {
throw new \Exception(__('Sending frequency limit has been reached.')); throw new \Exception(__('Sending frequency limit has been reached'));
} }
if($elapsed_time > $frequency_interval) { if($elapsed_time > $frequency_interval) {
$this->mta_log = array( $this->mta_log = array(

View File

@ -57,8 +57,8 @@ class Newsletter {
if($newsletter['type'] === 'notification' && !$newsletter_contains_posts) { if($newsletter['type'] === 'notification' && !$newsletter_contains_posts) {
return false; return false;
} }
// save all posts // extract and save newsletter posts
$newsletter = PostsTask::extractAndSave($newsletter); PostsTask::extractAndSave($newsletter);
return $newsletter; return $newsletter;
} }

View File

@ -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();
} }

View File

@ -34,7 +34,7 @@ abstract class Base {
if(in_array($block['type'], array('radio', 'checkbox'))) { if(in_array($block['type'], array('radio', 'checkbox'))) {
$rules['group'] = 'custom_field_'.$block['id']; $rules['group'] = 'custom_field_'.$block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id']; $rules['errors-container'] = '.mailpoet_error_'.$block['id'];
$rules['required-message'] = __('Please select at least one option.'); $rules['required-message'] = __('Please select at least one option');
} }
$validation = array(); $validation = array();

View File

@ -17,7 +17,7 @@ class Widget extends \WP_Widget {
'mailpoet_form', 'mailpoet_form',
__('MailPoet Form'), __('MailPoet Form'),
array( array(
'description' => __('Add a newsletter subscription form.') 'description' => __('Add a newsletter subscription form')
) )
); );
} }

View File

@ -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();

View File

@ -84,7 +84,7 @@ class Mailer {
); );
break; break;
default: default:
throw new \Exception(__('Mailing method does not exist.')); throw new \Exception(__('Mailing method does not exist'));
} }
return $mailer_instance; return $mailer_instance;
} }
@ -92,7 +92,7 @@ class Mailer {
function getMailer($mailer = false) { function getMailer($mailer = false) {
if(!$mailer) { if(!$mailer) {
$mailer = Setting::getValue('mta'); $mailer = Setting::getValue('mta');
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured.')); if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured'));
} }
$mailer['class'] = 'MailPoet\\Mailer\\Methods\\' . $mailer['method']; $mailer['class'] = 'MailPoet\\Mailer\\Methods\\' . $mailer['method'];
return $mailer; return $mailer;
@ -101,7 +101,7 @@ class Mailer {
function getSender($sender = false) { function getSender($sender = false) {
if(empty($sender)) { if(empty($sender)) {
$sender = Setting::getValue('sender', array()); $sender = Setting::getValue('sender', array());
if(empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured.')); if(empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured'));
} }
return array( return array(
'from_name' => $sender['name'], 'from_name' => $sender['name'],

View File

@ -9,10 +9,10 @@ class CustomField extends Model {
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
$this->addValidations('type', array( $this->addValidations('type', array(
'required' => __('Please specify a type.') 'required' => __('Please specify a type')
)); ));
} }

View File

@ -10,7 +10,7 @@ class Form extends Model {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
} }

View File

@ -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,12 +20,11 @@ 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();
$this->addValidations('type', array( $this->addValidations('type', array(
'required' => __('Please specify a type.') 'required' => __('Please specify a type')
)); ));
} }
@ -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();
} }
} }

View File

@ -9,10 +9,10 @@ class NewsletterOptionField extends Model {
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
$this->addValidations('newsletter_type', array( $this->addValidations('newsletter_type', array(
'required' => __('Please specify a newsletter type.') 'required' => __('Please specify a newsletter type')
)); ));
} }

View File

@ -10,10 +10,10 @@ class NewsletterTemplate extends Model {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
$this->addValidations('body', array( $this->addValidations('body', array(
'required' => __('The template body cannot be empty.') 'required' => __('The template body cannot be empty')
)); ));
} }

View File

@ -10,7 +10,7 @@ class Segment extends Model {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
} }
@ -102,7 +102,7 @@ class Segment extends Model {
$wp_segment->hydrate(array( $wp_segment->hydrate(array(
'name' => __('WordPress Users'), 'name' => __('WordPress Users'),
'description' => 'description' =>
__('This lists containts all of your WordPress users.'), __('This lists containts all of your WordPress users'),
'type' => 'wp_users' 'type' => 'wp_users'
)); ));
$wp_segment->save(); $wp_segment->save();

View File

@ -17,7 +17,7 @@ class Setting extends Model {
parent::__construct(); parent::__construct();
$this->addValidations('name', array( $this->addValidations('name', array(
'required' => __('Please specify a name.') 'required' => __('Please specify a name')
)); ));
} }

View File

@ -6,6 +6,10 @@ if(!defined('ABSPATH')) exit;
class StatisticsForms extends Model { class StatisticsForms extends Model {
public static $_table = MP_STATISTICS_FORMS_TABLE; public static $_table = MP_STATISTICS_FORMS_TABLE;
public static function getTotalSignups($form_id = false) {
return self::where('form_id', $form_id)->count();
}
public static function record($form_id, $subscriber_id) { public static function record($form_id, $subscriber_id) {
if($form_id > 0 && $subscriber_id > 0) { if($form_id > 0 && $subscriber_id > 0) {
// check if we already have a record for today // check if we already have a record for today

View File

@ -18,8 +18,8 @@ class Subscriber extends Model {
parent::__construct(); parent::__construct();
$this->addValidations('email', array( $this->addValidations('email', array(
'required' => __('Please enter your email address.'), 'required' => __('Please enter your email address'),
'isEmail' => __('Your email address is invalid.') 'isEmail' => __('Your email address is invalid!')
)); ));
} }

View File

@ -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);

View File

@ -23,8 +23,19 @@ class Renderer {
function render() { function render() {
$newsletter = $this->newsletter; $newsletter = $this->newsletter;
$rendered_body = $this->renderBody($newsletter['body']['content']); $body = (is_array($newsletter['body']))
$rendered_styles = $this->renderStyles($newsletter['body']['globalStyles']); ? $newsletter['body']
: array();
$content = (array_key_exists('content', $body))
? $body['content']
: array();
$styles = (array_key_exists('globalStyles', $body))
? $body['globalStyles']
: array();
$rendered_body = $this->renderBody($content);
$rendered_styles = $this->renderStyles($styles);
$template = $this->injectContentIntoTemplate($this->template, array( $template = $this->injectContentIntoTemplate($this->template, array(
$newsletter['subject'], $newsletter['subject'],
$rendered_styles, $rendered_styles,
@ -33,6 +44,7 @@ class Renderer {
)); ));
$template = $this->inlineCSSStyles($template); $template = $this->inlineCSSStyles($template);
$template = $this->postProcessTemplate($template); $template = $this->postProcessTemplate($template);
return array( return array(
'html' => $template, 'html' => $template,
'text' => $this->renderTextVersion($template) 'text' => $this->renderTextVersion($template)
@ -40,6 +52,10 @@ class Renderer {
} }
function renderBody($content) { function renderBody($content) {
$blocks = (array_key_exists('blocks', $content))
? $content['blocks']
: array();
$rendered_content = array_map(function($content_block) { $rendered_content = array_map(function($content_block) {
$column_count = count($content_block['blocks']); $column_count = count($content_block['blocks']);
$column_data = $this->blocks_renderer->render( $column_data = $this->blocks_renderer->render(
@ -51,7 +67,7 @@ class Renderer {
$column_count, $column_count,
$column_data $column_data
); );
}, $content['blocks']); }, $blocks);
return implode('', $rendered_content); return implode('', $rendered_content);
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use \MailPoet\Models\Form; use \MailPoet\Models\Form;
use \MailPoet\Models\StatisticsForms;
use \MailPoet\Form\Renderer as FormRenderer; use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Listing; use \MailPoet\Listing;
use \MailPoet\Form\Util; use \MailPoet\Form\Util;
@ -30,6 +31,9 @@ class Forms {
// fetch segments relations for each returned item // fetch segments relations for each returned item
foreach($listing_data['items'] as $key => $form) { foreach($listing_data['items'] as $key => $form) {
$form = $form->asArray(); $form = $form->asArray();
$form['signups'] = StatisticsForms::getTotalSignups($form['id']);
$form['segments'] = ( $form['segments'] = (
!empty($form['settings']['segments']) !empty($form['settings']['segments'])
? $form['settings']['segments'] ? $form['settings']['segments']

View File

@ -165,7 +165,7 @@ class Newsletters {
if(!isset($data['body'])) { if(!isset($data['body'])) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Newsletter data is missing.')) 'errors' => array(__('Newsletter data is missing'))
); );
} }
$newsletter_id = (isset($data['id'])) ? (int)$data['id'] : null; $newsletter_id = (isset($data['id'])) ? (int)$data['id'] : null;
@ -173,7 +173,7 @@ class Newsletters {
if(!$newsletter) { if(!$newsletter) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Newsletter could not be read.')) 'errors' => array(__('Newsletter could not be read'))
); );
} }
$newsletter->body = $data['body']; $newsletter->body = $data['body'];
@ -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();
} }

View File

@ -33,12 +33,14 @@ class SendingQueue {
if($newsletter === false) { if($newsletter === false) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('This newsletter does not exist.')) 'errors' => array(__('This newsletter does not exist'))
); );
} }
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')
@ -71,7 +69,7 @@ class SendingQueue {
if(!empty($queue)) { if(!empty($queue)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('This newsletter is already being sent.')) 'errors' => array(__('This newsletter is already being sent'))
); );
} }
@ -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);
@ -107,7 +93,7 @@ class SendingQueue {
$queue->subscribers = null; $queue->subscribers = null;
$queue->count_total = $queue->count_to_process = 0; $queue->count_total = $queue->count_to_process = 0;
$message = __('The newsletter has been scheduled.'); $message = __('The newsletter has been scheduled');
} else { } else {
$segments = $newsletter->segments()->findArray(); $segments = $newsletter->segments()->findArray();
$segment_ids = array_map(function($segment) { $segment_ids = array_map(function($segment) {
@ -120,7 +106,7 @@ class SendingQueue {
if(!count($subscribers)) { if(!count($subscribers)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('There are no subscribers.')) 'errors' => array(__('There are no subscribers'))
); );
} }
$queue->status = null; $queue->status = null;
@ -188,4 +174,4 @@ class SendingQueue {
'result' => $result 'result' => $result
); );
} }
} }

View File

@ -69,7 +69,7 @@ class Subscribers {
$form = Form::findOne($data['form_id']); $form = Form::findOne($data['form_id']);
unset($data['form_id']); unset($data['form_id']);
if($form === false || !$form->id()) { if($form === false || !$form->id()) {
$errors[] = __('This form does not exist.'); $errors[] = __('This form does not exist');
} }
$segment_ids = (!empty($data['segments']) $segment_ids = (!empty($data['segments'])

View File

@ -8,9 +8,9 @@ use MailPoet\Newsletter\Scheduler\Scheduler;
class WP { class WP {
static function synchronizeUser($wp_user_id, $old_wp_user_data = false) { static function synchronizeUser($wp_user_id, $old_wp_user_data = false) {
$wp_user = \get_userdata($wp_user_id); $wp_user = \get_userdata($wp_user_id);
$wp_users_segment = Segment::getWPSegment(); $wp_segment = Segment::getWPSegment();
if($wp_user === false or $wp_users_segment === false) return; if($wp_user === false or $wp_segment === false) return;
$subscriber = Subscriber::where('wp_user_id', $wp_user->ID) $subscriber = Subscriber::where('wp_user_id', $wp_user->ID)
->findOne(); ->findOne();
@ -20,8 +20,14 @@ class WP {
case 'delete_user': case 'delete_user':
case 'deleted_user': case 'deleted_user':
case 'remove_user_from_blog': case 'remove_user_from_blog':
if($subscriber !== false && $subscriber->id()) { if($subscriber !== false) {
$subscriber->delete(); // unlink subscriber to wp user
$subscriber->setExpr('wp_user_id', 'NULL')->save();
// delete subscription to wp segment
SubscriberSegment::where('subscriber_id', $subscriber->id)
->where('segment_id', $wp_segment->id)
->deleteMany();
} }
break; break;
case 'profile_update': case 'profile_update':
@ -55,7 +61,7 @@ class WP {
// add subscriber to the WP Users segment // add subscriber to the WP Users segment
SubscriberSegment::subscribeToSegments( SubscriberSegment::subscribeToSegments(
$subscriber, $subscriber,
array($wp_users_segment->id) array($wp_segment->id)
); );
// welcome email // welcome email
@ -73,7 +79,7 @@ class WP {
static function synchronizeUsers() { static function synchronizeUsers() {
// get wordpress users list // get wordpress users list
$wp_users_segment = Segment::getWPSegment(); $wp_segment = Segment::getWPSegment();
// fetch all wp users id // fetch all wp users id
$wp_users = \get_users(array( $wp_users = \get_users(array(

View File

@ -46,10 +46,10 @@ class Export {
function process() { function process() {
try { try {
if(is_writable($this->export_path) === false) { if(is_writable($this->export_path) === false) {
throw new \Exception(__("Couldn't save export file on the server.")); throw new \Exception(__("Couldn't save export file on the server"));
} }
if(!extension_loaded('zip')) { if(!extension_loaded('zip')) {
throw new \Exception(__('Export requires a ZIP extension to be installed on the host.')); throw new \Exception(__('Export requires a ZIP extension to be installed on the host'));
} }
$processed_subscribers = call_user_func( $processed_subscribers = call_user_func(
array( array(

View File

@ -135,13 +135,13 @@ class MailChimp {
$errorMessage = __('Invalid API Key.'); $errorMessage = __('Invalid API Key.');
break; break;
case 'connection': case 'connection':
$errorMessage = __('Could not connect to your MailChimp account.'); $errorMessage = __('Could not connect to your MailChimp account');
break; break;
case 'headers': case 'headers':
$errorMessage = __('The selected lists do not have matching columns (headers).'); $errorMessage = __('The selected lists do not have matching columns (headers)');
break; break;
case 'size': case 'size':
$errorMessage = __('The information received from MailChimp is too large for processing. Please limit the number of lists.'); $errorMessage = __('The information received from MailChimp is too large for processing. Please limit the number of lists!');
break; break;
case 'subscribers': case 'subscribers':
$errorMessage = __('Did not find any active subscribers.'); $errorMessage = __('Did not find any active subscribers.');

View File

@ -20,7 +20,7 @@ class Comment {
static function getSubscriptionField() { static function getSubscriptionField() {
$label = Setting::getValue( $label = Setting::getValue(
'subscribe.on_comment.label', 'subscribe.on_comment.label',
__('Yes, please add me to your mailing list.') __('Yes, please add me to your mailing list')
); );
return '<p class="comment-form-mailpoet"> return '<p class="comment-form-mailpoet">

View File

@ -20,7 +20,7 @@ class Pages {
private $data; private $data;
private $subscriber; private $subscriber;
function __construct($action, $data) { function __construct($action, $data = array()) {
$this->action = $action; $this->action = $action;
$this->data = $data; $this->data = $data;
$this->subscriber = $this->getSubscriber(); $this->subscriber = $this->getSubscriber();
@ -59,10 +59,8 @@ class Pages {
function confirm() { function confirm() {
if($this->subscriber !== false) { if($this->subscriber !== false) {
if($this->subscriber->status !== Subscriber::STATUS_SUBSCRIBED) { $this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$this->subscriber->status = Subscriber::STATUS_SUBSCRIBED; $this->subscriber->save();
$this->subscriber->save();
}
} }
} }
@ -78,14 +76,19 @@ class Pages {
function setPageTitle($page_title = '') { function setPageTitle($page_title = '') {
global $post; global $post;
if($post->post_title !== __('MailPoet Page')) return $page_title; if($this->isPreview() === false && $this->subscriber === false) {
return __("Hmmm... we don't have a record of you");
}
if( if(
($this->isMailPoetPage($post->ID) === false) ($post->post_title !== __('MailPoet Page'))
|| ||
($page_title !== single_post_title('', false)) ($page_title !== single_post_title('', false))
) { ) {
// when it's a custom page, just return the original page title
return $page_title; return $page_title;
} else { } else {
// when it's our own page, generate page title based on requested action
switch($this->action) { switch($this->action) {
case 'confirm': case 'confirm':
return $this->getConfirmTitle(); return $this->getConfirmTitle();
@ -97,36 +100,31 @@ class Pages {
return $this->getUnsubscribeTitle(); return $this->getUnsubscribeTitle();
} }
} }
return $page_title;
} }
function setPageContent($page_content = '[mailpoet_page]') { function setPageContent($page_content = '[mailpoet_page]') {
global $post; global $post;
if( // if we're not in preview mode and the subscriber does not exist
($this->isPreview() === false) if($this->isPreview() === false && $this->subscriber === false) {
&& return __("Your email address doesn't appear in our lists anymore. Sign up again or contact us if this appears to be a mistake.");
($this->isMailPoetPage($post->ID) === false)
) {
return $page_content;
}
$content = '';
switch($this->action) {
case 'confirm':
$content = $this->getConfirmContent();
break;
case 'manage':
$content = $this->getManageContent();
break;
case 'unsubscribe':
$content = $this->getUnsubscribeContent();
break;
} }
if(strpos($page_content, '[mailpoet_page]') !== false) { if(strpos($page_content, '[mailpoet_page]') !== false) {
return str_replace('[mailpoet_page]', $content, $page_content); $content = '';
switch($this->action) {
case 'confirm':
$content = $this->getConfirmContent();
break;
case 'manage':
$content = $this->getManageContent();
break;
case 'unsubscribe':
$content = $this->getUnsubscribeContent();
break;
}
return str_replace('[mailpoet_page]', trim($content), $page_content);
} else { } else {
return $page_content; return $page_content;
} }
@ -150,32 +148,22 @@ class Pages {
return $meta; return $meta;
} }
function isMailPoetPage($page_id = null) {
$mailpoet_page_ids = array_unique(array_values(
Setting::getValue('subscription.pages', array())
));
return (in_array($page_id, $mailpoet_page_ids));
}
private function getConfirmTitle() { private function getConfirmTitle() {
if($this->isPreview()) { if($this->isPreview()) {
$title = sprintf( $title = sprintf(
__("You've subscribed to: %s"), __("You have subscribed to: %s"),
'demo 1, demo 2' 'demo 1, demo 2'
); );
} else if($this->subscriber === false) {
$title = __('Your confirmation link expired, please subscribe again.');
} else { } else {
$segment_names = array_map(function($segment) { $segment_names = array_map(function($segment) {
return $segment->name; return $segment->name;
}, $this->subscriber->segments()->findMany()); }, $this->subscriber->segments()->findMany());
if(empty($segment_names)) { if(empty($segment_names)) {
$title = __("You're now subscribed!"); $title = __("You are now subscribed!");
} else { } else {
$title = sprintf( $title = sprintf(
__("You've subscribed to: %s"), __("You have subscribed to: %s"),
join(', ', $segment_names) join(', ', $segment_names)
); );
} }

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer; use \MailPoet\Config\Initializer;
/* /*
* Plugin Name: MailPoet * Plugin Name: MailPoet
* Version: 0.0.35 * Version: 0.0.37
* Plugin URI: http://www.mailpoet.com * Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters. * Description: MailPoet Newsletters.
* Author: MailPoet * Author: MailPoet
@ -22,7 +22,7 @@ use \MailPoet\Config\Initializer;
require 'vendor/autoload.php'; require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.35'); define('MAILPOET_VERSION', '0.0.37');
$initializer = new Initializer(array( $initializer = new Initializer(array(
'file' => __FILE__, 'file' => __FILE__,

View File

@ -73,7 +73,7 @@ class MailerTest extends MailPoetTest {
$mailer = new Mailer(); $mailer = new Mailer();
$this->fail('Mailer did not throw an exception'); $this->fail('Mailer did not throw an exception');
} catch(Exception $e) { } catch(Exception $e) {
expect($e->getMessage())->equals('Mailer is not configured.'); expect($e->getMessage())->equals('Mailer is not configured');
} }
} }
@ -82,7 +82,7 @@ class MailerTest extends MailPoetTest {
$mailer = new Mailer($mailer = $this->mailer); $mailer = new Mailer($mailer = $this->mailer);
$this->fail('Mailer did not throw an exception'); $this->fail('Mailer did not throw an exception');
} catch(Exception $e) { } catch(Exception $e) {
expect($e->getMessage())->equals('Sender name and email are not configured.'); expect($e->getMessage())->equals('Sender name and email are not configured');
} }
} }
@ -108,7 +108,7 @@ class MailerTest extends MailPoetTest {
$mailer = new Mailer(array('method' => 'Unknown'), $this->sender); $mailer = new Mailer(array('method' => 'Unknown'), $this->sender);
$this->fail('Mailer did not throw an exception'); $this->fail('Mailer did not throw an exception');
} catch(Exception $e) { } catch(Exception $e) {
expect($e->getMessage())->equals('Mailing method does not exist.'); expect($e->getMessage())->equals('Mailing method does not exist');
} }
} }
@ -155,4 +155,4 @@ class MailerTest extends MailPoetTest {
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to); $mailer = new Mailer($this->mailer, $this->sender, $this->reply_to);
expect($mailer->send($this->newsletter, $this->subscriber))->true(); expect($mailer->send($this->newsletter, $this->subscriber))->true();
} }
} }

View File

@ -72,8 +72,8 @@ class CustomFieldTest extends MailPoetTest {
$errors = $result->getErrors(); $errors = $result->getErrors();
expect(is_array($errors))->true(); expect(is_array($errors))->true();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
expect($errors[1])->equals('Please specify a type.'); expect($errors[1])->equals('Please specify a type');
} }
function testItHasACreatedAtOnCreation() { function testItHasACreatedAtOnCreation() {

View File

@ -19,7 +19,7 @@ class FormTest extends MailPoetTest {
$errors = $result->getErrors(); $errors = $result->getErrors();
expect(is_array($errors))->true(); expect(is_array($errors))->true();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
} }
function testItCanBeGrouped() { function testItCanBeGrouped() {

View File

@ -53,8 +53,8 @@ class NewsletterOptionFieldTest extends MailPoetTest {
$errors = $result->getErrors(); $errors = $result->getErrors();
expect(is_array($errors))->true(); expect(is_array($errors))->true();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
expect($errors[1])->equals('Please specify a newsletter type.'); expect($errors[1])->equals('Please specify a newsletter type');
} }
function testItHasACreatedAtOnCreation() { function testItHasACreatedAtOnCreation() {

View File

@ -26,8 +26,8 @@ class NewsletterTemplateTest extends MailPoetTest {
$errors = $result->getErrors(); $errors = $result->getErrors();
expect(is_array($errors))->true(); expect(is_array($errors))->true();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
expect($errors[1])->equals('The template body cannot be empty.'); expect($errors[1])->equals('The template body cannot be empty');
} }
function testItHasName() { function testItHasName() {

View File

@ -72,7 +72,7 @@ class SegmentTest extends MailPoetTest {
$errors = $result->getErrors(); $errors = $result->getErrors();
expect(is_array($errors))->true(); expect(is_array($errors))->true();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
} }
function testItHasACreatedAtOnCreation() { function testItHasACreatedAtOnCreation() {

View File

@ -16,7 +16,7 @@ class SettingTest extends MailPoetTest {
$errors = $invalid_setting->getErrors(); $errors = $invalid_setting->getErrors();
expect($errors)->notEmpty(); expect($errors)->notEmpty();
expect($errors[0])->equals('Please specify a name.'); expect($errors[0])->equals('Please specify a name');
} }
function testItHasDefaultSettings() { function testItHasDefaultSettings() {

View File

@ -36,6 +36,20 @@ class StatisticsFormsTest extends MailPoetTest {
expect($record)->false(); expect($record)->false();
} }
function testItCanReturnTheTotalSignupsOfAForm() {
// simulate 2 signups for form #1
StatisticsForms::record($form_id = 1, $subscriber_id = 2);
StatisticsForms::record($form_id = 1, $subscriber_id = 1);
// simulate 1 signup for form #2
StatisticsForms::record($form_id = 2, $subscriber_id = 2);
$form_1_signups = StatisticsForms::getTotalSignups($form_id = 1);
expect($form_1_signups)->equals(2);
$form_2_signups = StatisticsForms::getTotalSignups($form_id = 2);
expect($form_2_signups)->equals(1);
}
function _after() { function _after() {
StatisticsForms::deleteMany(); StatisticsForms::deleteMany();
} }

View File

@ -56,7 +56,7 @@ class SubscriberTest extends MailPoetTest {
)); ));
$subscriber->save(); $subscriber->save();
$errors = $subscriber->getErrors(); $errors = $subscriber->getErrors();
expect($errors)->contains("Your email address is invalid."); expect($errors)->contains("Your email address is invalid!");
// pdo error // pdo error
$subscriber = Subscriber::create(); $subscriber = Subscriber::create();
@ -443,4 +443,4 @@ class SubscriberTest extends MailPoetTest {
ORM::raw_execute('TRUNCATE ' . CustomField::$_table); ORM::raw_execute('TRUNCATE ' . CustomField::$_table);
ORM::raw_execute('TRUNCATE ' . SubscriberCustomField::$_table); ORM::raw_execute('TRUNCATE ' . SubscriberCustomField::$_table);
} }
} }

View File

@ -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();

View File

@ -95,18 +95,18 @@ class CustomFieldsTest extends MailPoetTest {
// missing type // missing type
$response = $router->save(array('name' => 'New custom field')); $response = $router->save(array('name' => 'New custom field'));
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a type.'); expect($response['errors'][0])->equals('Please specify a type');
// missing name // missing name
$response = $router->save(array('type' => 'text')); $response = $router->save(array('type' => 'text'));
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a name.'); expect($response['errors'][0])->equals('Please specify a name');
// missing data // missing data
$response = $router->save(); $response = $router->save();
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a name.'); expect($response['errors'][0])->equals('Please specify a name');
expect($response['errors'][1])->equals('Please specify a type.'); expect($response['errors'][1])->equals('Please specify a type');
} }
function testItCanGetACustomField() { function testItCanGetACustomField() {

View File

@ -59,7 +59,7 @@ class FormsTest extends MailPoetTest {
$router = new Forms(); $router = new Forms();
$response = $router->save(/* missing data */); $response = $router->save(/* missing data */);
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a name.'); expect($response['errors'][0])->equals('Please specify a name');
$response = $router->save($form_data); $response = $router->save($form_data);
expect($response['result'])->true(); expect($response['result'])->true();

View File

@ -48,7 +48,7 @@ class NewslettersTest extends MailPoetTest {
$response = $router->save($invalid_data); $response = $router->save($invalid_data);
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a type.'); expect($response['errors'][0])->equals('Please specify a type');
} }
function testItCanSaveAnExistingNewsletter() { function testItCanSaveAnExistingNewsletter() {
@ -123,7 +123,7 @@ class NewslettersTest extends MailPoetTest {
$response = $router->create(); $response = $router->create();
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a type.'); expect($response['errors'][0])->equals('Please specify a type');
} }
function testItCanGetListingData() { function testItCanGetListingData() {

View File

@ -44,7 +44,7 @@ class SegmentsTest extends MailPoetTest {
$router = new Segments(); $router = new Segments();
$response = $router->save(/* missing data */); $response = $router->save(/* missing data */);
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please specify a name.'); expect($response['errors'][0])->equals('Please specify a name');
$response = $router->save($segment_data); $response = $router->save($segment_data);
expect($response['result'])->true(); expect($response['result'])->true();

View File

@ -67,7 +67,7 @@ class SubscribersTest extends MailPoetTest {
$response = $router->save(/* missing data */); $response = $router->save(/* missing data */);
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Please enter your email address.'); expect($response['errors'][0])->equals('Please enter your email address');
$invalid_data = array( $invalid_data = array(
'email' => 'john.doe@invalid' 'email' => 'john.doe@invalid'
@ -75,7 +75,7 @@ class SubscribersTest extends MailPoetTest {
$response = $router->save($invalid_data); $response = $router->save($invalid_data);
expect($response['result'])->false(); expect($response['result'])->false();
expect($response['errors'][0])->equals('Your email address is invalid.'); expect($response['errors'][0])->equals('Your email address is invalid!');
} }
function testItCanSaveAnExistingSubscriber() { function testItCanSaveAnExistingSubscriber() {

View File

@ -225,7 +225,7 @@ class ExportTest extends MailPoetTest {
$this->export->export_path = '/fake_folder'; $this->export->export_path = '/fake_folder';
$result = $this->export->process(); $result = $this->export->process();
expect($result['errors'][0]) expect($result['errors'][0])
->equals("Couldn't save export file on the server."); ->equals("Couldn't save export file on the server");
} }
function testItCanProcess() { function testItCanProcess() {

View File

@ -8,7 +8,7 @@
<h2> <h2>
<input <input
type="text" type="text"
placeholder="<%= __('Click here to change the name!') %>" placeholder="<%= __('Click here to change the name') %>"
id="mailpoet_form_name" id="mailpoet_form_name"
value="<%= form.name %>" value="<%= form.name %>"
/> />
@ -52,7 +52,7 @@
<select <select
id="mailpoet_form_segments" id="mailpoet_form_segments"
name="segments" name="segments"
data-placeholder="<%= __('Please choose a list') %>" data-placeholder="<%= __('Please select a list') %>"
multiple multiple
data-parsley-required-message="<%= __('Please select a list.') %>" data-parsley-required-message="<%= __('Please select a list.') %>"
required required
@ -598,7 +598,7 @@
var name = $(this).siblings('.mailpoet_form_field').attr('wysija_name'); var name = $(this).siblings('.mailpoet_form_field').attr('wysija_name');
if(window.confirm( if(window.confirm(
"<%= __('Delete this field for all your subscribers?') %>" "<%= __('This field will be deleted for all your subscribers. Are you sure?') %>"
)) { )) {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'customFields', endpoint: 'customFields',

View File

@ -1,5 +1,5 @@
{{#if params.label}}<p>{{ params.label }}</p>{{/if}} {{#if params.label}}<p>{{ params.label }}</p>{{/if}}
{{#unless params.values}}<p class="mailpoet_error"><%= __('Please select at least 1 list') %></p>{{/unless}} {{#unless params.values}}<p class="mailpoet_error"><%= __('Please select at least one list') %></p>{{/unless}}
{{#each params.values}} {{#each params.values}}
<p> <p>
<input class="mailpoet_checkbox" type="checkbox" {{#if is_checked}}checked="checked"{{/if}} disabled="disabled" />{{ name }} <input class="mailpoet_checkbox" type="checkbox" {{#if is_checked}}checked="checked"{{/if}} disabled="disabled" />{{ name }}

View File

@ -1,7 +1,7 @@
<ul id="mailpoet_segment_selection" class="clearfix"></ul> <ul id="mailpoet_segment_selection" class="clearfix"></ul>
<div id="mailpoet_segment_available_container"> <div id="mailpoet_segment_available_container">
<h3><%= __('Select the segment you want to add:') %></h3> <h3><%= __('Select the segment that you want to add:') %></h3>
<select class="mailpoet_segment_available"></select> <select class="mailpoet_segment_available"></select>

View File

@ -16,12 +16,12 @@
'pageTitle': __('Forms'), 'pageTitle': __('Forms'),
'searchLabel': __('Search'), 'searchLabel': __('Search'),
'loadingItems': __('Loading forms...'), 'loadingItems': __('Loading forms...'),
'noItemsFound': __('No forms found.'), 'noItemsFound': __('No forms found'),
'selectAllLabel': __('All forms on this page are selected.'), 'selectAllLabel': __('All forms on this page are selected'),
'selectedAllLabel': __('All %d forms are selected.'), 'selectedAllLabel': __('All %d forms are selected'),
'selectAllLink': __('Select all forms on all pages.'), 'selectAllLink': __('Select all forms on all pages'),
'clearSelection': __('Clear selection.'), 'clearSelection': __('Clear selection'),
'permanentlyDeleted': __('%d forms permanently deleted.'), 'permanentlyDeleted': __('%d forms permanently deleted'),
'selectBulkAction': __('Select bulk action'), 'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'), 'bulkActions': __('Bulk Actions'),
'apply': __('Apply'), 'apply': __('Apply'),
@ -41,16 +41,17 @@
'formName': __('Name'), 'formName': __('Name'),
'segments': __('Lists'), 'segments': __('Lists'),
'signups': __('Signups'),
'createdOn': __('Created on'), 'createdOn': __('Created on'),
'oneFormTrashed': __('1 form was moved to the trash.'), 'oneFormTrashed': __('1 form was moved to the trash'),
'multipleFormsTrashed': __('%$1d forms were moved to the trash.'), 'multipleFormsTrashed': __('%$1d forms were moved to the trash'),
'oneFormDeleted': __('1 form was permanently deleted.'), 'oneFormDeleted': __('1 form was permanently deleted'),
'multipleFormsDeleted': __('%$1d forms were permanently deleted.'), 'multipleFormsDeleted': __('%$1d forms were permanently deleted'),
'oneFormRestored': __('1 form has been restored from the trash.'), 'oneFormRestored': __('1 form has been restored from the trash'),
'multipleFormsRestored': __('%$1d forms have been restored from the trash.'), 'multipleFormsRestored': __('%$1d forms have been restored from the trash'),
'edit': __('Edit'), 'edit': __('Edit'),
'duplicate': __('Duplicate'), 'duplicate': __('Duplicate'),
'formDuplicated': __('Form "%$1s" has been duplicated.'), 'formDuplicated': __('Form "%$1s" has been duplicated'),
'trash': __('Trash'), 'trash': __('Trash'),
'new': __('New'), 'new': __('New'),
}) %> }) %>

View File

@ -318,15 +318,15 @@
'failedToFetchAvailablePosts': __('Failed to fetch available posts'), 'failedToFetchAvailablePosts': __('Failed to fetch available posts'),
'failedToFetchRenderedPosts': __('Failed to fetch rendered posts'), 'failedToFetchRenderedPosts': __('Failed to fetch rendered posts'),
'shortcodesWindowTitle': __('Select a shortcode'), 'shortcodesWindowTitle': __('Select a shortcode'),
'unsubscribeLinkMissing': __('All newsletters must include an "unsubscribe" link. Add a footer widget to your newsletter to continue.'), 'unsubscribeLinkMissing': __('All newsletters must include an "unsubscribe" link. Add a footer widget to your newsletter to continue'),
'newsletterPreviewEmailMissing': __('Enter an email address to send the preview newsletter to.'), 'newsletterPreviewEmailMissing': __('Enter an email address to send the preview newsletter to'),
'newsletterPreviewFailed': __('Preview failed. Pleae check console log.'), 'newsletterPreviewFailed': __('Preview failed. Pleae check console log'),
'newsletterPreviewSent': __('Newsletter preview email has been successfully sent!'), 'newsletterPreviewSent': __('Newsletter preview email has been successfully sent!'),
'newsletterPreviewFailedToSend': __('Attempt to send a newsletter preview email failed. Please verify that your sending method is configured correctly and try again.'), 'newsletterPreviewFailedToSend': __('Attempt to send a newsletter preview email failed. Please verify that your sending method is configured correctly and try again'),
'templateNameMissing': __('Please add a template name'), 'templateNameMissing': __('Please add a template name'),
'templateDescriptionMissing': __('Please add a template description'), 'templateDescriptionMissing': __('Please add a template description'),
'templateSaved': __('Template has been saved.'), 'templateSaved': __('Template has been saved.'),
'templateSaveFailed': __('Template has not been saved, please try again.'), 'templateSaveFailed': __('Template has not been saved, please try again'),
'categoriesAndTags': __('Categories & tags'), 'categoriesAndTags': __('Categories & tags'),
'noPostsToDisplay': __('There is no content to display'), 'noPostsToDisplay': __('There is no content to display'),
}) %> }) %>
@ -1152,7 +1152,7 @@
}, },
header: { header: {
text: '<%= __('Display problems?') %>&nbsp;' + text: '<%= __('Display problems?') %>&nbsp;' +
'<a href="[link:newsletter_view_in_browser_url]"><%= __('Open this email in your web browser.') %></a>', '<a href="[link:newsletter_view_in_browser_url]"><%= __('Open this email in your web browser') %></a>',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent',

View File

@ -10,6 +10,6 @@
<input type="text" <input type="text"
class="mailpoet_input mailpoet_input_preheader" class="mailpoet_input mailpoet_input_preheader"
value="{{ model.preheader }}" value="{{ model.preheader }}"
placeholder="<%= __('Write your preheader here for Outlook users...') %>" placeholder="<%= __('Write your preheader here (for subscribers that use Outlook)') %>"
/> />
</div> </div>

View File

@ -28,12 +28,12 @@
'searchLabel': __('Search'), 'searchLabel': __('Search'),
'loadingItems': __('Loading newsletters...'), 'loadingItems': __('Loading newsletters...'),
'noItemsFound': __('No newsletters found.'), 'noItemsFound': __('No newsletters found'),
'selectAllLabel': __('All newsletters on this page are selected.'), 'selectAllLabel': __('All newsletters on this page are selected'),
'selectedAllLabel': __('All %d newsletters are selected.'), 'selectedAllLabel': __('All %d newsletters are selected'),
'selectAllLink': __('Select all newsletters on all pages.'), 'selectAllLink': __('Select all newsletters on all pages'),
'clearSelection': __('Clear selection.'), 'clearSelection': __('Clear selection'),
'permanentlyDeleted': __('%d newsletters were permanently deleted.'), 'permanentlyDeleted': __('%d newsletters were permanently deleted'),
'selectBulkAction': __('Select bulk action'), 'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'), 'bulkActions': __('Bulk Actions'),
'apply': __('Apply'), 'apply': __('Apply'),
@ -66,23 +66,23 @@
'viewHistory': __('View history'), 'viewHistory': __('View history'),
'createdOn': __('Created on'), 'createdOn': __('Created on'),
'lastModifiedOn': __('Last modified on'), 'lastModifiedOn': __('Last modified on'),
'oneNewsletterTrashed': __('1 newsletter was moved to the trash.'), 'oneNewsletterTrashed': __('1 newsletter was moved to the trash'),
'multipleNewslettersTrashed': __('%$1d newsletters were moved to the trash.'), 'multipleNewslettersTrashed': __('%$1d newsletters were moved to the trash'),
'oneNewsletterDeleted': __('1 newsletter was permanently deleted.'), 'oneNewsletterDeleted': __('1 newsletter was permanently deleted'),
'multipleNewslettersDeleted': __('%$1d newsletters were permanently deleted.'), 'multipleNewslettersDeleted': __('%$1d newsletters were permanently deleted'),
'oneNewsletterRestored': __('1 newsletter has been recovered from the trash.'), 'oneNewsletterRestored': __('1 newsletter has been recovered from the trash'),
'multipleNewslettersRestored': __('%$1d newsletters have been recovered from the trash.'), 'multipleNewslettersRestored': __('%$1d newsletters have been recovered from the trash'),
'trash': __('Trash'), 'trash': __('Trash'),
'edit': __('Edit'), 'edit': __('Edit'),
'duplicate': __('Duplicate'), 'duplicate': __('Duplicate'),
'newsletterDuplicated': __('Newsletter "%$1s" has been duplicated.'), 'newsletterDuplicated': __('Newsletter "%$1s" has been duplicated'),
'notSentYet': __('Not sent yet.'), 'notSentYet': __('Not sent yet'),
'scheduledFor': __('Scheduled for'), 'scheduledFor': __('Scheduled for'),
'scheduleIt': __('Schedule it'), 'scheduleIt': __('Schedule it'),
'active': __('Active'), 'active': __('Active'),
'inactive': __('Not Active'), 'inactive': __('Not Active'),
'newsletterQueueCompleted': __('Sent to %$1d of %$2d.'), 'newsletterQueueCompleted': __('Sent to %$1d of %$2d'),
'sentToXSubscribers': __('Sent to %$1d subscribers.'), 'sentToXSubscribers': __('Sent to %$1d subscribers'),
'resume': __('Resume'), 'resume': __('Resume'),
'pause': __('Pause'), 'pause': __('Pause'),
'new': __('New'), 'new': __('New'),
@ -92,7 +92,7 @@
'selectJsonFileToUpload': __('Select a .json file to upload'), 'selectJsonFileToUpload': __('Select a .json file to upload'),
'upload': __('Upload'), 'upload': __('Upload'),
'mailpoetGuideTemplateTitle': __("MailPoet's Guide"), 'mailpoetGuideTemplateTitle': __("MailPoet's Guide"),
'mailpoetGuideTemplateDescription': __("This is the standard template that comes with MailPoet."), 'mailpoetGuideTemplateDescription': __("This is the standard template that comes with MailPoet"),
'confirmTemplateDeletion': __('You are about to delete the template named "%$1s"'), 'confirmTemplateDeletion': __('You are about to delete the template named "%$1s"'),
'delete': __('Delete'), 'delete': __('Delete'),
'select': __('Select'), 'select': __('Select'),
@ -105,14 +105,14 @@
'regularNewsletterTypeDescription': __('Send a newsletter with images, buttons, dividers, and social bookmarks. Or, just send a basic text email.'), 'regularNewsletterTypeDescription': __('Send a newsletter with images, buttons, dividers, and social bookmarks. Or, just send a basic text email.'),
'create': __('Create'), 'create': __('Create'),
'welcomeNewsletterTypeTitle': __('Welcome email'), 'welcomeNewsletterTypeTitle': __('Welcome email'),
'welcomeNewsletterTypeDescription': __('Send an email for new users.'), 'welcomeNewsletterTypeDescription': __('Send an email to new users'),
'setUp': __('Set up'), 'setUp': __('Set up'),
'postNotificationNewsletterTypeTitle': __('Post notifications'), 'postNotificationNewsletterTypeTitle': __('Post notifications'),
'postNotificationsNewsletterTypeDescription': __('Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.'), 'postNotificationsNewsletterTypeDescription': __('Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.'),
'selectFrequency': __('Select a frequency'), 'selectFrequency': __('Select a frequency'),
'postNotificationSubjectLineTip': __("Insert [newsletter:total] to show number of posts, [newsletter:post_title] to show the latest post's title & [newsletter:number] to display the issue number."), 'postNotificationSubjectLineTip': __("Insert [newsletter:total] to show number of posts, [newsletter:post_title] to show the latest post's title & [newsletter:number] to display the issue number."),
'activate': __('Activate'), 'activate': __('Activate'),
'sendWelcomeEmailWhen': __('Send welcome email when...'), 'sendWelcomeEmailWhen': __('Send this welcome email when...'),
'daily': __('Once a day at...'), 'daily': __('Once a day at...'),
'weekly': __('Weekly on...'), 'weekly': __('Weekly on...'),
@ -144,22 +144,22 @@
'subjectLine': __('Subject line'), 'subjectLine': __('Subject line'),
'subjectLineTip': __("Be creative! It's the first thing that your subscribers see. Tempt them to open your email."), 'subjectLineTip': __("Be creative! It's the first thing that your subscribers see. Tempt them to open your email."),
'emptySubjectLineError': __('Please specify a subject.'), 'emptySubjectLineError': __('Please specify a subject'),
'segments': __('Segments'), 'segments': __('Segments'),
'segmentsTip': __('This subscriber segment will be used for this campaign.'), 'segmentsTip': __('This subscriber segment will be used for this campaign'),
'selectSegmentPlaceholder': __('Select a segment'), 'selectSegmentPlaceholder': __('Select a segment'),
'noSegmentsSelectedError': __('Please select a segment.'), 'noSegmentsSelectedError': __('Please select a segment'),
'sender': __('Sender'), 'sender': __('Sender'),
'senderTip': __('Your name and email.'), 'senderTip': __('Your name and email.'),
'senderNamePlaceholder': __('John Doe'), 'senderNamePlaceholder': __('John Doe'),
'senderAddressPlaceholder': __('john.doe@email.com'), 'senderAddressPlaceholder': __('john.doe@email.com'),
'replyTo': __('Reply-to'), 'replyTo': __('Reply-to'),
'replyToTip': __('When your subscribers reply to your newsletter, their emails will go to this address.'), 'replyToTip': __('When your subscribers reply to your newsletter, their emails will go to this address'),
'replyToNamePlaceholder': __('John Doe'), 'replyToNamePlaceholder': __('John Doe'),
'replyToAddressPlaceholder': __('john.doe@email.com'), 'replyToAddressPlaceholder': __('john.doe@email.com'),
'newsletterUpdated': __('Newsletter was updated successfully!'), 'newsletterUpdated': __('Newsletter was updated successfully!'),
'newsletterAdded': __('Newsletter was added successfully!'), 'newsletterAdded': __('Newsletter was added successfully!'),
'newsletterSendingError': __('An error occurred while trying to send. <a href="%$1s">Please check your settings.</a>'), 'newsletterSendingError': __('An error occurred while trying to send. <a href="%$1s">Please check your settings</a>'),
'finalNewsletterStep': __('Final step: last details'), 'finalNewsletterStep': __('Final step: last details'),
'saveDraftAndClose': __('Save as draft and close'), 'saveDraftAndClose': __('Save as draft and close'),
'orSimply': __('or simply'), 'orSimply': __('or simply'),
@ -211,10 +211,10 @@
'next': __('Next'), 'next': __('Next'),
'previous': __('Previous'), 'previous': __('Previous'),
'welcomeEmailActivated': __('Your welcome email is now active.'), 'welcomeEmailActivated': __('Your welcome email is now active!'),
'welcomeEmailActivationFailed': __('Your welcome email could not be activated, check its settings.'), 'welcomeEmailActivationFailed': __('Your welcome email could not be activated, please check the settings'),
'postNotificationActivated': __('Your post notification is now active.'), 'postNotificationActivated': __('Your post notification is now active!'),
'postNotificationActivationFailed': __('Your post notification could not be activated, check its settings.'), 'postNotificationActivationFailed': __('Your post notification could not be activated, check the settings'),
'welcomeEventSegment': __('This newsletter is sent when someone subscribes to the list "%$1s"'), 'welcomeEventSegment': __('This newsletter is sent when someone subscribes to the list "%$1s"'),
'welcomeEventWPUserAnyRole': __('This newsletter is sent when a new WordPress user is added to your site'), 'welcomeEventWPUserAnyRole': __('This newsletter is sent when a new WordPress user is added to your site'),
@ -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 %>

View File

@ -13,12 +13,12 @@
'pageTitle': __('Segments'), 'pageTitle': __('Segments'),
'searchLabel': __('Search'), 'searchLabel': __('Search'),
'loadingItems': __('Loading segments...'), 'loadingItems': __('Loading segments...'),
'noItemsFound': __('No segments found.'), 'noItemsFound': __('No segments found'),
'selectAllLabel': __('All segments on this page are selected.'), 'selectAllLabel': __('All segments on this page are selected'),
'selectedAllLabel': __('All %d segments are selected.'), 'selectedAllLabel': __('All %d segments are selected'),
'selectAllLink': __('Select all segments on all pages.'), 'selectAllLink': __('Select all segments on all pages'),
'clearSelection': __('Clear selection.'), 'clearSelection': __('Clear selection'),
'permanentlyDeleted': __('%d segments were permanently deleted.'), 'permanentlyDeleted': __('%d segments were permanently deleted'),
'selectBulkAction': __('Select bulk action'), 'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'), 'bulkActions': __('Bulk Actions'),
'apply': __('Apply'), 'apply': __('Apply'),
@ -31,18 +31,18 @@
'unconfirmed': __('Unconfirmed'), 'unconfirmed': __('Unconfirmed'),
'unsubscribed': __('Unsubscribed'), 'unsubscribed': __('Unsubscribed'),
'createdOn': __('Created on'), 'createdOn': __('Created on'),
'oneSegmentTrashed': __('1 segment was moved to the trash.'), 'oneSegmentTrashed': __('1 segment was moved to the trash'),
'multipleSegmentsTrashed': __('%$1d segments were moved to the trash.'), 'multipleSegmentsTrashed': __('%$1d segments were moved to the trash'),
'oneSegmentDeleted': __('1 segment was permanently deleted.'), 'oneSegmentDeleted': __('1 segment was permanently deleted'),
'multipleSegmentsDeleted': __('%$1d segments were permanently deleted.'), 'multipleSegmentsDeleted': __('%$1d segments were permanently deleted'),
'oneSegmentRestored': __('1 segment has been restored from the trash.'), 'oneSegmentRestored': __('1 segment has been restored from the trash'),
'multipleSegmentsRestored': __('%$1d segments have been restored from the trash.'), 'multipleSegmentsRestored': __('%$1d segments have been restored from the trash'),
'duplicate': __('Duplicate'), 'duplicate': __('Duplicate'),
'listDuplicated': __('List "%$1s" has been duplicated.'), 'listDuplicated': __('List "%$1s" has been duplicated'),
'update': __('Update'), 'update': __('Update'),
'forceSync': __('Force Sync'), 'forceSync': __('Force Sync'),
'readMore': __('Read More'), 'readMore': __('Read More'),
'listSynchronized': __('List "%$1s" has been synchronized.'), 'listSynchronized': __('List "%$1s" has been synchronized'),
'viewSubscribers': __('View Subscribers'), 'viewSubscribers': __('View Subscribers'),
'new': __('New'), 'new': __('New'),
'edit': __('Edit'), 'edit': __('Edit'),
@ -61,7 +61,7 @@
'pageOutOf': __('of'), 'pageOutOf': __('of'),
'numberOfItems': __('%$1d items'), 'numberOfItems': __('%$1d items'),
'segmentDescriptionTip': __('This text box is for your own use and is never shown to your subscribers.'), 'segmentDescriptionTip': __('This text box is for your own use and is never shown to your subscribers'),
'backToList': __('Back to list') 'backToList': __('Back to list')
}) %> }) %>
<% endblock %> <% endblock %>

View File

@ -80,19 +80,19 @@
}).done(function(response) { }).done(function(response) {
if(response === true) { if(response === true) {
MailPoet.Notice.success( MailPoet.Notice.success(
"<%= __('Settings saved.') %>", "<%= __('Settings saved') %>",
{ scroll: true } { scroll: true }
); );
} else { } else {
MailPoet.Notice.error( MailPoet.Notice.error(
"<%= __('Settings could not be saved.') %>", "<%= __('Settings could not be saved') %>",
{ scroll: true } { scroll: true }
); );
} }
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
}).error(function(errors) { }).error(function(errors) {
MailPoet.Notice.error( MailPoet.Notice.error(
"<%= __('An error occurred. Please check your settings.') %>", "<%= __('An error occurred. Please check the settings.') %>",
{ scroll: true } { scroll: true }
); );
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);

View File

@ -7,7 +7,7 @@
<%= __('Bounce email') %> <%= __('Bounce email') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Your bounced emails will be sent to this address.') %> <%= __('Your bounced emails will be sent to this address') %>
</p> </p>
</th> </th>
<td> <td>
@ -21,6 +21,52 @@
</p> </p>
</td> </td>
</tr> </tr>
<!-- task scheduler -->
<tr>
<th scope="row">
<label>
<%= __('Newsletter task scheduler') %>
</label>
<p class="description">
<%= __('Select what will activate your newsletter queue.') %>
<a href="#TODO"
target="_blank"
><%= __('Read more.') %></a>
</p>
</th>
<td>
<p>
<label>
<input
type="radio"
name="task_scheduler[method]"
value="WordPress"
<% if (settings.task_scheduler.method == 'WordPress') %>
checked="checked"
<% endif %>
/><%= __('Visitors to your website (recommended)') %>
</label>
</p>
<p>
<label>
<input
type="radio"
name="task_scheduler[method]"
value="MailPoet"
<% if (settings.task_scheduler.method == 'MailPoet') %>
checked="checked"
<% endif %>
/><%= __("MailPoet's own script. Doesn't work with [link]these hosts[/link].")
|replace({
'[link]': '<a target="_blank" href="#TODO">',
'[/link]': '</a>'
})
|raw
%>
</label>
</p>
</td>
</tr>
<!-- link tracking --> <!-- link tracking -->
<tr> <tr>
<th scope="row"> <th scope="row">
@ -28,30 +74,30 @@
<%= __('Open and click tracking') %> <%= __('Open and click tracking') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Some users prefer to not track their subscribers.') %> <%= __('Some users prefer to not track their subscribers') %>
</p> </p>
</th> </th>
<td> <td>
<p> <p>
<label> <label>
<input <input
type="radio" type="radio"
name="tracking[enabled]" name="tracking[enabled]"
value="1" value="1"
<% if(settings.tracking.enabled) %> <% if(settings.tracking.enabled) %>
checked="checked" checked="checked"
<% endif %> <% endif %>
/><%= __('Yes') %> /><%= __('Yes') %>
</label> </label>
&nbsp; &nbsp;
<label> <label>
<input <input
type="radio" type="radio"
name="tracking[enabled]" name="tracking[enabled]"
value="" value=""
<% if not(settings.tracking.enabled) %> <% if not(settings.tracking.enabled) %>
checked="checked" checked="checked"
<% endif %> <% endif %>
/><%= __('No') %> /><%= __('No') %>
</label> </label>
</p> </p>
@ -64,7 +110,7 @@
<%= __('Share anonymous data') %> <%= __('Share anonymous data') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Share anonymous data and help us improve the plugin! We appreciate your help.') %> <%= __('Share anonymous data and help us improve the plugin. We appreciate your help!') %>
<a <a
href="http://support.mailpoet.com/knowledgebase/share-your-data/?utm_source=wpadmin&utm_campaign=advanced_settings" href="http://support.mailpoet.com/knowledgebase/share-your-data/?utm_source=wpadmin&utm_campaign=advanced_settings"
target="_blank" target="_blank"
@ -102,7 +148,7 @@
<th scope="row"> <th scope="row">
<label><%= __('Reinstall from scratch') %></label> <label><%= __('Reinstall from scratch') %></label>
<p class="description"> <p class="description">
<%= __('Want to start from the beginning? This will completely wipe out MailPoet and reinstall from scratch.') %> <%= __('Want to start over? This will completely wipe out MailPoet and reinstall from scratch.') %>
</p> </p>
</th> </th>
<td> <td>
@ -122,7 +168,7 @@
$(function() { $(function() {
$('#mailpoet_reinstall').on('click', function() { $('#mailpoet_reinstall').on('click', function() {
if(confirm( if(confirm(
"<%= __('If you continue, all of your current MailPoet data will be permanently erased (newsletters, statistics, subscribers, etc...)') %>" "<%= __('Are you sure? All of your MailPoet data will be permanently erased (newsletters, statistics, subscribers, etc.)') %>"
)) { )) {
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);
MailPoet.Ajax.post({ MailPoet.Ajax.post({

View File

@ -6,7 +6,7 @@
<%= __("Default sender") %> <%= __("Default sender") %>
</label> </label>
<p class="description"> <p class="description">
<%= __('These email addresses will be the defaults for each new email created.') %> <%= __('These email addresses will be selected by default for each new email') %>
</p> </p>
</th> </th>
<td> <td>
@ -47,7 +47,7 @@
<%= __("Email notifications") %> <%= __("Email notifications") %>
</label> </label>
<p class="description"> <p class="description">
<%= __('These email addresses will receive notifications (separate each address with a comma).') %> <%= __('These email addresses will receive notifications (separate each address with a comma)') %>
</p> </p>
</th> </th>
<td> <td>
@ -88,7 +88,7 @@
<%= __('Subscribe in comments') %> <%= __('Subscribe in comments') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Visitors that comment on a post can click on a checkbox to subscribe to your list.') %> <%= __('Visitors that comment on a post can subscribe to your list via a checkbox') %>
</p> </p>
</th> </th>
<td> <td>
@ -112,7 +112,7 @@
<% if(settings.subscribe.on_comment.label) %> <% if(settings.subscribe.on_comment.label) %>
value="<%= settings.subscribe.on_comment.label %>" value="<%= settings.subscribe.on_comment.label %>"
<% else %> <% else %>
value="<%= __('Yes, add me to your mailing list.') %>" value="<%= __('Yes, add me to your mailing list') %>"
<% endif %> <% endif %>
/> />
</p> </p>
@ -148,7 +148,7 @@
<%= __('Subscribe in registration form') %> <%= __('Subscribe in registration form') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Allow users who register on your site to subscribe to a list.') %> <%= __('Allow users who register on your site to subscribe to a list') %>
</p> </p>
</th> </th>
<td> <td>
@ -176,7 +176,7 @@
<% if(settings.subscribe.on_register.label) %> <% if(settings.subscribe.on_register.label) %>
value="<%= settings.subscribe.on_register.label %>" value="<%= settings.subscribe.on_register.label %>"
<% else %> <% else %>
value="<%= __('Yes, add me to your mailing list.') %>" value="<%= __('Yes, add me to your mailing list') %>"
<% endif %> <% endif %>
/> />
</p> </p>
@ -203,7 +203,7 @@
</div> </div>
<% else %> <% else %>
<p> <p>
<em><%= __('Registration is disabled on this site.') %></em> <em><%= __('Registration is disabled on this site') %></em>
</p> </p>
<% endif %> <% endif %>
</td> </td>
@ -215,7 +215,7 @@
<%= __('Manage Subscription page') %> <%= __('Manage Subscription page') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Your subscribers will be directed to this page when they click the "Manage your subscription" link in your email.') %> <%= __('When your subscribers click the "Manage your subscription" link, they will be directed to this page') %>
</p> </p>
</th> </th>
<td> <td>
@ -270,9 +270,9 @@
<%= __('Unsubscribe page') %> <%= __('Unsubscribe page') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Your subscribers will be directed to this page when they click the "Unsubscribe" link in your email.') %> <%= __('When your subscribers click the "Unsubscribe" link, they will be directed to this page') %>
<br /> <br />
<%= __('Use this shortcode on your website\'s WordPress pages: [mailpoet_manage text="Manage your subscription"].') %> <%= __('Use this shortcode on your website\'s WordPress pages: [mailpoet_manage text="Manage your subscription"]') %>
</p> </p>
</th> </th>
<td> <td>
@ -307,7 +307,7 @@
<%= __('Archive page shortcode') %> <%= __('Archive page shortcode') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Paste this shortcode on a page to display a list of past newsletters.') %> <%= __('Paste this shortcode on a page to display a list of past newsletters') %>
</p> </p>
</th> </th>
<td> <td>
@ -343,7 +343,7 @@
<%= __('This shortcode displays the total number of subscribers') %> <%= __('This shortcode displays the total number of subscribers') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Paste this shortcode on a post or page to display the total number of confirmed subscribers.') %> <%= __('Paste this shortcode on a post or page to display the total number of confirmed subscribers') %>
</p> </p>
</th> </th>
<td> <td>

View File

@ -263,7 +263,7 @@
id="mailpoet_bounce_test" id="mailpoet_bounce_test"
class="button-secondary" class="button-secondary"
href="javascript:;" href="javascript:;"
><%= __('Is it working? Try to connect.') %></a> ><%= __('Is it working? Try to connect') %></a>
</label> </label>
</th> </th>
<td id="mailpoet_bounce_test_result"></td> <td id="mailpoet_bounce_test_result"></td>

View File

@ -55,7 +55,7 @@
</h3> </h3>
<p class="mailpoet_description"> <p class="mailpoet_description">
<strong><%= __('Currently in closed beta.') %></strong> <strong><%= __('Currently in Closed Beta') %></strong>
<br /> <br />
<%= __('[link]Sign up to our newsletter[/link] to get our latest news on our sending service plus other useful tips and tricks.') <%= __('[link]Sign up to our newsletter[/link] to get our latest news on our sending service plus other useful tips and tricks.')
| replace({ | replace({
@ -83,9 +83,9 @@
<h3><%= __('Your web host / web server') %></h3> <h3><%= __('Your web host / web server') %></h3>
<p class="mailpoet_description"> <p class="mailpoet_description">
<strong><%= __('Free, but not recommended.') %></strong> <strong><%= __('Free, but not recommended') %></strong>
<br /> <br />
<%= __('Web hosts generally have a bad reputation as a sender. Your newsletter will probably be considered spam.') %> <%= __('Web hosts generally have a bad reputation as a sender. Your newsletter will probably be considered spam') %>
</p> </p>
<div class="mailpoet_status"> <div class="mailpoet_status">
@ -105,9 +105,9 @@
<h3><%= __('Third-party') %></h3> <h3><%= __('Third-party') %></h3>
<p class="mailpoet_description"> <p class="mailpoet_description">
<strong><%= __('Currently the best solution.') %></strong> <strong><%= __('Currently the best solution') %></strong>
<br /> <br />
<%= __('Send with an external email provider. This is usually not free.') %> <%= __('Send with an external email provider. This is usually not free') %>
</p> </p>
<div class="mailpoet_status"> <div class="mailpoet_status">
@ -245,9 +245,9 @@
</p> </p>
<br /> <br />
<p> <p>
<%= __('<strong>Warning!</strong> Sending more than the recommended amount of emails? You may break the terms of your web host or provider.') %>' <%= __('<strong>Warning!</strong> Sending more than the recommended amount of emails? You may break the terms of your web host or provider!') %>'
<br /> <br />
<%= __('Please ask your host for the maximum number of emails you are allowed to send per day.') %> <%= __('Please ask your host for the maximum number of emails you are allowed to send per day') %>
</p> </p>
</div> </div>
</td> </td>
@ -546,7 +546,7 @@
<%= __('Authentication') %> <%= __('Authentication') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('Leave this option set to Yes. Only a tiny portion of SMTP services prefer Authentication to be turned off.') %> <%= __('Leave this option set to Yes. Only a tiny portion of SMTP services prefer Authentication to be turned off') %>
</p> </p>
</th> </th>
<td> <td>
@ -588,12 +588,12 @@
<%= __('SPF Signature (Highly recommended!)') %> <%= __('SPF Signature (Highly recommended!)') %>
</label> </label>
<p class="description"> <p class="description">
<%= __("Improves your delivery rate by verifying that you're allowed to send emails from your domain.") %> <%= __("Improves your delivery rate by verifying that you're allowed to send emails from your domain") %>
</p> </p>
</th> </th>
<td> <td>
<p> <p>
<%= __("SPF is set up in your DNS. Read your host's support documentation for more information.") %> <%= __("SPF is set up in your DNS. Read your host's support documentation for more information") %>
</p> </p>
</td> </td>
</tr> </tr>
@ -631,7 +631,7 @@
<a <a
href="javascript:;" href="javascript:;"
class="mailpoet_mta_setup_cancel" class="mailpoet_mta_setup_cancel"
><%= __('or cancel.') %></a> ><%= __('or Cancel') %></a>
</p> </p>
</th> </th>
<td></td> <td></td>
@ -688,9 +688,9 @@
data: { data: {
mailer: mailer, mailer: mailer,
newsletter: { newsletter: {
subject: "<%= __('This is a sending method test.') %>", subject: "<%= __('This is a Sending Method Test') %>",
body: { body: {
text: "<%= __('Yup, it works. You can start blasting away emails to the moon.') %>" text: "<%= __('Yup, it works! You can start blasting away emails to the moon.') %>"
} }
}, },
subscriber: { subscriber: {
@ -709,7 +709,7 @@
} else { } else {
if (response.errors) { if (response.errors) {
MailPoet.Notice.error( MailPoet.Notice.error(
"<%= __('The email could not be sent.') %> " + response.errors, "<%= __('The email could not be sent: ') %> " + response.errors,
{ scroll: true } { scroll: true }
); );
} }

View File

@ -151,7 +151,7 @@
<%= __('Confirmation page') %> <%= __('Confirmation page') %>
</label> </label>
<p class="description"> <p class="description">
<%= __('When subscribers click on the activation link, they are redirected to this page.') %> <%= __('When subscribers click on the activation link, they are redirected to this page') %>
</p> </p>
</th> </th>
<td> <td>
@ -192,9 +192,9 @@
var result = false; var result = false;
if(~~($(this).val()) === 1) { if(~~($(this).val()) === 1) {
result = confirm("<%= __('Subscribers will need to activate their subscription via email in order to receive your newsletters. This is highly recommended.') %>"); result = confirm("<%= __('Subscribers will need to activate their subscription via email in order to receive your newsletters. This is highly recommended!') %>");
} else { } else {
result = confirm("<%= __('Unconfirmed subscribers will receive your newsletters from without needing to activate their subscriptions.') %>"); result = confirm("<%= __('Unconfirmed subscribers will receive your newsletters from without needing to activate their subscriptions. This is not recommended!') %>");
} }
// if the user confirmed changing the signup confirmation (yes/no) // if the user confirmed changing the signup confirmation (yes/no)
if(result === true) { if(result === true) {

View File

@ -8,7 +8,7 @@
<!-- number of emails per day --> <!-- number of emails per day -->
<%= <%=
__("That's <strong>%1$s emails</strong> per day.") __("That's <strong>%1$s emails</strong> per day")
| format('{{ daily_emails }}') | format('{{ daily_emails }}')
| raw | raw
%> %>
@ -17,7 +17,7 @@
<br /><br /> <br /><br />
<span style="color: #d54e21;"> <span style="color: #d54e21;">
<%= <%=
__("That's %1$s emails per second. <strong>We highly recommend to send 1 email per second at most.</strong> This is the time MailPoet needs to process and send a single email from most hosts. <strong>Alternatively, send with MailPoet which can be 50 times faster.</strong>") __("That's %1$s emails per second. <strong>We highly recommend to send 1 email per second, at the absolute maximum.</strong> MailPoet needs at least one second to process and send a single email (on most hosts.) <strong>Alternatively, send with MailPoet, which can be up to 50 times faster.</strong>")
| format('{{ emails_per_second }}') | format('{{ emails_per_second }}')
| raw | raw
%> %>

View File

@ -8,7 +8,7 @@
</h1> </h1>
<% if segments is empty %> <% if segments is empty %>
<div class="error"> <div class="error">
<p><%= __("Yikes! Couldn't find any subscribers.") %></p> <p><%= __("Yikes! Couldn't find any subscribers") %></p>
</div> </div>
<% endif %> <% endif %>
<div class="inside"> <div class="inside">

View File

@ -32,26 +32,26 @@
<% block translations %> <% block translations %>
<%= localize({ <%= localize({
'noMailChimpLists': __('No active lists found.'), 'noMailChimpLists': __('No active lists found'),
'serverError': __('Server error:'), 'serverError': __('Server error:'),
'select': __('Select'), 'select': __('Select'),
'csvKBLink': csvKBLink, 'csvKBLink': csvKBLink,
'wrongFileFormat': __('Only comma-separated (CSV) file formats are supported.'), 'wrongFileFormat': __('Only comma-separated (CSV) file formats are supported'),
'maxPostSizeNotice': __('Your CSV is over %s and is too big to process. Please split the file into two or more sections.')|replace({'%s': maxPostSize}), 'maxPostSizeNotice': __('Your CSV is over %s and is too big to process. Please split the file into two or more sections.')|replace({'%s': maxPostSize}),
'dataProcessingError': __("Your data could not be processed. Please make sure it is in the correct format."), 'dataProcessingError': __("Your data could not be processed. Please make sure it is in the correct format."),
'noValidRecords': __('No valid records were found. This file needs to be formatted in a CSV style (comma-separated.) Look at some [link]examples on our support site[/link]'), 'noValidRecords': __('No valid records were found. This file needs to be formatted in a CSV style (comma-separated.) Look at some [link]examples on our support site[/link]'),
'importNoticeSkipped': __('%1$s records had issues and were skipped.'), 'importNoticeSkipped': __('%1$s records had issues and were skipped'),
'importNoticeInvalid': __('%1$s emails are not valid : %2$s.'), 'importNoticeInvalid': __('%1$s emails are not valid : %2$s'),
'importNoticeDuplicate': __('%1$s emails appear more than once in your file : %2$s.'), 'importNoticeDuplicate': __('%1$s emails appear more than once in your file : %2$s'),
'hideDetails': __('Hide details.'), 'hideDetails': __('Hide details'),
'showDetails': __('Show more details.'), 'showDetails': __('Show more details'),
'segmentSelectionRequired': __('Please select at least one list.'), 'segmentSelectionRequired': __('Please select at least one list'),
'addNewList': __('Add new list'), 'addNewList': __('Add new list'),
'addNewColumuserColumnsn': __('Add new list'), 'addNewColumuserColumnsn': __('Add new list'),
'userColumns': __('User columns'), 'userColumns': __('User columns'),
'selectedValueAlreadyMatched': __('The selected value is already matched to another column.'), 'selectedValueAlreadyMatched': __('The selected value is already matched to another column'),
'confirmCorrespondingColumn': __('Confirm that this column corresponds to the selected field.'), 'confirmCorrespondingColumn': __('Confirm that this column corresponds to the selected field'),
'columnContainInvalidElement': __('One of the columns contains an invalid email. Please fix it before continuing.'), 'columnContainInvalidElement': __('One of the columns contains an invalid email. Please fix it before continuing'),
'january': __('January'), 'january': __('January'),
'february': __('February'), 'february': __('February'),
'march': __('March'), 'march': __('March'),
@ -64,17 +64,17 @@
'october': __('October'), 'october': __('October'),
'november': __('November'), 'november': __('November'),
'december': __('December'), 'december': __('December'),
'noDateFieldMatch': __("Do not match as a 'date field' if most of the rows for that column return the same error."), 'noDateFieldMatch': __("Do not match as a 'date field' if most of the rows for that column return the same error"),
'emptyDate': __('Date cannot be empty'), 'emptyDate': __('Date cannot be empty'),
'verifyDateMatch': __('Verify that the date in blue matches the original date'), 'verifyDateMatch': __('Verify that the date in blue matches the original date'),
'pm': __('PM'), 'pm': __('PM'),
'am': __('AM'), 'am': __('AM'),
'dateMatchError': __('Error matching date.'), 'dateMatchError': __('Error matching date'),
'columnContainsInvalidDate': __('One of the columns contains an invalid date. Please fix it before continuing.'), 'columnContainsInvalidDate': __('One of the columns contains an invalid date. Please fix it before continuing'),
'listCreateError': __('Error adding a new segment:'), 'listCreateError': __('Error adding a new segment:'),
'columnContainsInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing.'), 'columnContainsInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing'),
'customFieldCreateError': __('Custom field could not be created.'), 'customFieldCreateError': __('Custom field could not be created'),
'subscribersCreated': __('%1$s subscribers added to %2$s.'), 'subscribersCreated': __('%1$s subscribers added to %2$s.'),
'subscribersUpdated': __('%1$s existing subscribers were updated and added to %2$s.') 'subscribersUpdated': __('%1$s existing subscribers were updated and added to %2$s')
}) %> }) %>
<% endblock %> <% endblock %>

View File

@ -113,7 +113,7 @@
<%= ('If the answer is "no", consider yourself a spammer.') %> <%= ('If the answer is "no", consider yourself a spammer.') %>
<br/> <br/>
<%= <%=
__('[link]Read more on support.mailpoet.com[/link].') __('[link]Read more on support.mailpoet.com[/link]')
|replace({ |replace({
'[link]': '<a target="_blank" href="http://support.mailpoet.com/knowledgebase/dont-import-subscribers-who-didnt-sign-up/#utm_source=wpadmin&utm_campaign=importwarning">', '[link]': '<a target="_blank" href="http://support.mailpoet.com/knowledgebase/dont-import-subscribers-who-didnt-sign-up/#utm_source=wpadmin&utm_campaign=importwarning">',
'[/link]': '</a>' '[/link]': '</a>'

View File

@ -34,12 +34,12 @@
<th scope="row"> <th scope="row">
<label> <label>
<%= __('Pick one or many segments') %> <%= __('Pick one or many segments') %>
<p class="description"><%= __('Pick the segments you want to import these subscribers to.') %> <p class="description"><%= __('Pick the segments that you want to import these subscribers to.') %>
</label> </label>
</th> </th>
<td> <td>
<select id="mailpoet_segments_select" data-placeholder="<%= __('Select') %>" multiple="multiple"></select> <select id="mailpoet_segments_select" data-placeholder="<%= __('Select') %>" multiple="multiple"></select>
<a href="javascript:;" class="mailpoet_create_segment"><%= __('Create new list') %></a> <a href="javascript:;" class="mailpoet_create_segment"><%= __('Create a new list') %></a>
</td> </td>
</tr> </tr>
<tr class="mailpoet_no_segments mailpoet_hidden"> <tr class="mailpoet_no_segments mailpoet_hidden">
@ -134,7 +134,7 @@
<input id="new_segment_name" type="text" name="name"/> <input id="new_segment_name" type="text" name="name"/>
</p> </p>
<p class="mailpoet_validation_error" data-error="segment_name_required"> <p class="mailpoet_validation_error" data-error="segment_name_required">
<%= __('Please specify a name.') %> <%= __('Please specify a name') %>
</p> </p>
<p class="mailpoet_validation_error" data-error="segment_name_not_unique"> <p class="mailpoet_validation_error" data-error="segment_name_not_unique">
<%= __('Another record already exists. Please specify a different "%1$s".')|format('name') %> <%= __('Another record already exists. Please specify a different "%1$s".')|format('name') %>
@ -192,10 +192,10 @@
<input id="new_column_name" type="text" name="name" value="{{ name }}"/> <input id="new_column_name" type="text" name="name" value="{{ name }}"/>
</p> </p>
<p class="mailpoet_validation_error" data-error="name_required"> <p class="mailpoet_validation_error" data-error="name_required">
<%= __('Please to specify a name.') %> <%= __('Please specify a name') %>
</p> </p>
<p class="mailpoet_validation_error" data-error="name_not_unique"> <p class="mailpoet_validation_error" data-error="name_not_unique">
<%= __('This name is already taken.') %> <%= __('This name is already taken') %>
</p> </p>
<hr/> <hr/>

View File

@ -25,10 +25,10 @@
<p>{{{updated}}}</p> <p>{{{updated}}}</p>
{{/if}} {{/if}}
{{#if no_action}} {{#if no_action}}
<p><%= __('No new subscribers were found/added.') %></p> <p><%= __('No new subscribers were found/added') %></p>
{{/if}} {{/if}}
{{#if added_to_segment_with_welcome_notification}} {{#if added_to_segment_with_welcome_notification}}
<p><%= __('Note: Imported subscribers will not receive any welcome emails.') %></p> <p><%= __('Note: Imported subscribers will not receive any welcome emails') %></p>
{{/if}} {{/if}}
</script> </script>
</div> </div>

View File

@ -17,12 +17,12 @@
'pageTitle': __('Subscribers'), 'pageTitle': __('Subscribers'),
'searchLabel': __('Search'), 'searchLabel': __('Search'),
'loadingItems': __('Loading subscribers...'), 'loadingItems': __('Loading subscribers...'),
'noItemsFound': __('No subscribers were found.'), 'noItemsFound': __('No subscribers were found'),
'selectAllLabel': __('All subscribers on this page are selected.'), 'selectAllLabel': __('All subscribers on this page are selected'),
'selectedAllLabel': __('All %d subscribers are selected.'), 'selectedAllLabel': __('All %d subscribers are selected'),
'selectAllLink': __('Select all subscribers on all pages.'), 'selectAllLink': __('Select all subscribers on all pages'),
'clearSelection': __('Clear selection.'), 'clearSelection': __('Clear selection'),
'permanentlyDeleted': __('%d subscribers were permanently deleted.'), 'permanentlyDeleted': __('%d subscribers were permanently deleted'),
'selectBulkAction': __('Select bulk action'), 'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'), 'bulkActions': __('Bulk Actions'),
'apply': __('Apply'), 'apply': __('Apply'),
@ -60,23 +60,23 @@
'lists': __('Lists'), 'lists': __('Lists'),
'subscribedOn': __('Subscribed on'), 'subscribedOn': __('Subscribed on'),
'lastModifiedOn': __('Last modified on'), 'lastModifiedOn': __('Last modified on'),
'oneSubscriberTrashed': __('1 subscriber was moved to the trash.'), 'oneSubscriberTrashed': __('1 subscriber was moved to the trash'),
'multipleSubscribersTrashed': __('%$1d subscribers were moved to the trash.'), 'multipleSubscribersTrashed': __('%$1d subscribers were moved to the trash'),
'oneSubscriberDeleted': __('1 subscriber was permanently deleted.'), 'oneSubscriberDeleted': __('1 subscriber was permanently deleted'),
'multipleSubscribersDeleted': __('%$1d subscribers were permanently deleted.'), 'multipleSubscribersDeleted': __('%$1d subscribers were permanently deleted'),
'oneSubscriberRestored': __('1 subscriber has been restored from the trash.'), 'oneSubscriberRestored': __('1 subscriber has been restored from the trash'),
'multipleSubscribersRestored': __('%$1d subscribers have been restored from the trash.'), 'multipleSubscribersRestored': __('%$1d subscribers have been restored from the trash'),
'moveToList': __('Move to list...'), 'moveToList': __('Move to list...'),
'multipleSubscribersMovedToList': __('%$1d subscribers were moved to list <strong>%$2s</strong>.'), 'multipleSubscribersMovedToList': __('%$1d subscribers were moved to list <strong>%$2s</strong>'),
'addToList': __('Add to list...'), 'addToList': __('Add to list...'),
'multipleSubscribersAddedToList': __('%$1d subscribers were added to list <strong>%$2s</strong>.'), 'multipleSubscribersAddedToList': __('%$1d subscribers were added to list <strong>%$2s</strong>'),
'removeFromList': __('Remove from list...'), 'removeFromList': __('Remove from list...'),
'multipleSubscribersRemovedFromList': __('%$1d subscribers were removed from list <strong>%$2s</strong>.'), 'multipleSubscribersRemovedFromList': __('%$1d subscribers were removed from list <strong>%$2s</strong>'),
'removeFromAllLists': __('Remove from all lists'), 'removeFromAllLists': __('Remove from all lists'),
'multipleSubscribersRemovedFromAllLists': __('%$1d subscribers were removed from all lists.'), 'multipleSubscribersRemovedFromAllLists': __('%$1d subscribers were removed from all lists'),
'resendConfirmationEmail': __('Resend confirmation email'), 'resendConfirmationEmail': __('Resend confirmation email'),
'multipleConfirmationEmailsSent': __('%$1d confirmation emails have been sent.'), 'multipleConfirmationEmailsSent': __('%$1d confirmation emails have been sent'),
'listsToWhichSubscriberWasSubscribed': __('Lists to which the subscriber was subscribed.'), 'listsToWhichSubscriberWasSubscribed': __('Lists to which the subscriber was subscribed'),
'WPUsersSegment': __('WordPress Users'), 'WPUsersSegment': __('WordPress Users'),
'WPUserEditNotice': __('This subscriber is a registered WordPress user. [link]Edit his profile[/link] to change his/her email.'), 'WPUserEditNotice': __('This subscriber is a registered WordPress user. [link]Edit his profile[/link] to change his/her email.'),
'tip': __('Tip:'), 'tip': __('Tip:'),

View File

@ -79,8 +79,8 @@
<h2> <h2>
<%= __('Welcome to MailPoet') %> <%= settings.version %> <%=__('BETA') %> <%= __('Welcome to MailPoet') %> <%= settings.version %> <%=__('BETA') %>
</h2> </h2>
<p>Youre one our extra special testers! We salute you.</p> <p>Youre one of our extra special testers! We salute you.</p>
<h3 class="changes">Big changes...watch the video!</h3> <h3 class="changes">Big changes are coming. Check out the video for more details!</h3>
<iframe src="https://player.vimeo.com/video/130151897?title=0&byline=0&portr <iframe src="https://player.vimeo.com/video/130151897?title=0&byline=0&portr
ait=0" width="710" height="399" frameborder="0" webkitallowfullscreen ait=0" width="710" height="399" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen></iframe> mozallowfullscreen allowfullscreen></iframe>