Compare commits

...

30 Commits

Author SHA1 Message Date
cb3d49f200 Bump up release version to 0.0.24 2016-04-15 21:27:07 +03:00
c0da428c27 Merge pull request #425 from mailpoet/subscription_pages
Subscription pages
2016-04-15 19:07:20 +02:00
435cd9b777 fixed unsubscribe action + updated public api decodeData to prevent warning 2016-04-15 18:59:20 +02:00
bfde34eb8d - Rebases master
- Updates PubliAPI's logic dealing with request data
2016-04-15 12:10:56 -04:00
a4c1b24c35 fixed unit tests 2016-04-15 11:56:33 -04:00
2cbd2d54f3 Subscription pages 2016-04-15 11:56:33 -04:00
378f6d803a Merge pull request #422 from mailpoet/link_tracking
Implements links tracking
2016-04-15 18:47:14 +03:00
af4d29ebe6 - Updates codes based on Taut's comments 2016-04-15 11:36:57 -04:00
599661e028 - Updates code based on Taut's comments 2016-04-14 20:35:56 -04:00
84294b7ee6 - Adds subsription tracking option to Settings->Advanced
- Updates sending queue worker to not replace links if tracking is
  disabled
Closes #417
2016-04-14 19:37:27 -04:00
a3e6eb5bba - Implementsi tracking clicks & redirecting URLs 2016-04-14 19:37:27 -04:00
67359980e9 - Renames statistics table to the new "statistics_entity" format
- Adds new clicks statistics model
2016-04-14 19:37:27 -04:00
f1b955d74a - Implements link extraction, processing, replacement and saving 2016-04-14 19:37:27 -04:00
809be415c5 Merge pull request #423 from mailpoet/text_block_fix
Fixes 'Can't use method return value in write context' error
2016-04-14 12:32:31 +03:00
457a4d1bba - Fixes 'Can't use method return value in write context' error 2016-04-13 22:46:59 -04:00
1e16912763 Merge pull request #420 from mailpoet/sending_queue_router_fix
Prevents newsletters from being queued when mailer is not configured
2016-04-12 16:02:18 +03:00
e2c9971c99 - Prevents newsletters from being queued when mailer is not configured
Closes #342
2016-04-12 08:58:21 -04:00
c1d31ca400 Merge pull request #414 from mailpoet/sending_last_step
Sending options in last newsletter creation step
2016-04-12 13:14:34 +02:00
a011c3aade Fix standard newsletters to not send when segments are missing 2016-04-12 14:11:26 +03:00
e35e97cdbf Fix Select2 integration in Form component 2016-04-12 14:04:01 +03:00
49a59d35a1 Add number of subscribers to segment selection 2016-04-12 14:04:01 +03:00
9a46640c15 Allow selecting only published segments, fix "Save" translation 2016-04-12 14:04:01 +03:00
678a0b3835 Add form loading state, remove errors and params 2016-04-12 14:04:01 +03:00
0c008325c4 - Fix unmounting select2 component
- Fix subjects of welcome and notification newsletters
- Remove debugging statements
2016-04-12 14:04:01 +03:00
ad5441487b Show different fields based on newsletter type, fix saving across
multiple endpoints
2016-04-12 14:04:01 +03:00
104620a40a Add react component support to Forms, make newsletter scheduling
components reusable
2016-04-12 14:04:01 +03:00
c1a3ba67f5 Merge pull request #418 from mailpoet/fix_initializer
fix cron issue
2016-04-11 09:41:08 -04:00
c42bbf3dc4 fix cron issue 2016-04-11 15:35:47 +02:00
7d34274fbf Merge pull request #416 from mailpoet/rendering_engine_update
Sets text-align to left when it's not center|justify|right
2016-04-11 13:51:42 +03:00
f930b3303b - Sets text-align to left when it's not center|justify|right 2016-04-08 20:37:00 -04:00
51 changed files with 1612 additions and 760 deletions

View File

@ -61,6 +61,10 @@ function(
case 'date':
field = (<FormFieldDate {...data} />);
break;
case 'reactComponent':
field = (<data.field.component {...data} />);
break;
}
if(inline === true) {
@ -121,4 +125,4 @@ function(
});
return FormField;
});
});

View File

@ -14,10 +14,12 @@ function(
return {
items: [],
initialized: false
}
};
},
componentWillMount: function() {
this.loadCachedItems();
},
componentDidMount: function() {
this.loadCachedItems();
this.setupSelect2();
},
componentDidUpdate: function(prevProps, prevState) {
@ -30,6 +32,9 @@ function(
.trigger('change');
}
},
componentWillUnmount: function() {
jQuery('#'+this.refs.select.id).select2('destroy');
},
setupSelect2: function() {
if(
!this.props.field.multiple
@ -67,11 +72,6 @@ function(
select2.on('change', this.handleChange);
select2.select2(
'val',
this.getSelectedValues()
);
this.setState({ initialized: true });
},
getSelectedValues: function() {
@ -161,4 +161,4 @@ function(
});
return Selection;
});
});

View File

@ -30,4 +30,4 @@ function(
});
return FormFieldText;
});
});

View File

@ -17,6 +17,11 @@ define(
mixins: [
Router.History
],
getDefaultProps: function() {
return {
params: {},
};
},
getInitialState: function() {
return {
loading: false,
@ -24,6 +29,12 @@ define(
item: {}
};
},
getValues: function() {
return this.props.item ? this.props.item : this.state.item;
},
getErrors: function() {
return this.props.errors ? this.props.errors : this.state.errors;
},
componentDidMount: function() {
if(this.props.params.id !== undefined) {
if(this.isMounted()) {
@ -37,7 +48,9 @@ define(
loading: false,
item: {}
});
this.refs.form.reset();
if (props.item === undefined) {
this.refs.form.reset();
}
} else {
this.loadItem(props.params.id);
}
@ -123,19 +136,23 @@ define(
}.bind(this));
},
handleValueChange: function(e) {
var item = this.state.item,
field = e.target.name;
if (this.props.onChange) {
return this.props.onChange(e);
} else {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
this.setState({
item: item
});
return true;
}
},
render: function() {
if(this.state.errors !== undefined) {
var errors = this.state.errors.map(function(error, index) {
if(this.getErrors() !== undefined) {
var errors = this.getErrors().map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
@ -146,14 +163,14 @@ define(
var formClasses = classNames(
'mailpoet_form',
{ 'mailpoet_form_loading': this.state.loading }
{ 'mailpoet_form_loading': this.state.loading || this.props.loading }
);
var fields = this.props.fields.map(function(field, i) {
return (
<FormField
field={ field }
item={ this.state.item }
item={ this.getValues() }
onValueChange={ this.handleValueChange }
key={ 'field-'+i } />
);

View File

@ -6,8 +6,8 @@ import NewsletterTypes from 'newsletters/types.jsx'
import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import NewsletterWelcome from 'newsletters/types/welcome/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })

View File

@ -2,137 +2,109 @@ define(
[
'react',
'react-router',
'underscore',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
'newsletters/send/standard.jsx',
'newsletters/send/notification.jsx',
'newsletters/send/welcome.jsx',
'newsletters/breadcrumb.jsx'
],
function(
React,
Router,
_,
MailPoet,
Form,
Selection,
StandardNewsletterFields,
NotificationNewsletterFields,
WelcomeNewsletterFields,
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
var messages = {
onUpdate: function() {
MailPoet.Notice.success(MailPoet.I18n.t('newsletterUpdated'));
},
onCreate: function() {
MailPoet.Notice.success(MailPoet.I18n.t('newsletterAdded'));
}
};
var NewsletterSend = React.createClass({
mixins: [
Router.History
],
componentDidMount: function() {
jQuery('#mailpoet_newsletter').parsley();
getInitialState: function() {
return {
fields: [],
item: {},
loading: false,
};
},
getFieldsByNewsletter: function(newsletter) {
switch(newsletter.type) {
case 'notification': return NotificationNewsletterFields;
case 'welcome': return WelcomeNewsletterFields;
default: return StandardNewsletterFields;
}
},
isAutomatedNewsletter: function() {
return this.state.item.type !== 'standard';
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
handleSend: function() {
componentDidMount: function() {
if(this.isMounted()) {
this.loadItem(this.props.params.id);
}
jQuery('#mailpoet_newsletter').parsley();
},
componentWillReceiveProps: function(props) {
this.loadItem(props.params.id);
},
loadItem: function(id) {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'get',
data: id
}).done((response) => {
if(response === false) {
this.setState({
loading: false,
item: {},
}, function() {
this.history.pushState(null, '/new');
}.bind(this));
} else {
this.setState({
loading: false,
item: response,
fields: this.getFieldsByNewsletter(response),
});
}
});
},
handleSend: function(e) {
e.preventDefault();
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val(),
sender: {
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
},
reply_to: {
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
}
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).then((response) => {
if (response.result === true) {
return MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: _.extend({}, this.state.item, {
newsletter_id: this.props.params.id,
}),
});
} else {
return response;
}
}).done(function(response) {
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
@ -147,10 +119,44 @@ define(
);
}
}
}.bind(this));
});
}
return false;
},
handleSave: function(e) {
e.preventDefault();
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
}
}
});
},
handleFormChange: function(e) {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
},
render: function() {
return (
<div>
@ -160,18 +166,21 @@ define(
<Form
id="mailpoet_newsletter"
endpoint="newsletters"
fields={ fields }
params={ this.props.params }
messages={ messages }
isValid={ this.isValid }
fields={ this.state.fields }
item={ this.state.item }
loading={ this.state.loading }
onChange={this.handleFormChange}
onSubmit={this.handleSave}
>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleSend }
value={MailPoet.I18n.t('send')} />
value={
this.isAutomatedNewsletter()
? MailPoet.I18n.t('activate')
: MailPoet.I18n.t('send')} />
&nbsp;
<input
className="button button-secondary"

View File

@ -0,0 +1,104 @@
define(
[
'mailpoet',
'newsletters/types/notification/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('postNotificationSubjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('selectPeriodicity'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
return fields;
}
);

View File

@ -0,0 +1,96 @@
define(
[
'mailpoet'
],
function(
MailPoet
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
return fields;
}
);

View File

@ -0,0 +1,81 @@
define(
[
'mailpoet',
'newsletters/types/welcome/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('sendWelcomeEmailWhen'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
return fields;
}
);

View File

@ -0,0 +1,96 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'newsletters/types/notification/scheduling.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Scheduling,
Breadcrumb
) {
var field = {
name: 'options',
label: 'Periodicity',
type: 'reactComponent',
component: Scheduling,
};
var NewsletterNotification = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
options: {
intervalType: 'daily',
timeOfDay: 0,
weekDay: 1,
monthDay: 0,
nthWeekDay: 1,
}
};
},
handleValueChange: function(event) {
var state = this.state;
state[event.target.name] = event.target.value;
this.setState(state);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: _.extend({}, this.state, {
type: 'notification',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
return (
<div>
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectPeriodicity')}</h3>
<Scheduling
item={this.state}
field={field}
onValueChange={this.handleValueChange} />
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterNotification;
}
);

View File

@ -4,26 +4,18 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
'form/fields/select.jsx'
],
function(
_,
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
Select
) {
var intervalField = {
name: 'interval',
name: 'intervalType',
values: {
'daily': MailPoet.I18n.t('daily'),
'weekly': MailPoet.I18n.t('weekly'),
@ -96,137 +88,113 @@ define(
},
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 1,
monthDay: 0,
nthWeekDay: 1,
};
var NotificationScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleIntervalChange: function(event) {
this.setState({
intervalType: event.target.value,
});
return this.handleValueChange(
'intervalType',
event.target.value
);
},
handleTimeOfDayChange: function(event) {
this.setState({
timeOfDay: event.target.value,
});
return this.handleValueChange(
'timeOfDay',
event.target.value
);
},
handleWeekDayChange: function(event) {
this.setState({
weekDay: event.target.value,
});
return this.handleValueChange(
'weekDay',
event.target.value
);
},
handleMonthDayChange: function(event) {
this.setState({
monthDay: event.target.value,
});
return this.handleValueChange(
'monthDay',
event.target.value
);
},
handleNthWeekDayChange: function(event) {
this.setState({
nthWeekDay: event.target.value,
});
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'notification',
options: this.state,
},
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
return this.handleValueChange(
'nthWeekDay',
event.target.value
);
},
render: function() {
var timeOfDaySelection,
var value = this._getCurrentValue(),
timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (this.state.intervalType !== 'immediately') {
if (value.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (this.state.intervalType === 'weekly'
|| this.state.intervalType === 'nthWeekDay') {
if (value.intervalType === 'weekly'
|| value.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleWeekDayChange} />
);
}
if (this.state.intervalType === 'monthly') {
if (value.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleMonthDayChange} />
);
}
if (this.state.intervalType === 'nthWeekDay') {
if (value.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<Select
field={intervalField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterWelcome;
return NotificationScheduling;
}
);

View File

@ -3,16 +3,12 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
'newsletters/breadcrumb.jsx'
],
function(
React,
Router,
MailPoet,
Form,
Selection,
Breadcrumb
) {
@ -32,7 +28,6 @@ define(
type: 'standard',
}
}).done(function(response) {
console.log(response);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {

View File

@ -4,9 +4,7 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
@ -15,9 +13,7 @@ define(
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
@ -36,7 +32,11 @@ define(
var availableSegmentValues = _.object(_.map(
availableSegments,
function(segment) {
return [segment.id, segment.name];
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1d)'.replace('%$1d', segment.subscribers);
}
return [segment.id, name];
}
));
var segmentField = {
@ -64,43 +64,51 @@ define(
}
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
event: 'segment',
segment: 1,
role: 'subscriber',
afterTimeNumber: 1,
afterTimeType: 'immediate',
};
var WelcomeScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleEventChange: function(event) {
this.setState({
event: event.target.value,
});
return this.handleValueChange(
'event',
event.target.value
);
},
handleSegmentChange: function(event) {
this.setState({
segment: event.target.value,
});
return this.handleValueChange(
'segment',
event.target.value
);
},
handleRoleChange: function(event) {
this.setState({
role: event.target.value,
});
return this.handleValueChange(
'role',
event.target.value
);
},
handleAfterTimeNumberChange: function(event) {
this.setState({
afterTimeNumber: event.target.value,
});
return this.handleValueChange(
'afterTimeNumber',
event.target.value
);
},
handleAfterTimeTypeChange: function(event) {
this.setState({
afterTimeType: event.target.value,
});
return this.handleValueChange(
'afterTimeType',
event.target.value
);
},
handleNext: function() {
MailPoet.Ajax.post({
@ -126,40 +134,38 @@ define(
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
var roleSegmentSelection, timeNumber;
if (this.state.event === 'user') {
var value = this._getCurrentValue(),
roleSegmentSelection, timeNumber;
if (value.event === 'user') {
roleSegmentSelection = (
<Select
field={roleField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleRoleChange} />
);
} else {
roleSegmentSelection = (
<Select
field={segmentField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleSegmentChange} />
);
}
if (this.state.afterTimeType !== 'immediate') {
if (value.afterTimeType !== 'immediate') {
timeNumber = (
<Text
field={afterTimeNumberField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeNumberChange} />
);
}
return (
<div>
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
<Select
field={events}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleEventChange} />
{roleSegmentSelection}
@ -168,21 +174,14 @@ define(
<Select
field={afterTimeTypeField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeTypeChange}/>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterWelcome;
return WelcomeScheduling;
}
);

View File

@ -0,0 +1,96 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'newsletters/types/welcome/scheduling.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Scheduling,
Breadcrumb
) {
var field = {
name: 'options',
label: 'Event',
type: 'reactComponent',
component: Scheduling,
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
options: {
event: 'segment',
segment: 1,
role: 'subscriber',
afterTimeNumber: 1,
afterTimeType: 'immediate',
}
};
},
handleValueChange: function(event) {
var state = this.state;
state[event.target.name] = event.target.value;
this.setState(state);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: _.extend({}, this.state, {
type: 'welcome',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
return (
<div>
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
<Scheduling
item={this.state}
field={field}
onValueChange={this.handleValueChange} />
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@ -278,33 +278,18 @@ const SubscriberList = React.createClass({
if (subscriber.subscriptions.length > 0) {
let subscribed_segments = [];
let unsubscribed_segments = [];
subscriber.subscriptions.map((subscription) => {
const segment = this.getSegmentFromId(subscription.segment_id);
if(segment === false) return;
if (subscription.status === 'subscribed') {
subscribed_segments.push(segment.name);
} else if (subscription.status === 'unsubscribed') {
unsubscribed_segments.push(segment.name);
}
});
segments = (
<span>
<span className="mailpoet_segments_subscribed">
{ subscribed_segments.join(', ') }
{
(
subscribed_segments.length > 0
&& unsubscribed_segments.length > 0
) ? ' / ' : ''
}
</span>
<span
className="mailpoet_segments_unsubscribed"
title={MailPoet.I18n.t('listsToWhichSubscriberWasSubscribed')}
>
{ unsubscribed_segments.join(', ') }
</span>
</span>
);

View File

@ -13,6 +13,7 @@ class Hooks {
$this->setupWPUsers();
$this->setupImageSize();
$this->setupListing();
$this->setupManageSubscription();
}
function setupSubscribe() {
@ -139,6 +140,18 @@ class Hooks {
);
}
function setupManageSubscription() {
// handle subscription form submission
add_action(
'admin_post_mailpoet_subscription_update',
'\MailPoet\Subscription\Manage::onSave'
);
add_action(
'admin_post_nopriv_mailpoet_subscription_update',
'\MailPoet\Subscription\Manage::onSave'
);
}
function setScreenOption($status, $option, $value) {
if(preg_match('/^mailpoet_(.*)_per_page$/', $option)) {
return $value;

View File

@ -39,7 +39,6 @@ class Initializer {
$this->setupShortcodes();
$this->setupHooks();
$this->setupImages();
$this->setupPublicAPI();
$this->runQueueSupervisor();
} catch(\Exception $e) {
// if anything goes wrong during init
@ -50,8 +49,8 @@ class Initializer {
function onInit() {
$this->setupRouter();
$this->setupPublicAPI();
$this->setupPages();
$this->runQueueSupervisor();
}
function setupDB() {
@ -69,35 +68,39 @@ class Initializer {
'SET TIME_ZONE = "' . Env::$db_timezone_offset. '"'
));
$subscribers = Env::$db_prefix . 'subscribers';
$settings = Env::$db_prefix . 'settings';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields';
$subscribers = Env::$db_prefix . 'subscribers';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletter_statistics = Env::$db_prefix . 'newsletter_statistics';
$newsletter_links = Env::$db_prefix . 'newsletter_links';
$statistics_newsletters = Env::$db_prefix . 'statistics_newsletters';
$statistics_clicks = Env::$db_prefix . 'statistics_clicks';
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
define('MP_STATISTICS_NEWSLETTERS_TABLE', $statistics_newsletters);
define('MP_STATISTICS_CLICKS_TABLE', $statistics_clicks);
}
function runMigrator() {
@ -153,9 +156,6 @@ class Initializer {
function setupPages() {
$pages = new \MailPoet\Settings\Pages();
$pages->init();
$subscription_pages = new \MailPoet\Subscription\Pages();
$subscription_pages->init();
}
function setupShortcodes() {
@ -185,4 +185,4 @@ class Initializer {
function setupImages() {
add_image_size('mailpoet_newsletter_max', 1320);
}
}
}

View File

@ -364,7 +364,7 @@ class Menu {
$data = array();
$data['segments'] = Segment::findArray();
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role');

View File

@ -10,20 +10,22 @@ class Migrator {
$this->prefix = Env::$db_prefix;
$this->charset = Env::$db_charset;
$this->models = array(
'subscribers',
'segments',
'settings',
'custom_fields',
'sending_queues',
'subscribers',
'subscriber_segment',
'subscriber_custom_field',
'newsletters',
'newsletter_templates',
'segments',
'subscriber_segment',
'newsletter_segment',
'custom_fields',
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'sending_queues',
'newsletter_statistics',
'forms'
'newsletter_segment',
'newsletter_links',
'forms',
'statistics_newsletters',
'statistics_clicks',
);
}
@ -49,6 +51,72 @@ class Migrator {
array_map($drop_table, $this->models);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscribers() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@ -66,15 +134,30 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -113,76 +196,6 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_option_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@ -210,37 +223,28 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
function newsletter_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_statistics() {
function newsletter_links() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'url varchar(255) NOT NULL,',
'hash varchar(20) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -261,6 +265,34 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_newsletters() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_clicks() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'link_id mediumint(9) NOT NULL,',
'count mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
private function sqlify($model, $attributes) {
$table = $this->prefix . $model;

View File

@ -74,9 +74,14 @@ class Populator {
$mailpoet_page_id = (int)$page->ID;
}
Setting::setValue('subscription.unsubscribe_page', $mailpoet_page_id);
Setting::setValue('subscription.manage_page', $mailpoet_page_id);
Setting::setValue('subscription.confirmation_page', $mailpoet_page_id);
$subscription = Setting::getValue('subscription.pages', array());
if(empty($subscription)) {
Setting::setValue('subscription.pages', array(
'unsubscribe' => $mailpoet_page_id,
'manage' => $mailpoet_page_id,
'confirmation' => $mailpoet_page_id
));
}
}
private function createDefaultSettings() {

View File

@ -2,6 +2,8 @@
namespace MailPoet\Config;
use MailPoet\Cron\Daemon;
use MailPoet\Subscription;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -21,7 +23,9 @@ class PublicAPI {
$this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->data = isset($_GET['data']) ? $_GET['data'] : false;
$this->data = isset($_GET['data']) ?
$_GET['data'] :
false;
}
function init() {
@ -31,12 +35,31 @@ class PublicAPI {
function queue() {
try {
$queue = new Daemon($this->data);
$queue = new Daemon($this->_decodeData());
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
}
}
function subscription() {
try {
$subscription = new Subscription\Pages($this->action, $this->_decodeData());
$this->_checkAndCallMethod($subscription, $this->action);
} catch(\Exception $e) {
}
}
function track() {
try {
if($this->action === 'click') {
$track_class = new Clicks($this->data);
}
if(!isset($track_class)) return;
$track_class->track();
} catch(\Exception $e) {
}
}
private function _checkAndCallMethod($class, $method, $terminate_request = false) {
if(!method_exists($class, $method)) {
if(!$terminate_request) return;
@ -50,4 +73,12 @@ class PublicAPI {
)
);
}
private function _decodeData() {
if($this->data !== false) {
return unserialize(base64_decode($this->data));
} else {
return array();
}
}
}

View File

@ -3,6 +3,7 @@ namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Subscription;
class Shortcodes {
function __construct() {

View File

@ -16,12 +16,12 @@ class Daemon {
private $timer;
function __construct($data) {
if (!$data) $this->abortWithError(__('Invalid or missing cron data.'));
if(empty($data)) $this->abortWithError(__('Invalid or missing cron data.'));
set_time_limit(0);
ignore_user_abort();
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
$this->data = unserialize(base64_decode($data));
$this->data = $data;
$this->timer = microtime(true);
}

View File

@ -4,9 +4,11 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsNewsletters;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Helpers;
@ -17,6 +19,7 @@ class SendingQueue {
public $mta_config;
public $mta_log;
public $processing_method;
public $divider = '***MailPoet***';
private $timer;
const batch_size = 50;
@ -38,7 +41,7 @@ class SendingQueue {
continue;
}
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->getNewsletterBodyAndSubject($queue, $newsletter);
$newsletter['body'] = $this->getOrRenderNewsletterBody($queue, $newsletter);
$queue->subscribers = (object) unserialize($queue->subscribers);
if(!isset($queue->subscribers->processed)) {
$queue->subscribers->processed = array();
@ -67,11 +70,25 @@ class SendingQueue {
}
}
function getNewsletterBodyAndSubject($queue, $newsletter) {
function getOrRenderNewsletterBody($queue, $newsletter) {
// check if newsletter has been rendered, in which case return its contents
// or render & and for future use
// or render and save for future reuse
if($queue->newsletter_rendered_body === null) {
$newsletter['body'] = $this->renderNewsletter($newsletter);
// render newsletter
$rendered_newsletter = $this->renderNewsletter($newsletter);
if((boolean) Setting::getValue('tracking.enabled')) {
// extract and replace links
$processed_newsletter = $this->processLinks(
$this->joinObject($rendered_newsletter),
$newsletter['id'],
$queue->id
);
list($newsletter['body']['html'], $newsletter['body']['text']) =
$this->splitObject($processed_newsletter);
}
else {
$newsletter['body'] = $rendered_newsletter;
}
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
$queue->newsletter_rendered_body_hash = md5($newsletter['body']['text']);
$queue->save();
@ -84,7 +101,7 @@ class SendingQueue {
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$processed_newsletters[] =
$this->processNewsletter($newsletter, $subscriber);
$this->processNewsletter($newsletter, $subscriber, $queue);
$transformed_subscribers[] =
$mailer->transformSubscriber($subscriber);
}
@ -125,8 +142,8 @@ class SendingQueue {
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber);
if (!$queue->newsletter_rendered_subject) {
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
$transformed_subscriber = $mailer->transformSubscriber($subscriber);
@ -154,7 +171,7 @@ class SendingQueue {
}
function updateNewsletterStatistics($data) {
return NewsletterStatistics::createMultiple($data);
return StatisticsNewsletters::createMultiple($data);
}
function renderNewsletter($newsletter) {
@ -162,20 +179,61 @@ class SendingQueue {
return $renderer->render();
}
function processNewsletter($newsletter, $subscriber = false) {
$divider = '***MailPoet***';
$data_for_shortcodes =
array_merge(array($newsletter['subject']), $newsletter['body']);
$body = implode($divider, $data_for_shortcodes);
function processLinks($text, $newsletter_id, $queue_id) {
$links = new Links();
list($text, $processed_links) = $links->replace($text);
foreach($processed_links as $link) {
// save extracted and processed links
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
$newsletter_link->queue_id = $queue_id;
$newsletter_link->hash = $link['hash'];
$newsletter_link->url = $link['url'];
$newsletter_link->save();
}
return $text;
}
function processNewsletter($newsletter, $subscriber = false, $queue) {
$data_for_shortcodes = array(
$newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
);
$processed_newsletter = $this->replaceShortcodes(
$newsletter,
$subscriber,
$this->joinObject($data_for_shortcodes)
);
if((boolean) Setting::getValue('tracking.enabled')) {
$processed_newsletter = $this->replaceLinks(
$newsletter['id'],
$subscriber['id'],
$queue->id,
$processed_newsletter
);
}
list($newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = $this->splitObject($processed_newsletter);
return $newsletter;
}
function replaceLinks($newsletter_id, $subscriber_id, $queue_id, $body) {
return str_replace(
'[mailpoet_data]',
sprintf('%s-%s-%s', $newsletter_id, $subscriber_id, $queue_id),
$body
);
}
function replaceShortcodes($newsletter, $subscriber, $body) {
$shortcodes = new Shortcodes(
$newsletter,
$subscriber
);
list($newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = explode($divider, $shortcodes->replace($body));
return $newsletter;
return $shortcodes->replace($body);
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
@ -284,4 +342,12 @@ class SendingQueue {
}
return;
}
private function joinObject($object = array()) {
return implode($this->divider, $object);
}
private function splitObject($object = array()) {
return explode($this->divider, $object);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class NewsletterLink extends Model {
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
function __construct() {
parent::__construct();
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class StatisticsClicks extends Model {
public static $_table = MP_STATISTICS_CLICKS_TABLE;
function __construct() {
parent::__construct();
}
}

View File

@ -3,8 +3,8 @@ namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class NewsletterStatistics extends Model {
public static $_table = MP_NEWSLETTER_STATISTICS_TABLE;
class StatisticsNewsletters extends Model {
public static $_table = MP_STATISTICS_NEWSLETTERS_TABLE;
function __construct() {
parent::__construct();
@ -12,7 +12,7 @@ class NewsletterStatistics extends Model {
static function createMultiple($data) {
return self::rawExecute(
'INSERT INTO `' . NewsletterStatistics::$_table . '` ' .
'INSERT INTO `' . self::$_table . '` ' .
'(newsletter_id, subscriber_id, queue_id) ' .
'VALUES ' . rtrim(
str_repeat('(?,?,?), ', count($data)/3),

View File

@ -48,30 +48,6 @@ class Subscriber extends Model {
return parent::delete();
}
function addToSegments(array $segment_ids = array()) {
$wp_users_segment = Segment::getWPUsers();
if($wp_users_segment !== false) {
// delete all relations to segments except WP users
SubscriberSegment::where('subscriber_id', $this->id)
->whereNotEqual('segment_id', $wp_users_segment->id)
->deleteMany();
} else {
// delete all relations to segments
SubscriberSegment::where('subscriber_id', $this->id)->deleteMany();
}
if(!empty($segment_ids)) {
$segments = Segment::whereIn('id', $segment_ids)->findMany();
foreach($segments as $segment) {
$association = SubscriberSegment::create();
$association->subscriber_id = $this->id;
$association->segment_id = $segment->id;
$association->save();
}
}
}
function sendConfirmationEmail() {
if($this->status === self::STATUS_UNCONFIRMED) {
$signup_confirmation = Setting::getValue('signup_confirmation');
@ -168,14 +144,18 @@ class Subscriber extends Model {
$subscriber->setExpr('deleted_at', 'NULL');
}
// auto subscribe when signup confirmation is disabled
if($signup_confirmation_enabled === false) {
$subscriber->set('status', self::STATUS_SUBSCRIBED);
if($subscriber->status !== self::STATUS_SUBSCRIBED) {
// auto subscribe when signup confirmation is disabled
if($signup_confirmation_enabled === true) {
$subscriber->set('status', self::STATUS_UNCONFIRMED);
} else {
$subscriber->set('status', self::STATUS_SUBSCRIBED);
}
}
if($subscriber->save()) {
// link subscriber to segments
$subscriber->addToSegments($segment_ids);
SubscriberSegment::addSubscriptions($subscriber, $segment_ids);
// signup confirmation
if($subscriber->status !== self::STATUS_SUBSCRIBED) {
@ -409,8 +389,8 @@ class Subscriber extends Model {
&&
($new_status === self::STATUS_UNSUBSCRIBED)
) {
// make sure we unsubscribe the user from all lists
SubscriberSegment::setSubscriptions($subscriber, array());
// make sure we unsubscribe the user from all segments
SubscriberSegment::removeSubscriptions($subscriber);
} else {
if($segment_ids !== false) {
SubscriberSegment::setSubscriptions($subscriber, $segment_ids);

View File

@ -16,27 +16,55 @@ class SubscriberSegment extends Model {
return $this->has_one(__NAMESPACE__.'\Subscriber', 'id', 'subscriber_id');
}
static function setSubscriptions($subscriber, $segment_ids = array()) {
static function removeSubscriptions($subscriber, $segment_ids = array()) {
if($subscriber->id > 0) {
// unsubscribe from current subscriptions
SubscriberSegment::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_UNSUBSCRIBED)
->save();
// subscribe to segments
foreach($segment_ids as $segment_id) {
if((int)$segment_id > 0) {
self::createOrUpdate(array(
'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_SUBSCRIBED
));
if(!empty($segment_ids)) {
// subscribe to segments
foreach($segment_ids as $segment_id) {
if((int)$segment_id > 0) {
self::createOrUpdate(array(
'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_UNSUBSCRIBED
));
}
}
} else {
// unsubscribe from all segments
SubscriberSegment::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_UNSUBSCRIBED)
->save();
}
}
}
return $subscriber;
static function addSubscriptions($subscriber, $segment_ids = array()) {
if($subscriber->id > 0) {
if(!empty($segment_ids)) {
// subscribe to segments
foreach($segment_ids as $segment_id) {
if((int)$segment_id > 0) {
self::createOrUpdate(array(
'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_SUBSCRIBED
));
}
}
} else {
// subscribe to all segments
SubscriberSegment::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_SUBSCRIBED)
->save();
}
}
}
static function setSubscriptions($subscriber, $segment_ids = array()) {
self::removeSubscriptions($subscriber);
self::addSubscriptions($subscriber, $segment_ids);
}
static function subscribed($orm) {

View File

@ -0,0 +1,67 @@
<?php
namespace MailPoet\Newsletter\Links;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Security;
class Links {
public $newsletter_id;
public $queue_id;
public $subscriber_id;
function __construct(
$newsletter_id = false,
$subscriber_id = false,
$queue_id = false) {
$this->newsletter_id = $newsletter_id;
$this->queue_id = $queue_id;
$this->subscriber_id = $subscriber_id;
}
function extract($text) {
// adopted from WP's wp_extract_urls() function & modified to work on hrefs
# match href=' or href="
$regex = '#(?:href.*?=.*?)(["\']?)('
# match http://
. '(?:([\w-]+:)?//?)'
# match everything except for special characters # until .
. '[^\s()<>]+'
. '[.]'
# conditionally match everything except for special characters after .
. '(?:'
. '\([\w\d]+\)|'
. '(?:'
. '[^`!()\[\]{};:\'".,<>«»“”‘’\s]|'
. '(?:[:]\d+)?/?'
. ')+'
. ')'
. ')\\1#';
preg_match_all($regex, $text, $links);
$shortcodes = new Shortcodes();;
$shortcodes = $shortcodes->extract($text);
return array_merge(
array_unique($links[2]),
$shortcodes
);
}
function replace($text, $links = false) {
$links = ($links) ? $links : $this->extract($text);
$processed_links = array();
foreach($links as $link) {
$hash = Security::generateRandomString(5);
$processed_links[] = array(
'hash' => $hash,
'url' => $link
);
$encoded_link = sprintf(
'%s/?mailpoet&endpoint=track&action=click&data=%s',
home_url(),
'[mailpoet_data]-'.$hash
);
$link_regex = '/' . preg_quote($link, '/') . '/';
$text = preg_replace($link_regex, $encoded_link, $text);
}
return array($text, $processed_links);
}
}

View File

@ -20,7 +20,7 @@ class Renderer {
}
function createElementFromBlockType($block, $column_count) {
$block = StylesHelper::setTextAlign($block);
$block = StylesHelper::applyTextAlignment($block);
$block_class = __NAMESPACE__ . '\\' . ucfirst($block['type']);
return (class_exists($block_class)) ? $block_class::render($block, $column_count) : '';
}

View File

@ -90,7 +90,7 @@ class Text {
$paragraph->cellpadding = 0;
$next_element = $paragraph->getNextSibling();
// unless this is the last element in column, add double line breaks
$line_breaks = ($next_element && !empty($next_element->getInnerText())) ? '<br /><br />' : '';
$line_breaks = ($next_element && !$next_element->getInnerText()) ? '<br /><br />' : '';
// if this element is followed by a list, add single line break
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->getInnerText())) ? '<br />' : $line_breaks;
$paragraph->html('
@ -117,6 +117,7 @@ class Text {
$list->class = 'mailpoet_paragraph';
$list->style .= 'padding-top:0;padding-bottom:0;margin-top:10px;';
}
$list->style = StylesHelper::applyTextAlignment($list->style);
$list->style .= 'margin-bottom:10px;';
}
return $DOM->__toString();
@ -128,6 +129,7 @@ class Text {
$headings = $DOM->query('h1, h2, h3, h4');
if(!$headings->count()) return $html;
foreach($headings as $heading) {
$heading->style = StylesHelper::applyTextAlignment($heading->style);
$heading->style .= 'padding:0;font-style:normal;font-weight:normal;';
}
return $DOM->__toString();

View File

@ -65,20 +65,20 @@ class StylesHelper {
return $css;
}
static function setTextAlign($block) {
$alignments = array(
'center',
'right',
'justify'
);
$text_alignment = isset($block['styles']['block']['textAlign']) ?
strtolower($block['styles']['block']['textAlign']) :
false;
if(in_array($text_alignment, $alignments)) {
static function applyTextAlignment($block) {
if(is_array($block)) {
$text_alignment = isset($block['styles']['block']['textAlign']) ?
strtolower($block['styles']['block']['textAlign']) :
false;
if(preg_match('/center|right|justify/i', $text_alignment)) {
return $block;
}
$block['styles']['block']['textAlign'] = 'left';
return $block;
}
$block['styles']['block']['textAlign'] = 'left';
return $block;
return (preg_match('/text-align.*?[center|justify|right]/i', $block)) ?
$block :
$block . 'text-align:left;';
}
static function applyFontFamily($attribute, $style) {

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Setting;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Subscription {
@ -17,27 +18,41 @@ class Subscription {
$action,
$default_value = false,
$newsletter = false,
$subscriber = false
$subscriber = false,
$text = false,
$shortcode
) {
switch($action) {
case 'unsubscribe':
return '<a target="_blank" href="'.
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
)
.'">'.__('Unsubscribe').'</a>';
break;
case 'unsubscribe_url':
return SubscriptionUrl::getUnsubscribeUrl($subscriber);
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getUnsubscribeUrl($subscriber)
);
break;
case 'manage':
return '<a target="_blank" href="'.
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
)
.'">'.__('Manage subscription').'</a>';
break;
case 'manage_url':
return SubscriptionUrl::getManageUrl($subscriber);
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getManageUrl($subscriber)
);
break;
default:
@ -45,4 +60,10 @@ class Subscription {
break;
}
}
static function getShortcodeUrl($shortcode, $url) {
return ((boolean) Setting::getValue('tracking.enabled')) ?
$shortcode :
$url;
}
}

View File

@ -14,21 +14,27 @@ class Shortcodes {
}
function extract($text) {
preg_match_all('/\[(?:\w+):.*?\]/', $text, $shortcodes);
preg_match_all('/\[(?:\w+):.*?\]/ism', $text, $shortcodes);
return array_unique($shortcodes[0]);
}
function match($shortcode) {
preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
$shortcode,
$match
);
return $match;
}
function process($shortcodes, $text) {
$processed_shortcodes = array_map(
function($shortcode) use($text) {
preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
$shortcode,
$shortcode_details
);
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
$shortcode_details = $this->match($shortcode);
$shortcode_type = ucfirst($shortcode_details['type']);
$shortcode_action = $shortcode_details['action'];
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . $shortcode_type;
$shortcode_default_value = isset($shortcode_details['default'])
? $shortcode_details['default'] : false;
if(!class_exists($shortcode_class)) return false;
@ -37,7 +43,8 @@ class Shortcodes {
$shortcode_default_value,
$this->newsletter,
$this->subscriber,
$text
$text,
$shortcode
);
}, $shortcodes);
return $processed_shortcodes;

View File

@ -1,10 +1,10 @@
<?php
namespace MailPoet\Router;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\Segment;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Util\Helpers;
use Cron\CronExpression as Cron;

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Settings;
use \MailPoet\Subscription;
class Pages {
function __construct() {
@ -65,9 +66,11 @@ class Pages {
return array(
'id' => $page->ID,
'title' => $page->post_title,
'preview_url' => add_query_arg(array(
'mailpoet_preview' => 1
), get_permalink($page->ID))
'url' => array(
'unsubscribe' => Subscription\Url::getSubscriptionUrl($page, 'unsubscribe'),
'manage' => Subscription\Url::getSubscriptionUrl($page, 'manage'),
'confirm' => Subscription\Url::getSubscriptionUrl($page, 'confirm')
)
);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace MailPoet\Statistics\Track;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\StatisticsClicks;
use MailPoet\Models\Subscriber;
use MailPoet\Subscription\Url as SubscriptionUrl;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Clicks {
public $url;
function __construct($url) {
$this->url = $url;
}
function track($url = false) {
$url = ($url) ? $url : $this->url;
if(!preg_match('/\d+-\d+-\d+-[a-zA-Z0-9]/', $url)) $this->abort();
list ($newsletter_id, $subscriber_id, $queue_id, $hash) = explode('-', $url);
$subscriber = Subscriber::findOne($subscriber_id);
$link = NewsletterLink::where('hash', $hash)
->findOne();
if(!$link || !$subscriber) $this->abort();
$statistics = StatisticsClicks::where('link_id', $link->id)
->where('subscriber_id', $subscriber_id)
->where('newsletter_id', $newsletter_id)
->where('queue_id', $queue_id)
->findOne();
if(!$statistics) {
$statistics = StatisticsClicks::create();
$statistics->newsletter_id = $newsletter_id;
$statistics->link_id = $link->id;
$statistics->subscriber_id = $subscriber_id;
$statistics->queue_id = $queue_id;
$statistics->count = 1;
$statistics->save();
} else {
$statistics->count++;
$statistics->save();
}
$url = (preg_match('/\[subscription:.*?\]/', $link->url)) ?
$this->processSubscriptionUrl($link->url, $subscriber) :
$link->url;
header('Location: ' . $url, true, 302);
}
function processSubscriptionUrl($url, $subscriber) {
preg_match('/\[subscription:(.*?)\]/', $url, $match);
$action = $match[1];
if(preg_match('/unsubscribe/', $action)) {
$url = SubscriptionUrl::getUnsubscribeUrl($subscriber);
}
if(preg_match('/manage/', $action)) {
$url = SubscriptionUrl::getManageUrl($subscriber);
}
return $url;
}
private function abort() {
header('HTTP/1.0 404 Not Found');
exit;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Util\Url;
class Manage {
static function onSave() {
$action = (isset($_POST['action']) ? $_POST['action'] : null);
if($action !== 'mailpoet_subscription_update') {
Url::redirectBack();
}
$reserved_keywords = array('action', 'mailpoet_redirect');
$subscriber_data = array_diff_key(
$_POST,
array_flip($reserved_keywords)
);
if(isset($subscriber_data['email'])) {
if($subscriber_data['email'] !== Pages::DEMO_EMAIL) {
$subscriber = Subscriber::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
}
}
// TBD: success/error messages (not present in MP2)
Url::redirectBack();
}
}

View File

@ -3,6 +3,7 @@ namespace MailPoet\Subscription;
use \MailPoet\Router\Subscribers;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Models\CustomField;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Segment;
@ -13,51 +14,123 @@ use \MailPoet\Subscription;
class Pages {
const DEMO_EMAIL = 'demo@mailpoet.com';
function __construct() {
private $action;
private $data;
private $subscriber;
function __construct($action, $data) {
$this->action = $action;
$this->data = $data;
$this->subscriber = $this->getSubscriber();
// handle subscription pages title & content
add_filter('wp_title', array($this,'setWindowTitle'), 10, 3);
add_filter('document_title_parts', array($this,'setWindowTitleParts'), 10, 1);
add_filter('the_title', array($this,'setPageTitle'), 10, 1);
add_filter('the_content', array($this,'setPageContent'), 10, 1);
// manage subscription link shortcode
// [mailpoet_manage text="Manage your subscription"]
add_shortcode('mailpoet_manage', array($this, 'getManageLink'));
}
function init() {
$action = $this->getAction();
if($action !== null) {
add_filter('wp_title', array($this,'setWindowTitle'), 10, 3);
add_filter('document_title_parts', array($this,'setWindowTitleParts'), 10, 1);
add_filter('the_title', array($this,'setPageTitle'), 10, 1);
add_filter('the_content', array($this,'setPageContent'), 10, 1);
}
add_action(
'admin_post_mailpoet_subscriber_save',
array($this, 'subscriberSave')
);
add_action(
'admin_post_nopriv_mailpoet_subscriber_save',
array($this, 'subscriberSave')
private function isPreview() {
return (
array_key_exists('preview', $_GET)
|| array_key_exists('preview', $this->data)
);
}
function subscriberSave() {
$action = (isset($_POST['action']) ? $_POST['action'] : null);
if($action !== 'mailpoet_subscriber_save') {
Url::redirectBack();
}
function getSubscriber() {
$token = (isset($this->data['token'])) ? $this->data['token'] : null;
$email = (isset($this->data['email'])) ? $this->data['email'] : null;
$reserved_keywords = array('action', 'mailpoet_redirect');
$subscriber_data = array_diff_key(
$_POST,
array_flip($reserved_keywords)
);
if(isset($subscriber_data['email'])) {
if($subscriber_data['email'] !== self::DEMO_EMAIL) {
$subscriber = Subscriber::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
if(Subscriber::generateToken($email) === $token) {
$subscriber = Subscriber::findOne($email);
if($subscriber !== false) {
return $subscriber;
}
}
// TBD: success/error messages (not present in MP2)
Url::redirectBack();
return false;
}
function isPreview() {
return (array_key_exists('mailpoet_preview', $_GET));
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) {
if($this->subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED) {
$this->subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$this->subscriber->save();
SubscriberSegment::removeSubscriptions($this->subscriber);
}
}
}
function setPageTitle($page_title = '') {
global $post;
if($post->post_title !== __('MailPoet Page')) return $page_title;
if(
($this->isMailPoetPage($post->ID) === false)
||
($page_title !== single_post_title('', false))
) {
return $page_title;
} else {
switch($this->action) {
case 'confirm':
return $this->getConfirmTitle();
break;
case 'manage':
return $this->getManageTitle();
break;
case 'unsubscribe':
return $this->getUnsubscribeTitle();
break;
}
}
return $page_title;
}
function setPageContent($page_content = '[mailpoet_page]') {
global $post;
if(
($this->isPreview() === false)
&&
($this->isMailPoetPage($post->ID) === false)
) {
return $page_content;
}
$content = '';
switch($this->action) {
case 'confirm':
$content = $this->getConfirmContent();
break;
case 'manage':
$content = $this->getManageContent();
break;
case 'unsubscribe':
$content = $this->getUnsubscribeContent();
break;
}
if(strpos($page_content, '[mailpoet_page]') !== false) {
return str_replace('[mailpoet_page]', $content, $page_content);
} else {
return $page_content.$content;
}
}
function setWindowTitle($title, $separator, $separator_location = 'right') {
@ -80,93 +153,24 @@ class Pages {
function isMailPoetPage($page_id = null) {
$mailpoet_page_ids = array_unique(array_values(
Setting::getValue('subscription', array())
Setting::getValue('subscription.pages', array())
));
return (in_array($page_id, $mailpoet_page_ids));
}
function setPageTitle($page_title = '') {
global $post;
if($post->post_title !== __('MailPoet Page')) return $page_title;
if(
($this->isMailPoetPage($post->ID) === false)
||
($page_title !== single_post_title('', false))
) {
return $page_title;
} else {
$subscriber = $this->getSubscriber();
switch($this->getAction()) {
case 'confirm':
return $this->getConfirmTitle($subscriber);
break;
case 'manage':
return $this->getManageTitle($subscriber);
break;
case 'unsubscribe':
if($subscriber !== false) {
if($subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$subscriber->save();
}
}
return $this->getUnsubscribeTitle($subscriber);
break;
}
}
return $page_title;
}
function setPageContent($page_content = '[mailpoet_page]') {
global $post;
if(
($this->isPreview() === false)
&&
($this->isMailPoetPage($post->ID) === false)
) {
return $page_content;
}
$content = '';
$subscriber = $this->getSubscriber();
switch($this->getAction()) {
case 'confirm':
$content = $this->getConfirmContent($subscriber);
break;
case 'manage':
$content = $this->getManageContent($subscriber);
break;
case 'unsubscribe':
$content = $this->getUnsubscribeContent($subscriber);
break;
}
return str_replace('[mailpoet_page]', $content, $page_content);
}
private function getConfirmTitle($subscriber) {
private function getConfirmTitle() {
if($this->isPreview()) {
$title = sprintf(
__("You've subscribed to: %s"),
'demo 1, demo 2'
);
} else if($subscriber === false) {
} else if($this->subscriber === false) {
$title = __('Your confirmation link expired, please subscribe again.');
} else {
if($subscriber->status !== Subscriber::STATUS_SUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$subscriber->save();
}
$segment_names = array_map(function($segment) {
return $segment->name;
}, $subscriber->segments()->findMany());
}, $this->subscriber->segments()->findMany());
if(empty($segment_names)) {
$title = __("You've subscribed!");
@ -180,41 +184,35 @@ class Pages {
return $title;
}
private function getManageTitle($subscriber) {
if($this->isPreview()) {
return sprintf(
__('Edit your subscriber profile: %s'),
self::DEMO_EMAIL
);
} else if($subscriber !== false) {
return sprintf(
__('Edit your subscriber profile: %s'),
$subscriber->email
);
private function getManageTitle() {
if($this->isPreview() || $this->subscriber !== false) {
return __("Manage your subscription");
}
}
private function getUnsubscribeTitle($subscriber) {
if($this->isPreview() || $subscriber !== false) {
return __("You've unsubscribed!");
private function getUnsubscribeTitle() {
if($this->isPreview() || $this->subscriber !== false) {
return __("You've successfully unsubscribed");
}
}
private function getConfirmContent($subscriber) {
if($this->isPreview() || $subscriber !== false) {
private function getConfirmContent() {
if($this->isPreview() || $this->subscriber !== false) {
return __("Yup, we've added you to our list. You'll hear from us shortly.");
}
}
private function getManageContent($subscriber) {
private function getManageContent() {
if($this->isPreview()) {
$subscriber = Subscriber::create();
$subscriber->hydrate(array(
'email' => self::DEMO_EMAIL
'email' => self::DEMO_EMAIL,
'first_name' => 'John',
'last_name' => 'Doe'
));
} else if($subscriber !== false) {
$subscriber = $subscriber
} else if($this->subscriber !== false) {
$subscriber = $this->subscriber
->withCustomFields()
->withSubscriptions();
} else {
@ -238,8 +236,8 @@ class Pages {
->findMany();
}
$subscribed_segment_ids = array();
if(!empty($subscriber->subscriptions)) {
foreach ($subscriber->subscriptions as $subscription) {
if(!empty($this->subscriber->subscriptions)) {
foreach ($this->subscriber->subscriptions as $subscription) {
if($subscription['status'] === Subscriber::STATUS_SUBSCRIBED) {
$subscribed_segment_ids[] = $subscription['segment_id'];
}
@ -255,16 +253,6 @@ class Pages {
}, $segments);
$fields = array(
array(
'id' => 'email',
'type' => 'text',
'params' => array(
'label' => __('Email'),
'required' => true,
'value' => $subscriber->email,
'readonly' => true
)
),
array(
'id' => 'first_name',
'type' => 'text',
@ -333,53 +321,54 @@ class Pages {
$form_html = '<form method="POST" '.
'action="'.admin_url('admin-post.php').'" '.
'novalidate>';
$form_html .= '<input type="hidden" name="action" '.
'value="mailpoet_subscriber_save" />';
$form_html .= '<input type="hidden" name="action"'.
' value="mailpoet_subscription_update" />';
$form_html .= '<input type="hidden" name="segments" value="" />';
$form_html .= '<input type="hidden" name="mailpoet_redirect" '.
'value="'.Url::getCurrentUrl().'" />';
$form_html .= '<input type="hidden" name="email" value="'.$subscriber->email.'" />';
$form_html .= '<p class="mailpoet_paragraph">';
$form_html .= '<label>Email *<br /><strong>'.$subscriber->email.'</strong></label>';
$form_html .= '<br /><span style="font-size:85%;">';
if($subscriber->wp_user_id !== null) {
$form_html .= str_replace(
array('[link]', '[/link]'),
array('<a href="'.wp_login_url().'" target="_blank">', '</a>'),
__('[link]Log in to your account[/link] to update your email.')
);
} else {
$form_html .= __('Need to change your email address? Unsubscribe here and simply sign up again.');
}
$form_html .= '</span>';
$form_html .= '</p>';
// subscription form
$form_html .= \MailPoet\Form\Renderer::renderBlocks($form);
$form_html .= '</form>';
return $form_html;
}
private function getUnsubscribeContent($subscriber) {
private function getUnsubscribeContent() {
$content = '';
if($this->isPreview() || $subscriber !== false) {
$content = '<p>'.__("Great, you'll never hear from us again!").'</p>';
if($subscriber !== false) {
$content .= '<p><strong>'.
str_replace(
array('[link]', '[/link]'),
array('<a href="'.Subscription\Url::getConfirmationUrl($subscriber).'">', '</a>'),
__('You made a mistake? [link]Undo unsubscribe.[/link]')
).
'</strong></p>';
}
if($this->isPreview() || $this->subscriber !== false) {
$content .= '<p>'.__('You made a mistake?').' <strong>';
$content .= '[mailpoet_manage]';
$content .= '</strong></p>';
}
return $content;
}
private function getSubscriber() {
$token = (isset($_GET['mailpoet_token']))
? $_GET['mailpoet_token']
: null;
$email = (isset($_GET['mailpoet_email']))
? $_GET['mailpoet_email']
: null;
function getManageLink($params) {
// get label or display default label
$text = (
isset($params['text'])
? $params['text']
: __('Manage your subscription')
);
if(Subscriber::generateToken($email) === $token) {
$subscriber = Subscriber::findOne($email);
if($subscriber !== false) {
return $subscriber;
}
}
return false;
}
private function getAction() {
return (isset($_GET['mailpoet_action']))
? $_GET['mailpoet_action']
: null;
return '<a href="'.Subscription\Url::getManageUrl(
$this->subscriber
).'">'.$text.'</a>';
}
}

View File

@ -6,21 +6,21 @@ use \MailPoet\Models\Setting;
class Url {
static function getConfirmationUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.confirmation_page'));
$post = get_post(Setting::getValue('subscription.pages.confirmation'));
return self::getSubscriptionUrl($post, 'confirm', $subscriber);
}
static function getManageUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.manage_page'));
$post = get_post(Setting::getValue('subscription.pages.manage'));
return self::getSubscriptionUrl($post, 'manage', $subscriber);
}
static function getUnsubscribeUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.unsubscribe_page'));
$post = get_post(Setting::getValue('subscription.pages.unsubscribe'));
return self::getSubscriptionUrl($post, 'unsubscribe', $subscriber);
}
private static function getSubscriptionUrl(
static function getSubscriptionUrl(
$post = null, $action = null, $subscriber = false
) {
if($post === null || $action === null) return;
@ -28,22 +28,26 @@ class Url {
$url = get_permalink($post);
if($subscriber !== false) {
if(is_object($subscriber)) {
$subscriber = $subscriber->asArray();
}
$params = array(
'mailpoet_action='.$action,
'mailpoet_token='.Subscriber::generateToken($subscriber['email']),
'mailpoet_email='.$subscriber['email']
$data = array(
'token' => Subscriber::generateToken($subscriber['email']),
'email' => $subscriber['email']
);
} else {
$params = array(
'mailpoet_action='.$action,
'mailpoet_preview=1'
$data = array(
'preview' => 1
);
}
$params = array(
'endpoint=subscription',
'action='.$action,
'data='.base64_encode(serialize($data))
);
// add parameters
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?').join('&', $params);

View File

@ -6,13 +6,7 @@ class Url {
}
static function getCurrentUrl() {
global $wp;
return home_url(
add_query_arg(
$wp->query_string,
$wp->request
)
);
return home_url(add_query_arg(null, null));
}
static function redirectTo($url = null) {

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer;
/*
* Plugin Name: MailPoet
* Version: 0.0.23
* Version: 0.0.24
* 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.23');
define('MAILPOET_VERSION', '0.0.24');
$initializer = new Initializer(array(
'file' => __FILE__,

View File

@ -333,23 +333,6 @@ class SubscriberTest extends MailPoetTest {
expect($subscriber->deleted_at)->equals(null);
}
function testItCanBeAddedToSegments() {
$segment = Segment::create();
$segment->hydrate(array('name' => 'List #1'));
$segment->save();
$segment2 = Segment::create();
$segment2->hydrate(array('name' => 'List #2'));
$segment2->save();
$this->subscriber->addToSegments(array($segment->id(), $segment2->id()));
$subscriber_segments = $this->subscriber->segments()->findArray();
expect($this->subscriber->segments()->count())->equals(2);
expect($subscriber_segments[0]['name'])->equals('List #1');
expect($subscriber_segments[1]['name'])->equals('List #2');
}
function testItCanBeUpdatedByEmail() {
$subscriber_updated = Subscriber::createOrUpdate(array(
'email' => $this->data['email'],

View File

@ -207,7 +207,7 @@ class NewsletterRendererTest extends MailPoetTest {
)
)->true();
expect($DOM('tr > td.mailpoet_text > ul.mailpoet_paragraph', 0)->attr('style'))
->contains('padding-top:0;padding-bottom:0;margin-top:10px;margin-bottom:10px;');
->contains('padding-top:0;padding-bottom:0;margin-top:10px;text-align:left;margin-bottom:10px;');
// headings should be styled
expect($DOM('tr > td.mailpoet_text > h1', 0)->attr('style'))
->contains('padding:0;font-style:normal;font-weight:normal;');

View File

@ -14,51 +14,64 @@ class UrlTest extends MailPoetTest {
// preview
$url = Url::getConfirmationUrl(false);
expect($url)->notNull();
expect($url)->contains('mailpoet_action=confirm');
expect($url)->contains('preview');
expect($url)->contains('action=confirm');
expect($url)->contains('endpoint=subscription');
// actual subscriber
$subscriber = Subscriber::createOrUpdate(array(
'email' => 'john@mailpoet.com'
));
$url = Url::getConfirmationUrl($subscriber);
expect($url)->contains('mailpoet_action=confirm');
expect($url)->contains('mailpoet_token=');
expect($url)->contains('mailpoet_email=');
expect($url)->contains('action=confirm');
expect($url)->contains('endpoint=subscription');
$this->checkData($url);
}
function testItReturnsTheManageSubscriptionUrl() {
// preview
$url = Url::getManageUrl(false);
expect($url)->notNull();
expect($url)->contains('mailpoet_action=manage');
expect($url)->contains('preview');
expect($url)->contains('action=manage');
expect($url)->contains('endpoint=subscription');
// actual subscriber
$subscriber = Subscriber::createOrUpdate(array(
'email' => 'john@mailpoet.com'
));
$url = Url::getManageUrl($subscriber);
expect($url)->contains('mailpoet_action=manage');
expect($url)->contains('mailpoet_token=');
expect($url)->contains('mailpoet_email=');
expect($url)->contains('action=manage');
expect($url)->contains('endpoint=subscription');
$this->checkData($url);
}
function testItReturnsTheUnsubscribeUrl() {
// preview
$url = Url::getUnsubscribeUrl(false);
expect($url)->notNull();
expect($url)->contains('mailpoet_action=unsubscribe');
expect($url)->contains('preview');
expect($url)->contains('action=unsubscribe');
expect($url)->contains('endpoint=subscription');
// actual subscriber
$subscriber = Subscriber::createOrUpdate(array(
'email' => 'john@mailpoet.com'
));
$url = Url::getUnsubscribeUrl($subscriber);
expect($url)->contains('mailpoet_action=unsubscribe');
expect($url)->contains('mailpoet_token=');
expect($url)->contains('mailpoet_email=');
expect($url)->contains('action=unsubscribe');
expect($url)->contains('endpoint=subscription');
$this->checkData($url);
}
private function checkData($url) {
// extract & decode data from url
$url_params = parse_url($url);
parse_str($url_params['query'], $params);
$data = unserialize(base64_decode($params['data']));
expect($data['email'])->contains('john@mailpoet.com');
expect($data['token'])->notEmpty();
}
function _after() {

View File

@ -85,6 +85,11 @@
'setUp': __('Set up'),
'postNotificationNewsletterTypeTitle': __('Post notifications'),
'postNotificationsNewsletterTypeDescription': __('Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.'),
'selectPeriodicity': __('Select a periodicity'),
'periodicity': __('Periodicity'),
'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...'),
'daily': __('Once a day at...'),
'weekly': __('Weekly on...'),

View File

@ -36,6 +36,7 @@
'emptyTrash': __('Empty Trash'),
'restore': __('Restore'),
'deletePermanently': __('Delete Permanently'),
'save': __('Save'),
'numberOfItems': __('%$1d items'),

View File

@ -61,6 +61,39 @@
</p>
</td>
</tr>
<!-- link tracking -->
<tr>
<th scope="row">
<label>
<%= __('Open and click tracking') %>
<p class="description"><%= __('Some users prefer not to track their subscribers.') %></p>
</th>
<td>
<p>
<label>
<input
type="radio"
name="tracking[enabled]"
value="1"
<% if(settings.tracking.enabled) %>
checked="checked"
<% endif %>
/><%= __('Yes') %>
</label>
&nbsp;
<label>
<input
type="radio"
name="tracking[enabled]"
value=""
<% if not(settings.tracking.enabled) %>
checked="checked"
<% endif %>
/><%= __('No') %>
</label>
</p>
</td>
</tr>
<!-- share anonymous data? -->
<tr>
<th scope="row">

View File

@ -209,13 +209,13 @@
<select
class="mailpoet_page_selection"
id="subscription_manage_page"
name="subscription[manage_page]"
name="subscription[pages][manage]"
>
<% for page in pages %>
<option
value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=manage"
<% if(page.id == settings.subscription.manage_page) %>
data-preview-url="<%= page.url.manage|raw %>"
<% if(page.id == settings.subscription.pages.manage) %>
selected="selected"
<% endif %>
><%= page.title %></option>
@ -256,6 +256,8 @@
<%= __('Unsubscribe page') %>
<p class="description">
<%= __('The page your subscribers see when they click on "Unsubscribe" in your emails.') %>
<br />
<%= __('Use this shortcode in your own pages: [mailpoet_manage text="Manage your subscription"].') %>
</p>
</label>
</th>
@ -264,13 +266,13 @@
<select
class="mailpoet_page_selection"
id="subscription_unsubscribe_page"
name="subscription[unsubscribe_page]"
name="subscription[pages][unsubscribe]"
>
<% for page in pages %>
<option
value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=unsubscribe"
<% if(page.id == settings.subscription.unsubscribe_page) %>
data-preview-url="<%= page.url.unsubscribe|raw %>"
<% if(page.id == settings.subscription.pages.unsubscribe) %>
selected="selected"
<% endif %>
><%= page.title %></option>

View File

@ -151,13 +151,13 @@
<p>
<select
class="mailpoet_page_selection"
name="subscription[confirmation_page]"
name="subscription[pages][confirmation]"
>
<% for page in pages %>
<option
value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=confirm"
<% if(page.id == settings.subscription.confirmation_page) %>
data-preview-url="<%= page.url.confirm|raw %>"
<% if(page.id == settings.subscription.pages.confirmation) %>
selected="selected"
<% endif %>
><%= page.title %></option>