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';
MailPoet.Ajax = {
version: 0.5,
@ -52,6 +52,13 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
var params = this.getParams();
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
if(method === 'get') {
jqXHR = jQuery.get(

View File

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

View File

@ -1,6 +1,7 @@
import MailPoet from 'mailpoet'
import jQuery from 'jquery'
import React from 'react'
import _ from 'underscore'
import { Router, Link } from 'react-router'
import classNames from 'classnames'
import ListingBulkActions from 'listing/bulk_actions.jsx'
@ -13,7 +14,7 @@ import ListingFilters from 'listing/filters.jsx'
const ListingItem = React.createClass({
getInitialState: function() {
return {
toggled: true
expanded: false
};
},
handleSelectItem: function(e) {
@ -34,7 +35,7 @@ const ListingItem = React.createClass({
this.props.onDeleteItem(id);
},
handleToggleItem: function(id) {
this.setState({ toggled: !this.state.toggled });
this.setState({ expanded: !this.state.expanded });
},
render: function() {
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 (
<tr className={ row_classes }>
@ -303,13 +304,12 @@ const Listing = React.createClass({
getParam: function(param) {
const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param);
return [matches[1], matches[2]]
return [matches[1], matches[2]];
},
initWithParams: function(params) {
let state = this.getInitialState();
// check for url params
if (params.splat !== undefined) {
if (params.splat) {
params.splat.split('/').map(param => {
let [key, value] = this.getParam(param);
switch(key) {
@ -348,6 +348,17 @@ const Listing = React.createClass({
this.getItems();
}.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() {
if (this.props.location) {
let params = Object.keys(this.state)
@ -378,17 +389,37 @@ const Listing = React.createClass({
.filter(key => { return (key !== undefined) })
.join('/');
// prepend url with "tab" if specified
if (this.props.tab !== undefined) {
params = `/${ this.props.tab }/${ params }`;
// set url
let url = this.getUrlWithParams(params);
if (this.props.location.pathname !== url) {
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 {
params = `/${ params }`;
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]);
}
});
}
if (this.props.location.pathname !== params) {
this.context.router.push(`${params}`);
}
}
return base_url;
},
componentDidMount: function() {
if (this.isMounted()) {
@ -416,7 +447,7 @@ const Listing = React.createClass({
endpoint: this.props.endpoint,
action: 'listing',
data: {
tab: (this.props.tab) ? this.props.tab : '',
params: this.getParams(),
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
@ -531,7 +562,7 @@ const Listing = React.createClass({
var data = params || {};
data.listing = {
tab: (this.props.tab) ? this.props.tab : '',
params: this.getParams(),
offset: 0,
limit: 0,
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>
);
},
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) {
const rowClasses = classNames(
'manage-column',
@ -277,7 +291,7 @@ const NewsletterListNotification = React.createClass({
{ this.renderSettings(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('history') }>
<a href="#TODO">{ MailPoet.I18n.t('viewHistory') }</a>
{ this.renderHistoryLink(newsletter) }
</td>
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
@ -299,7 +313,8 @@ const NewsletterListNotification = React.createClass({
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
tab="notification"
type="notification"
base_url="notification"
onRenderItem={ this.renderItem }
columns={ columns }
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 ListingTabs from 'newsletters/listings/tabs.jsx'
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = {
@ -139,135 +141,7 @@ const newsletter_actions = [
];
const NewsletterListStandard = React.createClass({
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();
});
},
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>
);
}
},
mixins: [QueueMixin, StatisticsMixin],
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
@ -291,7 +165,7 @@ const NewsletterListStandard = React.createClass({
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderStatus(newsletter) }
{ this.renderQueueStatus(newsletter) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
@ -321,7 +195,8 @@ const NewsletterListStandard = React.createClass({
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
tab="standard"
type="standard"
base_url="standard"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }

View File

@ -343,7 +343,8 @@ const NewsletterListWelcome = React.createClass({
location={ this.props.location }
params={ this.props.params }
endpoint="newsletters"
tab="welcome"
type="welcome"
base_url="welcome"
onRenderItem={ this.renderItem }
columns={ columns }
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 NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
import NewsletterListNotificationHistory from 'newsletters/listings/notification_history.jsx'
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
return this.props.children
return this.props.children;
}
});
@ -31,18 +32,16 @@ if(container) {
<Route path="/" component={ App }>
<IndexRedirect to="standard" />
{/* Listings */}
<Route name="listing/standard" path="standard" component={ NewsletterListStandard } />
<Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } />
<Route name="listing/notification" path="notification" component={ NewsletterListNotification } />
<Route path="standard/*" component={ NewsletterListStandard } />
<Route path="welcome/*" component={ NewsletterListWelcome } />
<Route path="notification/*" component={ NewsletterListNotification } />
<Route path="standard(/)**" params={{ tab: 'standard' }} component={ NewsletterListStandard } />
<Route path="welcome(/)**" component={ NewsletterListWelcome } />
<Route path="notification/history/:parent_id(/)**" component={ NewsletterListNotificationHistory } />
<Route path="notification(/)**" component={ NewsletterListNotification } />
{/* Newsletter: type selection */}
<Route path="new" component={ NewsletterTypes } />
{/* New newsletter: types */}
<Route name="new/standard" path="new/standard" component={ NewsletterTypeStandard } />
<Route name="new/welcome" path="new/welcome" component={ NewsletterTypeWelcome } />
<Route name="new/notification" path="new/notification" component={ NewsletterTypeNotification } />
<Route path="new/standard" component={ NewsletterTypeStandard } />
<Route path="new/welcome" component={ NewsletterTypeWelcome } />
<Route path="new/notification" component={ NewsletterTypeNotification } />
{/* Template selection */}
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
{/* Sending options */}

View File

@ -32,7 +32,7 @@ class API {
$endpoint = self::ENDPOINT_NAMESPACE . ucfirst($this->endpoint);
if(!$this->api_request) return;
if(!$this->endpoint || !class_exists($endpoint)) {
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API endpoint.'));
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API endpoint.'));
}
$this->callEndpoint(
$endpoint,
@ -43,7 +43,7 @@ class API {
function callEndpoint($endpoint, $action, $data) {
if(!method_exists($endpoint, $action)) {
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API action.'));
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API action.'));
}
call_user_func(
array(
@ -56,9 +56,16 @@ class API {
static function decodeRequestData($data) {
$data = base64_decode($data);
return (is_serialized($data)) ?
unserialize($data) :
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API data format.'));
if(is_serialized($data)) {
$data = unserialize($data);
}
if(!is_array($data)) {
$data = array();
}
return $data;
}
static function encodeRequestData($data) {
@ -76,7 +83,7 @@ class API {
return add_query_arg($params, home_url());
}
function terminateRequest($code, $message) {
static function terminateRequest($code, $message) {
status_header($code, $message);
exit;
}

View File

@ -10,6 +10,7 @@ class Subscription {
static function confirm($data) {
$subscription = new UserSubscription\Pages('confirm', $data);
$subscription->confirm();
}
static function manage($data) {
@ -18,5 +19,6 @@ class Subscription {
static function 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('username', Env::$db_username);
\ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG);
\ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\PDO::MYSQL_ATTR_INIT_COMMAND =>

View File

@ -173,6 +173,7 @@ class Migrator {
function newsletters() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'parent_id mediumint(9) NULL,',
'subject varchar(250) NOT NULL DEFAULT "",',
'type varchar(20) NOT NULL DEFAULT "standard",',
'sender_address varchar(150) NOT NULL DEFAULT "",',

View File

@ -69,6 +69,13 @@ class Populator {
private function createDefaultSettings() {
$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
$sender = array(
'name' => $current_user->display_name,
@ -106,7 +113,7 @@ class Populator {
$default_segment->hydrate(array(
'name' => __('My First List'),
'description' =>
__('The list is automatically created when you install MailPoet')
__('This list is automatically created when you install MailPoet')
));
$default_segment->save();
}

View File

@ -254,7 +254,7 @@ class FranksRoastHouseTemplate {
),
array(
"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() {
return array(
'name' => __("Post Notifications Blank Template"),
'description' => __("A simple and plain post notifications template for you to create your own email from."),
'name' => __("Blank Post Notifications Template"),
'description' => __("A simple post notifications template. Customize it and make it your own!"),
'readonly' => 0,
'thumbnail' => $this->getThumbnail(),
'body' => json_encode($this->getBody()),
@ -72,7 +72,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array(
array(
"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() {
return array(
'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,
'thumbnail' => $this->getThumbnail(),
'body' => json_encode($this->getBody()),
@ -101,7 +101,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"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\SubscriberSegment;
use \MailPoet\Subscription;
use MailPoet\Newsletter\Url as NewsletterUrl;
class Shortcodes {
function __construct() {
@ -113,7 +114,9 @@ class Shortcodes {
}
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($newsletter->subject).
'</a>';

View File

@ -67,29 +67,33 @@ class Scheduler {
}
function processPostNotificationNewsletter($newsletter, $queue) {
// ensure that segments exist
$segments = $newsletter->segments()->findArray();
if(empty($segments)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
}
$segment_ids = array_map(function($segment) {
return $segment['id'];
return (int)$segment['id'];
}, $segments);
// ensure that subscribers are in segments
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
if(empty($subscribers)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
}
// schedule new queue if the post notification is not destined for immediate delivery
if($newsletter->intervalType !== NewsletterScheduler::INTERVAL_IMMEDIATELY) {
$new_queue = SendingQueue::create();
$new_queue->newsletter_id = $newsletter->id;
$new_queue->status = NewsletterScheduler::STATUS_SCHEDULED;
self::deleteQueueOrUpdateNextRunDate($new_queue, $newsletter);
}
// create a duplicate newsletter that acts as a history record
$notification_history = $this->createNotificationHistory($newsletter->id);
if(!$notification_history) return;
// queue newsletter for delivery
$queue->newsletter_id = $notification_history->id;
$queue->subscribers = serialize(
array(
'to_process' => $subscribers
@ -164,7 +168,7 @@ class Scheduler {
return true;
}
private function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
if($newsletter->intervalType === NewsletterScheduler::INTERVAL_IMMEDIATELY) {
$queue->delete();
} else {
@ -173,4 +177,12 @@ class Scheduler {
$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,10 +178,11 @@ class SendingQueue {
if(!$queue->count_to_process) {
$queue->processed_at = current_time('mysql');
$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);
// 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);
}
}

View File

@ -43,7 +43,7 @@ class Mailer {
function getMailerConfig() {
$mta_config = Setting::getValue('mta');
if(!$mta_config) {
throw new \Exception(__('Mailer is not configured.'));
throw new \Exception(__('Mailer is not configured'));
}
return $mta_config;
}
@ -90,7 +90,7 @@ class Mailer {
if($this->mta_log['sent'] === $frequency_limit &&
$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) {
$this->mta_log = array(

View File

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

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterPost;
if(!defined('ABSPATH')) exit;
@ -18,9 +19,12 @@ class Posts {
if(!count($matched_posts_ids)) {
return $newsletter;
}
$newsletter_id = ($newsletter['type'] === Newsletter::TYPE_NOTIFICATION_HISTORY) ?
$newsletter['parent_id'] :
$newsletter['id'];
foreach($matched_posts_ids as $post_id) {
$newletter_post = NewsletterPost::create();
$newletter_post->newsletter_id = $newsletter['id'];
$newletter_post->newsletter_id = $newsletter_id;
$newletter_post->post_id = $post_id;
$newletter_post->save();
}

View File

@ -34,7 +34,7 @@ abstract class Base {
if(in_array($block['type'], array('radio', 'checkbox'))) {
$rules['group'] = 'custom_field_'.$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();

View File

@ -17,7 +17,7 @@ class Widget extends \WP_Widget {
'mailpoet_form',
__('MailPoet Form'),
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->data = array(
// tabs
'tab' => (isset($data['tab']) ? $data['tab'] : false),
// extra parameters
'params' => (isset($data['params']) ? $data['params'] : array()),
// pagination
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
'limit' => (isset($data['limit'])
@ -121,7 +121,6 @@ class Handler {
$this->table_name.'.'.$this->data['sort_by']
)
->findMany();
} else {
$this->setFilter();
$this->setGroup();

View File

@ -84,7 +84,7 @@ class Mailer {
);
break;
default:
throw new \Exception(__('Mailing method does not exist.'));
throw new \Exception(__('Mailing method does not exist'));
}
return $mailer_instance;
}
@ -92,7 +92,7 @@ class Mailer {
function getMailer($mailer = false) {
if(!$mailer) {
$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'];
return $mailer;
@ -101,7 +101,7 @@ class Mailer {
function getSender($sender = false) {
if(empty($sender)) {
$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(
'from_name' => $sender['name'],

View File

@ -9,10 +9,10 @@ class CustomField extends Model {
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name.')
'required' => __('Please specify a name')
));
$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();
$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_WELCOME = 'welcome';
const TYPE_NOTIFICATION = 'notification';
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
// standard newsletters
const STATUS_DRAFT = 'draft';
@ -19,12 +20,11 @@ class Newsletter extends Model {
// automatic newsletters status
const STATUS_ACTIVE = 'active';
function __construct() {
parent::__construct();
$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()) {
// get current newsletter's data as an array
$newsletter_data = $this->asArray();
// remove id so that it creates a new record
@ -109,6 +108,43 @@ class Newsletter extends Model {
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() {
$model = parent::asArray();
@ -125,6 +161,14 @@ class Newsletter extends Model {
return parent::delete();
}
function children() {
return $this->has_many(
__NAMESPACE__.'\Newsletter',
'parent_id',
'id'
);
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
@ -139,6 +183,11 @@ class Newsletter extends Model {
return $this;
}
function withChildrenCount() {
$this->children_count = $this->children()->count();
return $this;
}
function options() {
return $this->has_many_through(
__NAMESPACE__.'\NewsletterOptionField',
@ -189,15 +238,11 @@ class Newsletter extends Model {
} else {
$this->statistics = $statistics->asArray();
}
return $this;
}
function getStatistics() {
if($this->queue === false) {
return false;
}
return SendingQueue::tableAlias('queues')
$statistics_query = SendingQueue::tableAlias('queues')
->selectExpr(
'COUNT(DISTINCT(clicks.subscriber_id)) as clicked, ' .
'COUNT(DISTINCT(opens.subscriber_id)) as opened, ' .
@ -217,10 +262,23 @@ class Newsletter extends Model {
MP_STATISTICS_UNSUBSCRIBES_TABLE,
'queues.id = unsubscribes.queue_id',
'unsubscribes'
)
);
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 = '') {
if(strlen(trim($search)) > 0) {
@ -230,6 +288,15 @@ class Newsletter extends Model {
}
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();
$segment_list = array();
$segment_list[] = array(
@ -239,7 +306,7 @@ class Newsletter extends Model {
foreach($segments as $segment) {
$newsletters = $segment->newsletters()
->filter('filterType', $data['tab'])
->filter('filterType', $type)
->filter('groupBy', $data);
$newsletters_count = $newsletters->count();
@ -260,18 +327,32 @@ class Newsletter extends Model {
}
static function filterBy($orm, $data = array()) {
$type = isset($data['tab']) ? $data['tab'] : null;
// apply filters
if(!empty($data['filter'])) {
foreach($data['filter'] as $key => $value) {
if($key === 'segment') {
$segment = Segment::findOne($value);
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;
}
@ -306,7 +387,14 @@ class Newsletter extends Model {
}
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(
array(
@ -431,7 +519,8 @@ class Newsletter extends Model {
if(in_array($type, array(
self::TYPE_STANDARD,
self::TYPE_WELCOME,
self::TYPE_NOTIFICATION
self::TYPE_NOTIFICATION,
self::TYPE_NOTIFICATION_HISTORY
))) {
$orm->where('type', $type);
}
@ -447,7 +536,6 @@ class Newsletter extends Model {
'updated_at',
'deleted_at'
))
->filter('filterType', $data['tab'])
->filter('filterBy', $data)
->filter('groupBy', $data)
->filter('search', $data['search']);

View File

@ -9,10 +9,10 @@ class NewsletterOptionField extends Model {
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name.')
'required' => __('Please specify a name')
));
$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();
$this->addValidations('name', array(
'required' => __('Please specify a name.')
'required' => __('Please specify a name')
));
$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();
$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(
'name' => __('WordPress Users'),
'description' =>
__('This lists containts all of your WordPress users.'),
__('This lists containts all of your WordPress users'),
'type' => 'wp_users'
));
$wp_segment->save();

View File

@ -17,7 +17,7 @@ class Setting extends Model {
parent::__construct();
$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 {
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) {
if($form_id > 0 && $subscriber_id > 0) {
// check if we already have a record for today

View File

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

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Models\Newsletter;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Renderer {
@ -10,7 +11,10 @@ class Renderer {
function __construct(array $newsletter, $posts = false) {
$this->newsletter = $newsletter;
$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) {
@ -45,12 +49,12 @@ class Renderer {
function processAutomatedLatestContent($args, $column_count) {
$posts_to_exclude = $this->getPosts();
$ALCPosts = $this->ALC->getPosts($args, $posts_to_exclude);
foreach($ALCPosts as $post) {
$ALC_posts = $this->ALC->getPosts($args, $posts_to_exclude);
foreach($ALC_posts as $post) {
$posts_to_exclude[] = $post->ID;
}
$transformed_posts = array(
'blocks' => $this->ALC->transformPosts($args, $ALCPosts)
'blocks' => $this->ALC->transformPosts($args, $ALC_posts)
);
$this->setPosts($posts_to_exclude);
$transformed_posts = StylesHelper::applyTextAlignment($transformed_posts);

View File

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

View File

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

View File

@ -165,7 +165,7 @@ class Newsletters {
if(!isset($data['body'])) {
return array(
'result' => false,
'errors' => array(__('Newsletter data is missing.'))
'errors' => array(__('Newsletter data is missing'))
);
}
$newsletter_id = (isset($data['id'])) ? (int)$data['id'] : null;
@ -173,7 +173,7 @@ class Newsletters {
if(!$newsletter) {
return array(
'result' => false,
'errors' => array(__('Newsletter could not be read.'))
'errors' => array(__('Newsletter could not be read'))
);
}
$newsletter->body = $data['body'];
@ -245,6 +245,7 @@ class Newsletters {
}
function listing($data = array()) {
$listing = new Listing\Handler(
'\MailPoet\Models\Newsletter',
$data
@ -267,6 +268,11 @@ class Newsletters {
$newsletter
->withOptions()
->withSegments()
->withChildrenCount();
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION_HISTORY) {
$newsletter
->withSegments()
->withSendingQueue()
->withStatistics();
}

View File

@ -33,12 +33,14 @@ class SendingQueue {
if($newsletter === false) {
return array(
'result' => false,
'errors' => array(__('This newsletter does not exist.'))
'errors' => array(__('This newsletter does not exist'))
);
}
if($newsletter->type === Newsletter::TYPE_WELCOME) {
// set welcome email active
if($newsletter->type === Newsletter::TYPE_WELCOME ||
$newsletter->type === Newsletter::TYPE_NOTIFICATION
) {
// set newsletter status to active
$result = $newsletter->setStatus(Newsletter::STATUS_ACTIVE);
$errors = $result->getErrors();
@ -48,20 +50,16 @@ class SendingQueue {
'errors' => $errors
);
} else {
$message = ($newsletter->type === Newsletter::TYPE_WELCOME) ?
__('Your welcome email has been activated') :
__('Your post notification has been activated');
return array(
'result' => true,
'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')
@ -71,7 +69,7 @@ class SendingQueue {
if(!empty($queue)) {
return array(
'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;
}
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) {
// set newsletter status
$newsletter->setStatus(Newsletter::STATUS_SCHEDULED);
@ -107,7 +93,7 @@ class SendingQueue {
$queue->subscribers = null;
$queue->count_total = $queue->count_to_process = 0;
$message = __('The newsletter has been scheduled.');
$message = __('The newsletter has been scheduled');
} else {
$segments = $newsletter->segments()->findArray();
$segment_ids = array_map(function($segment) {
@ -120,7 +106,7 @@ class SendingQueue {
if(!count($subscribers)) {
return array(
'result' => false,
'errors' => array(__('There are no subscribers.'))
'errors' => array(__('There are no subscribers'))
);
}
$queue->status = null;

View File

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

View File

@ -8,9 +8,9 @@ use MailPoet\Newsletter\Scheduler\Scheduler;
class WP {
static function synchronizeUser($wp_user_id, $old_wp_user_data = false) {
$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)
->findOne();
@ -20,8 +20,14 @@ class WP {
case 'delete_user':
case 'deleted_user':
case 'remove_user_from_blog':
if($subscriber !== false && $subscriber->id()) {
$subscriber->delete();
if($subscriber !== false) {
// 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;
case 'profile_update':
@ -55,7 +61,7 @@ class WP {
// add subscriber to the WP Users segment
SubscriberSegment::subscribeToSegments(
$subscriber,
array($wp_users_segment->id)
array($wp_segment->id)
);
// welcome email
@ -73,7 +79,7 @@ class WP {
static function synchronizeUsers() {
// get wordpress users list
$wp_users_segment = Segment::getWPSegment();
$wp_segment = Segment::getWPSegment();
// fetch all wp users id
$wp_users = \get_users(array(

View File

@ -46,10 +46,10 @@ class Export {
function process() {
try {
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')) {
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(
array(

View File

@ -135,13 +135,13 @@ class MailChimp {
$errorMessage = __('Invalid API Key.');
break;
case 'connection':
$errorMessage = __('Could not connect to your MailChimp account.');
$errorMessage = __('Could not connect to your MailChimp account');
break;
case 'headers':
$errorMessage = __('The selected lists do not have matching columns (headers).');
$errorMessage = __('The selected lists do not have matching columns (headers)');
break;
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;
case 'subscribers':
$errorMessage = __('Did not find any active subscribers.');

View File

@ -20,7 +20,7 @@ class Comment {
static function getSubscriptionField() {
$label = Setting::getValue(
'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">

View File

@ -20,7 +20,7 @@ class Pages {
private $data;
private $subscriber;
function __construct($action, $data) {
function __construct($action, $data = array()) {
$this->action = $action;
$this->data = $data;
$this->subscriber = $this->getSubscriber();
@ -59,12 +59,10 @@ class Pages {
function confirm() {
if($this->subscriber !== false) {
if($this->subscriber->status !== Subscriber::STATUS_SUBSCRIBED) {
$this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$this->subscriber->save();
}
}
}
function unsubscribe() {
if($this->subscriber !== false) {
@ -78,14 +76,19 @@ class Pages {
function setPageTitle($page_title = '') {
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(
($this->isMailPoetPage($post->ID) === false)
($post->post_title !== __('MailPoet Page'))
||
($page_title !== single_post_title('', false))
) {
// when it's a custom page, just return the original page title
return $page_title;
} else {
// when it's our own page, generate page title based on requested action
switch($this->action) {
case 'confirm':
return $this->getConfirmTitle();
@ -97,20 +100,17 @@ class Pages {
return $this->getUnsubscribeTitle();
}
}
return $page_title;
}
function setPageContent($page_content = '[mailpoet_page]') {
global $post;
if(
($this->isPreview() === false)
&&
($this->isMailPoetPage($post->ID) === false)
) {
return $page_content;
// if we're not in preview mode and the subscriber does not exist
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.");
}
if(strpos($page_content, '[mailpoet_page]') !== false) {
$content = '';
switch($this->action) {
@ -124,9 +124,7 @@ class Pages {
$content = $this->getUnsubscribeContent();
break;
}
if(strpos($page_content, '[mailpoet_page]') !== false) {
return str_replace('[mailpoet_page]', $content, $page_content);
return str_replace('[mailpoet_page]', trim($content), $page_content);
} else {
return $page_content;
}
@ -150,32 +148,22 @@ class Pages {
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() {
if($this->isPreview()) {
$title = sprintf(
__("You've subscribed to: %s"),
__("You have subscribed to: %s"),
'demo 1, demo 2'
);
} else if($this->subscriber === false) {
$title = __('Your confirmation link expired, please subscribe again.');
} else {
$segment_names = array_map(function($segment) {
return $segment->name;
}, $this->subscriber->segments()->findMany());
if(empty($segment_names)) {
$title = __("You're now subscribed!");
$title = __("You are now subscribed!");
} else {
$title = sprintf(
__("You've subscribed to: %s"),
__("You have subscribed to: %s"),
join(', ', $segment_names)
);
}

View File

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

View File

@ -73,7 +73,7 @@ class MailerTest extends MailPoetTest {
$mailer = new Mailer();
$this->fail('Mailer did not throw an exception');
} 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);
$this->fail('Mailer did not throw an exception');
} 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);
$this->fail('Mailer did not throw an exception');
} catch(Exception $e) {
expect($e->getMessage())->equals('Mailing method does not exist.');
expect($e->getMessage())->equals('Mailing method does not exist');
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,6 +36,20 @@ class StatisticsFormsTest extends MailPoetTest {
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() {
StatisticsForms::deleteMany();
}

View File

@ -56,7 +56,7 @@ class SubscriberTest extends MailPoetTest {
));
$subscriber->save();
$errors = $subscriber->getErrors();
expect($errors)->contains("Your email address is invalid.");
expect($errors)->contains("Your email address is invalid!");
// pdo error
$subscriber = Subscriber::create();

View File

@ -19,7 +19,9 @@ class NewsletterRendererTest extends MailPoetTest {
),
'id' => 1,
'subject' => 'Some subject',
'preheader' => 'Some preheader'
'preheader' => 'Some preheader',
'type' => 'standard',
'status' => 'active'
);
$this->renderer = new Renderer($this->newsletter);
$this->column_renderer = new ColumnRenderer();

View File

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

View File

@ -59,7 +59,7 @@ class FormsTest extends MailPoetTest {
$router = new Forms();
$response = $router->save(/* missing data */);
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);
expect($response['result'])->true();

View File

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

View File

@ -44,7 +44,7 @@ class SegmentsTest extends MailPoetTest {
$router = new Segments();
$response = $router->save(/* missing data */);
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);
expect($response['result'])->true();

View File

@ -67,7 +67,7 @@ class SubscribersTest extends MailPoetTest {
$response = $router->save(/* missing data */);
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(
'email' => 'john.doe@invalid'
@ -75,7 +75,7 @@ class SubscribersTest extends MailPoetTest {
$response = $router->save($invalid_data);
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() {

View File

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

View File

@ -8,7 +8,7 @@
<h2>
<input
type="text"
placeholder="<%= __('Click here to change the name!') %>"
placeholder="<%= __('Click here to change the name') %>"
id="mailpoet_form_name"
value="<%= form.name %>"
/>
@ -52,7 +52,7 @@
<select
id="mailpoet_form_segments"
name="segments"
data-placeholder="<%= __('Please choose a list') %>"
data-placeholder="<%= __('Please select a list') %>"
multiple
data-parsley-required-message="<%= __('Please select a list.') %>"
required
@ -598,7 +598,7 @@
var name = $(this).siblings('.mailpoet_form_field').attr('wysija_name');
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({
endpoint: 'customFields',

View File

@ -1,5 +1,5 @@
{{#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}}
<p>
<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>
<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>

View File

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

View File

@ -318,15 +318,15 @@
'failedToFetchAvailablePosts': __('Failed to fetch available posts'),
'failedToFetchRenderedPosts': __('Failed to fetch rendered posts'),
'shortcodesWindowTitle': __('Select a shortcode'),
'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.'),
'newsletterPreviewFailed': __('Preview failed. Pleae check console log.'),
'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'),
'newsletterPreviewFailed': __('Preview failed. Pleae check console log'),
'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'),
'templateDescriptionMissing': __('Please add a template description'),
'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'),
'noPostsToDisplay': __('There is no content to display'),
}) %>
@ -1152,7 +1152,7 @@
},
header: {
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: {
block: {
backgroundColor: 'transparent',

View File

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

View File

@ -28,12 +28,12 @@
'searchLabel': __('Search'),
'loadingItems': __('Loading newsletters...'),
'noItemsFound': __('No newsletters found.'),
'selectAllLabel': __('All newsletters on this page are selected.'),
'selectedAllLabel': __('All %d newsletters are selected.'),
'selectAllLink': __('Select all newsletters on all pages.'),
'clearSelection': __('Clear selection.'),
'permanentlyDeleted': __('%d newsletters were permanently deleted.'),
'noItemsFound': __('No newsletters found'),
'selectAllLabel': __('All newsletters on this page are selected'),
'selectedAllLabel': __('All %d newsletters are selected'),
'selectAllLink': __('Select all newsletters on all pages'),
'clearSelection': __('Clear selection'),
'permanentlyDeleted': __('%d newsletters were permanently deleted'),
'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'),
'apply': __('Apply'),
@ -66,23 +66,23 @@
'viewHistory': __('View history'),
'createdOn': __('Created on'),
'lastModifiedOn': __('Last modified on'),
'oneNewsletterTrashed': __('1 newsletter was moved to the trash.'),
'multipleNewslettersTrashed': __('%$1d newsletters were moved to the trash.'),
'oneNewsletterDeleted': __('1 newsletter was permanently deleted.'),
'multipleNewslettersDeleted': __('%$1d newsletters were permanently deleted.'),
'oneNewsletterRestored': __('1 newsletter has been recovered from the trash.'),
'multipleNewslettersRestored': __('%$1d newsletters have been recovered from the trash.'),
'oneNewsletterTrashed': __('1 newsletter was moved to the trash'),
'multipleNewslettersTrashed': __('%$1d newsletters were moved to the trash'),
'oneNewsletterDeleted': __('1 newsletter was permanently deleted'),
'multipleNewslettersDeleted': __('%$1d newsletters were permanently deleted'),
'oneNewsletterRestored': __('1 newsletter has been recovered from the trash'),
'multipleNewslettersRestored': __('%$1d newsletters have been recovered from the trash'),
'trash': __('Trash'),
'edit': __('Edit'),
'duplicate': __('Duplicate'),
'newsletterDuplicated': __('Newsletter "%$1s" has been duplicated.'),
'notSentYet': __('Not sent yet.'),
'newsletterDuplicated': __('Newsletter "%$1s" has been duplicated'),
'notSentYet': __('Not sent yet'),
'scheduledFor': __('Scheduled for'),
'scheduleIt': __('Schedule it'),
'active': __('Active'),
'inactive': __('Not Active'),
'newsletterQueueCompleted': __('Sent to %$1d of %$2d.'),
'sentToXSubscribers': __('Sent to %$1d subscribers.'),
'newsletterQueueCompleted': __('Sent to %$1d of %$2d'),
'sentToXSubscribers': __('Sent to %$1d subscribers'),
'resume': __('Resume'),
'pause': __('Pause'),
'new': __('New'),
@ -92,7 +92,7 @@
'selectJsonFileToUpload': __('Select a .json file to upload'),
'upload': __('Upload'),
'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"'),
'delete': __('Delete'),
'select': __('Select'),
@ -105,14 +105,14 @@
'regularNewsletterTypeDescription': __('Send a newsletter with images, buttons, dividers, and social bookmarks. Or, just send a basic text email.'),
'create': __('Create'),
'welcomeNewsletterTypeTitle': __('Welcome email'),
'welcomeNewsletterTypeDescription': __('Send an email for new users.'),
'welcomeNewsletterTypeDescription': __('Send an email to new users'),
'setUp': __('Set up'),
'postNotificationNewsletterTypeTitle': __('Post notifications'),
'postNotificationsNewsletterTypeDescription': __('Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.'),
'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."),
'activate': __('Activate'),
'sendWelcomeEmailWhen': __('Send welcome email when...'),
'sendWelcomeEmailWhen': __('Send this welcome email when...'),
'daily': __('Once a day at...'),
'weekly': __('Weekly on...'),
@ -144,22 +144,22 @@
'subjectLine': __('Subject line'),
'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'),
'segmentsTip': __('This subscriber segment will be used for this campaign.'),
'segmentsTip': __('This subscriber segment will be used for this campaign'),
'selectSegmentPlaceholder': __('Select a segment'),
'noSegmentsSelectedError': __('Please select a segment.'),
'noSegmentsSelectedError': __('Please select a segment'),
'sender': __('Sender'),
'senderTip': __('Your name and email.'),
'senderNamePlaceholder': __('John Doe'),
'senderAddressPlaceholder': __('john.doe@email.com'),
'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'),
'replyToAddressPlaceholder': __('john.doe@email.com'),
'newsletterUpdated': __('Newsletter was updated 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'),
'saveDraftAndClose': __('Save as draft and close'),
'orSimply': __('or simply'),
@ -211,10 +211,10 @@
'next': __('Next'),
'previous': __('Previous'),
'welcomeEmailActivated': __('Your welcome email is now active.'),
'welcomeEmailActivationFailed': __('Your welcome email could not be activated, check its settings.'),
'postNotificationActivated': __('Your post notification is now active.'),
'postNotificationActivationFailed': __('Your post notification could not be activated, check its settings.'),
'welcomeEmailActivated': __('Your welcome email is now active!'),
'welcomeEmailActivationFailed': __('Your welcome email could not be activated, please check the settings'),
'postNotificationActivated': __('Your post notification is now active!'),
'postNotificationActivationFailed': __('Your post notification could not be activated, check the settings'),
'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'),
@ -229,6 +229,9 @@
'sendNthWeekDay': __('Send every %$1s %$2s of the month at %$3s'),
'sendImmediately': __('Send immediately'),
'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 %>

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<%= __('Bounce email') %>
</label>
<p class="description">
<%= __('Your bounced emails will be sent to this address.') %>
<%= __('Your bounced emails will be sent to this address') %>
</p>
</th>
<td>
@ -21,6 +21,52 @@
</p>
</td>
</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 -->
<tr>
<th scope="row">
@ -28,7 +74,7 @@
<%= __('Open and click tracking') %>
</label>
<p class="description">
<%= __('Some users prefer to not track their subscribers.') %>
<%= __('Some users prefer to not track their subscribers') %>
</p>
</th>
<td>
@ -64,7 +110,7 @@
<%= __('Share anonymous data') %>
</label>
<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
href="http://support.mailpoet.com/knowledgebase/share-your-data/?utm_source=wpadmin&utm_campaign=advanced_settings"
target="_blank"
@ -102,7 +148,7 @@
<th scope="row">
<label><%= __('Reinstall from scratch') %></label>
<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>
</th>
<td>
@ -122,7 +168,7 @@
$(function() {
$('#mailpoet_reinstall').on('click', function() {
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.Ajax.post({

View File

@ -6,7 +6,7 @@
<%= __("Default sender") %>
</label>
<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>
</th>
<td>
@ -47,7 +47,7 @@
<%= __("Email notifications") %>
</label>
<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>
</th>
<td>
@ -88,7 +88,7 @@
<%= __('Subscribe in comments') %>
</label>
<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>
</th>
<td>
@ -112,7 +112,7 @@
<% if(settings.subscribe.on_comment.label) %>
value="<%= settings.subscribe.on_comment.label %>"
<% else %>
value="<%= __('Yes, add me to your mailing list.') %>"
value="<%= __('Yes, add me to your mailing list') %>"
<% endif %>
/>
</p>
@ -148,7 +148,7 @@
<%= __('Subscribe in registration form') %>
</label>
<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>
</th>
<td>
@ -176,7 +176,7 @@
<% if(settings.subscribe.on_register.label) %>
value="<%= settings.subscribe.on_register.label %>"
<% else %>
value="<%= __('Yes, add me to your mailing list.') %>"
value="<%= __('Yes, add me to your mailing list') %>"
<% endif %>
/>
</p>
@ -203,7 +203,7 @@
</div>
<% else %>
<p>
<em><%= __('Registration is disabled on this site.') %></em>
<em><%= __('Registration is disabled on this site') %></em>
</p>
<% endif %>
</td>
@ -215,7 +215,7 @@
<%= __('Manage Subscription page') %>
</label>
<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>
</th>
<td>
@ -270,9 +270,9 @@
<%= __('Unsubscribe page') %>
</label>
<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 />
<%= __('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>
</th>
<td>
@ -307,7 +307,7 @@
<%= __('Archive page shortcode') %>
</label>
<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>
</th>
<td>
@ -343,7 +343,7 @@
<%= __('This shortcode displays the total number of subscribers') %>
</label>
<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>
</th>
<td>

View File

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

View File

@ -55,7 +55,7 @@
</h3>
<p class="mailpoet_description">
<strong><%= __('Currently in closed beta.') %></strong>
<strong><%= __('Currently in Closed Beta') %></strong>
<br />
<%= __('[link]Sign up to our newsletter[/link] to get our latest news on our sending service plus other useful tips and tricks.')
| replace({
@ -83,9 +83,9 @@
<h3><%= __('Your web host / web server') %></h3>
<p class="mailpoet_description">
<strong><%= __('Free, but not recommended.') %></strong>
<strong><%= __('Free, but not recommended') %></strong>
<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>
<div class="mailpoet_status">
@ -105,9 +105,9 @@
<h3><%= __('Third-party') %></h3>
<p class="mailpoet_description">
<strong><%= __('Currently the best solution.') %></strong>
<strong><%= __('Currently the best solution') %></strong>
<br />
<%= __('Send with an external email provider. This is usually not free.') %>
<%= __('Send with an external email provider. This is usually not free') %>
</p>
<div class="mailpoet_status">
@ -245,9 +245,9 @@
</p>
<br />
<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 />
<%= __('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>
</div>
</td>
@ -546,7 +546,7 @@
<%= __('Authentication') %>
</label>
<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>
</th>
<td>
@ -588,12 +588,12 @@
<%= __('SPF Signature (Highly recommended!)') %>
</label>
<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>
</th>
<td>
<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>
</td>
</tr>
@ -631,7 +631,7 @@
<a
href="javascript:;"
class="mailpoet_mta_setup_cancel"
><%= __('or cancel.') %></a>
><%= __('or Cancel') %></a>
</p>
</th>
<td></td>
@ -688,9 +688,9 @@
data: {
mailer: mailer,
newsletter: {
subject: "<%= __('This is a sending method test.') %>",
subject: "<%= __('This is a Sending Method Test') %>",
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: {
@ -709,7 +709,7 @@
} else {
if (response.errors) {
MailPoet.Notice.error(
"<%= __('The email could not be sent.') %> " + response.errors,
"<%= __('The email could not be sent: ') %> " + response.errors,
{ scroll: true }
);
}

View File

@ -151,7 +151,7 @@
<%= __('Confirmation page') %>
</label>
<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>
</th>
<td>
@ -192,9 +192,9 @@
var result = false;
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 {
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(result === true) {

View File

@ -8,7 +8,7 @@
<!-- 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 }}')
| raw
%>
@ -17,7 +17,7 @@
<br /><br />
<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 }}')
| raw
%>

View File

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

View File

@ -32,26 +32,26 @@
<% block translations %>
<%= localize({
'noMailChimpLists': __('No active lists found.'),
'noMailChimpLists': __('No active lists found'),
'serverError': __('Server error:'),
'select': __('Select'),
'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}),
'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]'),
'importNoticeSkipped': __('%1$s records had issues and were skipped.'),
'importNoticeInvalid': __('%1$s emails are not valid : %2$s.'),
'importNoticeDuplicate': __('%1$s emails appear more than once in your file : %2$s.'),
'hideDetails': __('Hide details.'),
'showDetails': __('Show more details.'),
'segmentSelectionRequired': __('Please select at least one list.'),
'importNoticeSkipped': __('%1$s records had issues and were skipped'),
'importNoticeInvalid': __('%1$s emails are not valid : %2$s'),
'importNoticeDuplicate': __('%1$s emails appear more than once in your file : %2$s'),
'hideDetails': __('Hide details'),
'showDetails': __('Show more details'),
'segmentSelectionRequired': __('Please select at least one list'),
'addNewList': __('Add new list'),
'addNewColumuserColumnsn': __('Add new list'),
'userColumns': __('User columns'),
'selectedValueAlreadyMatched': __('The selected value is already matched to another column.'),
'confirmCorrespondingColumn': __('Confirm that this column corresponds to the selected field.'),
'columnContainInvalidElement': __('One of the columns contains an invalid email. Please fix it before continuing.'),
'selectedValueAlreadyMatched': __('The selected value is already matched to another column'),
'confirmCorrespondingColumn': __('Confirm that this column corresponds to the selected field'),
'columnContainInvalidElement': __('One of the columns contains an invalid email. Please fix it before continuing'),
'january': __('January'),
'february': __('February'),
'march': __('March'),
@ -64,17 +64,17 @@
'october': __('October'),
'november': __('November'),
'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'),
'verifyDateMatch': __('Verify that the date in blue matches the original date'),
'pm': __('PM'),
'am': __('AM'),
'dateMatchError': __('Error matching date.'),
'columnContainsInvalidDate': __('One of the columns contains an invalid date. Please fix it before continuing.'),
'dateMatchError': __('Error matching date'),
'columnContainsInvalidDate': __('One of the columns contains an invalid date. Please fix it before continuing'),
'listCreateError': __('Error adding a new segment:'),
'columnContainsInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing.'),
'customFieldCreateError': __('Custom field could not be created.'),
'columnContainsInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing'),
'customFieldCreateError': __('Custom field could not be created'),
'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 %>

View File

@ -113,7 +113,7 @@
<%= ('If the answer is "no", consider yourself a spammer.') %>
<br/>
<%=
__('[link]Read more on support.mailpoet.com[/link].')
__('[link]Read more on support.mailpoet.com[/link]')
|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>'

View File

@ -34,12 +34,12 @@
<th scope="row">
<label>
<%= __('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>
</th>
<td>
<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>
</tr>
<tr class="mailpoet_no_segments mailpoet_hidden">
@ -134,7 +134,7 @@
<input id="new_segment_name" type="text" name="name"/>
</p>
<p class="mailpoet_validation_error" data-error="segment_name_required">
<%= __('Please specify a name.') %>
<%= __('Please specify a name') %>
</p>
<p class="mailpoet_validation_error" data-error="segment_name_not_unique">
<%= __('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 }}"/>
</p>
<p class="mailpoet_validation_error" data-error="name_required">
<%= __('Please to specify a name.') %>
<%= __('Please specify a name') %>
</p>
<p class="mailpoet_validation_error" data-error="name_not_unique">
<%= __('This name is already taken.') %>
<%= __('This name is already taken') %>
</p>
<hr/>

View File

@ -25,10 +25,10 @@
<p>{{{updated}}}</p>
{{/if}}
{{#if no_action}}
<p><%= __('No new subscribers were found/added.') %></p>
<p><%= __('No new subscribers were found/added') %></p>
{{/if}}
{{#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}}
</script>
</div>

View File

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

View File

@ -79,8 +79,8 @@
<h2>
<%= __('Welcome to MailPoet') %> <%= settings.version %> <%=__('BETA') %>
</h2>
<p>Youre one our extra special testers! We salute you.</p>
<h3 class="changes">Big changes...watch the video!</h3>
<p>Youre one of our extra special testers! We salute you.</p>
<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
ait=0" width="710" height="399" frameborder="0" webkitallowfullscreen
mozallowfullscreen allowfullscreen></iframe>