Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
71d8fb0d93 | |||
2f293da7a3 | |||
17b56f0160 | |||
bb9fce7f82 | |||
ad4f1f8326 | |||
8ece62c9a6 | |||
a9b9e9c631 | |||
6e289b6a8f | |||
20ced8b099 | |||
f38b632707 | |||
8ce0595342 | |||
f11de2f1ad | |||
e28451d410 | |||
72882aaf2b | |||
fdf9dd0fa3 | |||
97dd0abea2 | |||
a099174226 | |||
a054acc6e6 | |||
74254d7e2a | |||
1795964c69 | |||
0118b2472a | |||
5fe03f0dee | |||
9aef6850c2 | |||
f688a69f8b | |||
b2682fa0b7 | |||
18b15c5440 | |||
c3416977bb | |||
88a00bc38c | |||
a1441dfde6 | |||
8e7336d352 | |||
e0e2933cdf | |||
c93ec629ea | |||
d613df6558 | |||
01c9096543 | |||
f120e839dd | |||
f0ab592c04 | |||
1a269d28b3 | |||
8d4a666bf0 | |||
4a96e483a6 | |||
263a66407f | |||
c4b728f4e1 | |||
9970ad7fb6 | |||
eb380499d9 | |||
4b528549f5 | |||
a903017bc2 | |||
afd25e9174 | |||
b1bbf1b3bc | |||
9e84e8df93 | |||
df775b5a07 | |||
c9c22b9b52 | |||
93d20688ea | |||
9ebcddfa2a | |||
85995bc8a6 | |||
a8f2959bc6 | |||
36242bd580 | |||
1e7dbc8449 | |||
12c159c627 | |||
82ed7e51c5 | |||
0b3aa0d12a | |||
4d788f69aa | |||
c721843c12 | |||
d2be407ccb | |||
e6337216cf | |||
f1c396f0b0 | |||
3622bc9fcb | |||
14fe333678 | |||
cf6466197a | |||
f56bee76f2 | |||
2ec6bc8c99 | |||
4aeccb1961 | |||
bd593b1ad4 | |||
bb7812bd5d | |||
73ed070a34 | |||
9e81c48bf8 | |||
f9028d28c0 | |||
da32b243ea | |||
5092c3d328 | |||
06ad4488bf | |||
a856800e6d | |||
dfc680f3a1 | |||
a9baecc504 | |||
11e15659ac | |||
ebe440a272 | |||
853794d459 | |||
cfea13bf81 | |||
7f291d80b9 | |||
9840b55de6 | |||
ca7322933f | |||
81569e5b81 | |||
1942972282 | |||
a23aac370c | |||
99d6f74d1b | |||
a883e1176c | |||
24b98a1154 | |||
8dbb6ab79f | |||
3e7d1690bd | |||
07d533a810 | |||
499936e3ab |
@ -89,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
function makepot() {
|
||||
$this->_exec('grunt makepot'.
|
||||
$this->_exec('./node_modules/.bin/grunt makepot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
);
|
||||
}
|
||||
|
||||
function pushpot() {
|
||||
$this->_exec('grunt pushpot'.
|
||||
$this->_exec('./node_modules/.bin/grunt pushpot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
);
|
||||
@ -123,7 +123,7 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->compileJs();
|
||||
|
||||
$this->_exec(join(' ', array(
|
||||
'./node_modules/mocha/bin/mocha',
|
||||
'./node_modules/.bin/mocha',
|
||||
'-r tests/javascript/mochaTestHelper.js',
|
||||
'tests/javascript/testBundles/**/*.js'
|
||||
)));
|
||||
|
@ -17,3 +17,5 @@
|
||||
|
||||
@require 'settings'
|
||||
@require 'progress_bar'
|
||||
|
||||
@require 'subscribers'
|
@ -37,3 +37,6 @@
|
||||
|
||||
input[type=text]
|
||||
vertical-align: middle
|
||||
|
||||
.mailpoet_form_field_block
|
||||
display: block
|
||||
|
@ -30,3 +30,8 @@ $block-hover-highlight-color = $primary-active-color
|
||||
|
||||
.mailpoet_content
|
||||
position: relative
|
||||
line-height: 1.61803398875
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6
|
||||
line-height: 1.61803398875
|
||||
font-style: normal
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_footer_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
p
|
||||
margin: 0
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_header_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
p
|
||||
margin: 0
|
||||
|
@ -1,16 +1,23 @@
|
||||
$text-vertical-padding = 3px
|
||||
|
||||
.mailpoet_text_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
& > .mailpoet_content
|
||||
overflow: hidden
|
||||
padding-top: 13px
|
||||
padding-bottom: 13px
|
||||
padding-top: 0
|
||||
padding-bottom: 0px
|
||||
padding-left: 20px
|
||||
padding-right: 20px
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
padding: 0
|
||||
margin: 0
|
||||
font-weight: normal
|
||||
|
||||
p
|
||||
margin-top: 0
|
||||
font-weight: normal
|
||||
|
||||
blockquote
|
||||
margin: 1em
|
||||
padding-left: 1em
|
||||
|
@ -2,3 +2,13 @@
|
||||
|
||||
@require 'parsley'
|
||||
@require 'form_validation'
|
||||
|
||||
/* labels */
|
||||
.mailpoet_text_label
|
||||
.mailpoet_textarea_label
|
||||
.mailpoet_select_label
|
||||
.mailpoet_radio_label
|
||||
.mailpoet_checkbox_label
|
||||
.mailpoet_list_label
|
||||
.mailpoet_date_label
|
||||
display:block
|
||||
|
3
assets/css/src/subscribers.styl
Normal file
3
assets/css/src/subscribers.styl
Normal file
@ -0,0 +1,3 @@
|
||||
#subscribers_container
|
||||
.mailpoet_segments_unsubscribed
|
||||
color: lighten(#555, 33)
|
136
assets/js/src/date.js
Normal file
136
assets/js/src/date.js
Normal file
@ -0,0 +1,136 @@
|
||||
define('date',
|
||||
[
|
||||
'mailpoet',
|
||||
'jquery',
|
||||
'moment'
|
||||
], function(
|
||||
MailPoet,
|
||||
jQuery,
|
||||
Moment
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
MailPoet.Date = {
|
||||
version: 0.1,
|
||||
options: {},
|
||||
defaults: {
|
||||
offset: 0,
|
||||
format: 'F, d Y H:i:s'
|
||||
},
|
||||
init: function(options) {
|
||||
options = options || {};
|
||||
|
||||
// set UTC offset
|
||||
if (
|
||||
options.offset === undefined
|
||||
&& window.mailpoet_date_offset !== undefined
|
||||
) {
|
||||
options.offset = window.mailpoet_date_offset;
|
||||
}
|
||||
// set date format
|
||||
if (
|
||||
options.format === undefined
|
||||
&& window.mailpoet_date_format !== undefined
|
||||
) {
|
||||
options.format = window.mailpoet_date_format;
|
||||
}
|
||||
// merge options
|
||||
this.options = jQuery.extend({}, this.defaults, options);
|
||||
|
||||
return this;
|
||||
},
|
||||
format: function(date, options) {
|
||||
this.init(options);
|
||||
return Moment.utc(date)
|
||||
.local()
|
||||
.format(this.convertFormat(this.options.format));
|
||||
},
|
||||
short: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y'
|
||||
});
|
||||
},
|
||||
full: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y H:i:s'
|
||||
});
|
||||
},
|
||||
time: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'H:i:s'
|
||||
});
|
||||
},
|
||||
convertFormat: function(format) {
|
||||
const format_mappings = {
|
||||
date: {
|
||||
D: 'ddd',
|
||||
l: 'dddd',
|
||||
d: 'DD',
|
||||
j: 'D',
|
||||
z: 'DDDD',
|
||||
N: 'E',
|
||||
S: '',
|
||||
M: 'MMM',
|
||||
F: 'MMMM',
|
||||
m: 'MM',
|
||||
n: '',
|
||||
t: '',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
H: 'HH',
|
||||
h: 'hh',
|
||||
g: 'h',
|
||||
A: 'A',
|
||||
i: 'mm',
|
||||
s: 'ss',
|
||||
T: 'z',
|
||||
O: 'ZZ',
|
||||
w: 'd',
|
||||
W: 'WW'
|
||||
},
|
||||
strftime: {
|
||||
a: 'ddd',
|
||||
A: 'dddd',
|
||||
b: 'MMM',
|
||||
B: 'MMMM',
|
||||
d: 'DD',
|
||||
e: 'D',
|
||||
F: 'YYYY-MM-DD',
|
||||
H: 'HH',
|
||||
I: 'hh',
|
||||
j: 'DDDD',
|
||||
k: 'H',
|
||||
l: 'h',
|
||||
m: 'MM',
|
||||
M: 'mm',
|
||||
p: 'A',
|
||||
S: 'ss',
|
||||
u: 'E',
|
||||
w: 'd',
|
||||
W: 'WW',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
z: 'ZZ',
|
||||
Z: 'z'
|
||||
}
|
||||
};
|
||||
|
||||
const replacements = format_mappings['date'];
|
||||
|
||||
let outputFormat = '';
|
||||
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (format.indexOf(key) !== -1) {
|
||||
format = format.replace(key, '%'+key);
|
||||
}
|
||||
});
|
||||
outputFormat = format;
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (outputFormat.indexOf('%'+key) !== -1) {
|
||||
outputFormat = outputFormat.replace('%'+key, replacements[key]);
|
||||
}
|
||||
});
|
||||
return outputFormat;
|
||||
}
|
||||
};
|
||||
});
|
@ -26,7 +26,7 @@ function(
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
jQuery('#'+this.refs.select.id)
|
||||
.val(this.props.item[this.props.field.name])
|
||||
.val(this.getSelectedValues())
|
||||
.trigger('change');
|
||||
}
|
||||
},
|
||||
@ -44,10 +44,14 @@ function(
|
||||
templateResult: function(item) {
|
||||
if(item.element && item.element.selected) {
|
||||
return null;
|
||||
} else {
|
||||
if(item.title) {
|
||||
return item.title;
|
||||
} else {
|
||||
return item.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var hasRemoved = false;
|
||||
@ -65,15 +69,25 @@ function(
|
||||
|
||||
select2.select2(
|
||||
'val',
|
||||
this.props.item[this.props.field.name]
|
||||
this.getSelectedValues()
|
||||
);
|
||||
|
||||
this.setState({ initialized: true });
|
||||
},
|
||||
getSelectedValues: function() {
|
||||
if(this.props.field['selected'] !== undefined) {
|
||||
return this.props.field['selected'](this.props.item);
|
||||
} else if(this.props.item !== undefined && this.props.field.name !== undefined) {
|
||||
return this.props.item[this.props.field.name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
loadCachedItems: function() {
|
||||
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
|
||||
var items = window['mailpoet_'+this.props.field.endpoint];
|
||||
|
||||
|
||||
if(this.props.field['filter'] !== undefined) {
|
||||
items = items.filter(this.props.field.filter);
|
||||
}
|
||||
@ -98,31 +112,48 @@ function(
|
||||
});
|
||||
}
|
||||
},
|
||||
getLabel: function(item) {
|
||||
if(this.props.field['getLabel'] !== undefined) {
|
||||
return this.props.field.getLabel(item, this.props.item);
|
||||
}
|
||||
return item.name;
|
||||
},
|
||||
getSearchLabel: function(item) {
|
||||
if(this.props.field['getSearchLabel'] !== undefined) {
|
||||
return this.props.field.getSearchLabel(item, this.props.item);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getValue: function(item) {
|
||||
if(this.props.field['getValue'] !== undefined) {
|
||||
return this.props.field.getValue(item, this.props.item);
|
||||
}
|
||||
return item.id;
|
||||
},
|
||||
render: function() {
|
||||
var options = this.state.items.map(function(item, index) {
|
||||
const options = this.state.items.map((item, index) => {
|
||||
let label = this.getLabel(item);
|
||||
let searchLabel = this.getSearchLabel(item);
|
||||
let value = this.getValue(item);
|
||||
|
||||
return (
|
||||
<option
|
||||
key={ item.id }
|
||||
value={ item.id }
|
||||
key={ 'option-'+index }
|
||||
value={ value }
|
||||
title={ searchLabel }
|
||||
>
|
||||
{ item.name }
|
||||
{ label }
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
var default_value = (
|
||||
(this.props.item !== undefined && this.props.field.name !== undefined)
|
||||
? this.props.item[this.props.field.name]
|
||||
: null
|
||||
);
|
||||
|
||||
return (
|
||||
<select
|
||||
id={ this.props.field.id || this.props.field.name }
|
||||
ref="select"
|
||||
data-placeholder={ this.props.field.placeholder }
|
||||
multiple={ this.props.field.multiple }
|
||||
defaultValue={ default_value }
|
||||
defaultValue={ this.getSelectedValues() }
|
||||
{...this.props.field.validation}
|
||||
>{ options }</select>
|
||||
);
|
||||
|
@ -146,7 +146,7 @@ const FormList = React.createClass({
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Created on">
|
||||
<abbr>{ form.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -12,9 +12,19 @@ define([
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'underscore',
|
||||
'jquery'
|
||||
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
|
||||
], function(
|
||||
App,
|
||||
BaseBlock,
|
||||
ButtonBlock,
|
||||
DividerBlock,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
_,
|
||||
jQuery
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -32,10 +42,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -63,7 +73,7 @@ define([
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
this.fetchPosts();
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
|
||||
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
|
||||
},
|
||||
@ -73,7 +83,7 @@ define([
|
||||
that.get('_container').get('blocks').reset(content, {parse: true});
|
||||
that.trigger('postsChanged');
|
||||
}).fail(function(error) {
|
||||
console.log('ALC fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
@ -81,7 +91,7 @@ define([
|
||||
* ALC posts on each model change
|
||||
*/
|
||||
_scheduleFetchPosts: function() {
|
||||
var timeout = 2000,
|
||||
var timeout = 500,
|
||||
that = this;
|
||||
if (this._fetchPostsTimer !== undefined) {
|
||||
clearTimeout(this._fetchPostsTimer);
|
||||
@ -142,9 +152,9 @@ define([
|
||||
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_automated_latest_content_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -273,12 +283,13 @@ define([
|
||||
},
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -287,6 +298,12 @@ define([
|
||||
this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
|
@ -16,7 +16,7 @@ define([
|
||||
defaults: function() {
|
||||
return this._getDefaults({
|
||||
type: 'footer',
|
||||
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>',
|
||||
text: '<a href="[subscription:unsubscribe_url]">Unsubscribe</a> | <a href="[subscription:manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent',
|
||||
@ -41,7 +41,7 @@ define([
|
||||
getTemplate: function() { return templates.footerBlock; },
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
}, base.BlockView.prototype.modelEvents),
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
|
||||
|
@ -16,7 +16,7 @@ define([
|
||||
defaults: function() {
|
||||
return this._getDefaults({
|
||||
type: 'header',
|
||||
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>',
|
||||
text: 'Display problems? <a href="[newsletter:view_in_browser_url]">View it in your browser</a>',
|
||||
styles: {
|
||||
block: {
|
||||
backgroundColor: 'transparent',
|
||||
@ -41,7 +41,7 @@ define([
|
||||
getTemplate: function() { return templates.headerBlock; },
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
}, base.BlockView.prototype.modelEvents),
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
|
||||
|
@ -43,10 +43,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -88,7 +88,7 @@ define([
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
|
||||
|
||||
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
|
||||
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
|
||||
this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
|
||||
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
|
||||
|
||||
@ -101,7 +101,7 @@ define([
|
||||
that.get('_selectedPosts').reset(); // Empty out the collection
|
||||
that.trigger('change:_availablePosts');
|
||||
}).fail(function() {
|
||||
console.log('Posts fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchAvailablePosts'));
|
||||
});
|
||||
},
|
||||
_refreshTransformedPosts: function() {
|
||||
@ -118,7 +118,7 @@ define([
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
|
||||
}).fail(function() {
|
||||
console.log('Posts _refreshTransformedPosts error', arguments);
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
_insertSelectedPosts: function() {
|
||||
@ -134,7 +134,7 @@ define([
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
collection.add(posts, { at: index });
|
||||
}).fail(function() {
|
||||
console.log('Posts fetchPosts error', arguments);
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -394,9 +394,9 @@ define([
|
||||
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_posts_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -448,11 +448,11 @@ define([
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_posts_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -461,6 +461,13 @@ define([
|
||||
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
|
@ -10,7 +10,6 @@ define([
|
||||
availableStyles: {},
|
||||
socialIcons: {},
|
||||
blockDefaults: {},
|
||||
translations: {},
|
||||
sidepanelWidth: '331px',
|
||||
validation: {},
|
||||
urls: {},
|
||||
|
@ -206,12 +206,10 @@ define([
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Saving template with ', templateName, templateDescription);
|
||||
Module.saveTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
}).done(function() {
|
||||
console.log('Template saved', arguments);
|
||||
MailPoet.Notice.success(
|
||||
App.getConfig().get('translations.templateSaved'),
|
||||
{
|
||||
@ -220,7 +218,6 @@ define([
|
||||
}
|
||||
);
|
||||
}).fail(function() {
|
||||
console.log('Template save failed', arguments);
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateSaveFailed'),
|
||||
{
|
||||
@ -262,7 +259,6 @@ define([
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Exporting template with ', templateName, templateDescription);
|
||||
Module.exportTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
@ -278,7 +274,6 @@ define([
|
||||
next: function() {
|
||||
this.hideOptionContents();
|
||||
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
|
||||
console.log('Next');
|
||||
window.location.href = App.getConfig().get('urls.send');
|
||||
}
|
||||
},
|
||||
@ -289,7 +284,7 @@ define([
|
||||
}
|
||||
|
||||
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
|
||||
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) {
|
||||
JSON.stringify(jsonObject).indexOf("[subscription:unsubscribe_url]") < 0) {
|
||||
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
|
||||
return;
|
||||
}
|
||||
|
@ -1,13 +1,24 @@
|
||||
define([
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'backbone.supermodel',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'sticky-kit'
|
||||
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
|
||||
], function(
|
||||
App,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
Backbone,
|
||||
Marionette,
|
||||
SuperModel,
|
||||
_,
|
||||
jQuery,
|
||||
StickyKit
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -230,27 +241,39 @@ define([
|
||||
json.body = JSON.stringify(json.body);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'render',
|
||||
data: json,
|
||||
}).done(function(response){
|
||||
console.log('Should open a new window');
|
||||
window.open('data:text/html,' + encodeURIComponent(response.rendered_body), '_blank');
|
||||
MailPoet.Modal.loading(false);
|
||||
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
|
||||
}).fail(function(error) {
|
||||
console.log('Preview error', json);
|
||||
MailPoet.Modal.loading(false);
|
||||
alert('Something went wrong, check console');
|
||||
});
|
||||
},
|
||||
sendPreview: function() {
|
||||
// testing sending method
|
||||
console.log('trying to send a preview');
|
||||
// get form data
|
||||
var $emailField = this.$('#mailpoet_preview_to_email');
|
||||
var data = {
|
||||
subscriber: this.$('#mailpoet_preview_to_email').val(),
|
||||
subscriber: $emailField.val(),
|
||||
id: App.getNewsletter().get('id'),
|
||||
};
|
||||
|
||||
if (data.subscriber.length <= 0) {
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.newsletterPreviewEmailMissing'),
|
||||
{
|
||||
positionAfter: $emailField,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// send test email
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
|
@ -135,7 +135,7 @@ define(
|
||||
});
|
||||
},
|
||||
renderStatus: function(item) {
|
||||
if(item.queue === null) {
|
||||
if(!item.queue) {
|
||||
return (
|
||||
<span>Not sent yet.</span>
|
||||
);
|
||||
@ -208,10 +208,8 @@ define(
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
var segments = mailpoet_segments.filter(function(segment) {
|
||||
return (jQuery.inArray(segment.id, newsletter.segments) !== -1);
|
||||
}).map(function(segment) {
|
||||
return segment.name;
|
||||
var segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
}).join(', ');
|
||||
|
||||
return (
|
||||
@ -229,10 +227,10 @@ define(
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed on">
|
||||
<abbr>{ newsletter.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Last modified on">
|
||||
<abbr>{ newsletter.updated_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -53,13 +53,13 @@ define(
|
||||
var weekDayField = {
|
||||
name: 'weekDay',
|
||||
values: {
|
||||
0: 'Monday',
|
||||
1: 'Tuesday',
|
||||
2: 'Wednesday',
|
||||
3: 'Thursday',
|
||||
4: 'Friday',
|
||||
5: 'Saturday',
|
||||
6: 'Sunday',
|
||||
0: 'Sunday',
|
||||
1: 'Monday',
|
||||
2: 'Tuesday',
|
||||
3: 'Wednesday',
|
||||
4: 'Thursday',
|
||||
5: 'Friday',
|
||||
6: 'Saturday'
|
||||
},
|
||||
};
|
||||
|
||||
@ -84,10 +84,10 @@ define(
|
||||
var nthWeekDayField = {
|
||||
name: 'nthWeekDay',
|
||||
values: {
|
||||
'0': '1st',
|
||||
'1': '2nd',
|
||||
'2': '3rd',
|
||||
'3': 'last',
|
||||
'1': '1st',
|
||||
'2': '2nd',
|
||||
'3': '3rd',
|
||||
'L': 'last',
|
||||
},
|
||||
};
|
||||
|
||||
@ -99,9 +99,9 @@ define(
|
||||
return {
|
||||
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
|
||||
timeOfDay: 0,
|
||||
weekDay: 0,
|
||||
weekDay: 1,
|
||||
monthDay: 0,
|
||||
nthWeekDay: 0,
|
||||
nthWeekDay: 1,
|
||||
};
|
||||
},
|
||||
handleIntervalChange: function(event) {
|
||||
|
@ -155,6 +155,8 @@ define(
|
||||
<h1>Welcome email</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<h3>{MailPoetI18n.selectEventToSendWelcomeEmail}</h3>
|
||||
|
||||
<Select
|
||||
field={events}
|
||||
item={this.state}
|
||||
|
@ -181,16 +181,16 @@ const SegmentList = React.createClass({
|
||||
<abbr>{ segment.description }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed">
|
||||
<abbr>{ segment.subscribed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.subscribed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Unconfirmed">
|
||||
<abbr>{ segment.unconfirmed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.unconfirmed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Unsubscribed">
|
||||
<abbr>{ segment.unsubscribed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Created on">
|
||||
<abbr>{ segment.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ define(
|
||||
MailPoet,
|
||||
Form
|
||||
) {
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'email',
|
||||
@ -45,8 +44,37 @@ define(
|
||||
placeholder: "Select a list",
|
||||
endpoint: "segments",
|
||||
multiple: true,
|
||||
selected: function(subscriber) {
|
||||
if (Array.isArray(subscriber.subscriptions) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return subscriber.subscriptions.map(function(subscription) {
|
||||
if (subscription.status === 'subscribed') {
|
||||
return subscription.segment_id;
|
||||
}
|
||||
});
|
||||
},
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
},
|
||||
getSearchLabel: function(segment, subscriber) {
|
||||
let label = '';
|
||||
|
||||
if (subscriber.subscriptions !== undefined) {
|
||||
subscriber.subscriptions.map(function(subscription) {
|
||||
if (segment.id === subscription.segment_id) {
|
||||
label = segment.name;
|
||||
|
||||
if (subscription.status === 'unsubscribed') {
|
||||
const unsubscribed_at = MailPoet.Date
|
||||
.format(subscription.updated_at);
|
||||
label += ' (Unsubscribed on '+unsubscribed_at+')';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -58,11 +86,11 @@ define(
|
||||
label: custom_field.name,
|
||||
type: custom_field.type
|
||||
};
|
||||
if(custom_field.params) {
|
||||
if (custom_field.params) {
|
||||
field.params = custom_field.params;
|
||||
}
|
||||
|
||||
if(custom_field.params.values) {
|
||||
if (custom_field.params.values) {
|
||||
field.values = custom_field.params.values;
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ define(
|
||||
exportData.exportConfirmedOption = false;
|
||||
renderSegmentsAndFields(segmentsContainerElement, segments);
|
||||
}
|
||||
segmentsContainerElement.select2('val', selectedSegments);
|
||||
segmentsContainerElement.val(selectedSegments).trigger('change');
|
||||
});
|
||||
|
||||
function toggleNextStepButton(condition) {
|
||||
|
@ -6,7 +6,9 @@ define(
|
||||
'mailpoet',
|
||||
'handlebars',
|
||||
'papaparse',
|
||||
'select2'
|
||||
'select2',
|
||||
'asyncqueue',
|
||||
'xss'
|
||||
],
|
||||
function (
|
||||
Backbone,
|
||||
@ -14,12 +16,15 @@ define(
|
||||
jQuery,
|
||||
MailPoet,
|
||||
Handlebars,
|
||||
Papa
|
||||
Papa,
|
||||
AsyncQueue,
|
||||
xss
|
||||
) {
|
||||
if (!jQuery('#mailpoet_subscribers_import').length) {
|
||||
return;
|
||||
}
|
||||
jQuery(document).ready(function () {
|
||||
var noticeTimeout = 3000;
|
||||
jQuery('input[name="select_method"]').attr('checked', false);
|
||||
// configure router
|
||||
router = new (Backbone.Router.extend({
|
||||
@ -124,7 +129,7 @@ define(
|
||||
var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1;
|
||||
if (pasteSize > maxPostSizeBytes) {
|
||||
MailPoet.Notice.error(MailPoetI18n.maxPostSizeNotice, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -144,7 +149,7 @@ define(
|
||||
if (ext === null || ext[1].toLowerCase() !== 'csv') {
|
||||
this.value = '';
|
||||
MailPoet.Notice.error(MailPoetI18n.wrongFileFormat, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
@ -194,7 +199,7 @@ define(
|
||||
if (response.result === false) {
|
||||
MailPoet.Notice.hide();
|
||||
MailPoet.Notice.error(response.errors, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
jQuery('.mailpoet_mailchimp-key-status')
|
||||
.removeClass()
|
||||
@ -219,7 +224,7 @@ define(
|
||||
MailPoet.Modal.loading(false);
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -246,7 +251,7 @@ define(
|
||||
else {
|
||||
MailPoet.Notice.hide();
|
||||
MailPoet.Notice.error(response.errors, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
}
|
||||
MailPoet.Modal.loading(false);
|
||||
@ -254,7 +259,7 @@ define(
|
||||
MailPoet.Modal.loading(false);
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.serverError + result.statusText.toLowerCase() + '.', {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -346,13 +351,13 @@ define(
|
||||
error: function () {
|
||||
MailPoet.Notice.hide();
|
||||
MailPoet.Notice.error(MailPoetI18n.dataProcessingError, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
},
|
||||
complete: function (CSV) {
|
||||
for (var rowCount in CSV.data) {
|
||||
var rowData = CSV.data[rowCount].map(function (el) {
|
||||
return el.trim();
|
||||
return filterXSS(el.trim());
|
||||
}),
|
||||
rowColumnCount = rowData.length;
|
||||
// set the number of row elements based on the first non-empty row
|
||||
@ -430,7 +435,7 @@ define(
|
||||
errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink);
|
||||
errorNotice = errorNotice.replace('[/link]', '</a>');
|
||||
MailPoet.Notice.error(errorNotice, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -561,7 +566,7 @@ define(
|
||||
if (!segmentSelectionNotice.length) {
|
||||
MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, {
|
||||
static: true,
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
scroll: true,
|
||||
id: 'segmentSelection',
|
||||
hideClose: true
|
||||
@ -640,7 +645,7 @@ define(
|
||||
MailPoet.Modal.close();
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.segmentCreateError + response.message + '.', {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -649,7 +654,7 @@ define(
|
||||
MailPoet.Modal.close();
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
|
||||
timeout: 3000
|
||||
timeout: noticeTimeout
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -856,7 +861,7 @@ define(
|
||||
}
|
||||
else {
|
||||
MailPoet.Notice.error(MailPoetI18n.customFieldCreateError, {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
}
|
||||
MailPoet.Modal.loading(false);
|
||||
@ -865,7 +870,7 @@ define(
|
||||
MailPoet.Modal.loading(false);
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -930,7 +935,7 @@ define(
|
||||
if (!jQuery('[data-id="notice_invalidEmail"]').length) {
|
||||
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, {
|
||||
static: true,
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
scroll: true,
|
||||
hideClose: true,
|
||||
id: 'invalidEmail'
|
||||
@ -1010,7 +1015,7 @@ define(
|
||||
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
|
||||
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidDate, {
|
||||
static: true,
|
||||
timeout: 3000,
|
||||
timeout: noticeTimeout,
|
||||
scroll: true,
|
||||
hideClose: true,
|
||||
id: 'invalidDate'
|
||||
@ -1050,64 +1055,98 @@ define(
|
||||
}
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
var subscribers = {};
|
||||
var columns = {},
|
||||
queue = new jQuery.AsyncQueue(),
|
||||
batchNumber = 0,
|
||||
batchSize = 2000,
|
||||
timestamp = Date.now() / 1000,
|
||||
subscribers = [],
|
||||
importResults = {
|
||||
'created': 0,
|
||||
'updated': 0,
|
||||
'errors': [],
|
||||
'segments': []
|
||||
},
|
||||
splitSubscribers = function (subscribers, size) {
|
||||
return subscribers.reduce(function (res, item, index) {
|
||||
if (index % size === 0) {
|
||||
res.push([]);
|
||||
}
|
||||
res[res.length - 1].push(item);
|
||||
return res;
|
||||
}, []);
|
||||
},
|
||||
subscribers = splitSubscribers(importData.step1.subscribers, batchSize);
|
||||
|
||||
_.each(jQuery('select.mailpoet_subscribers_column_data_match'),
|
||||
function (column, index) {
|
||||
function (column, columnIndex) {
|
||||
var columnId = jQuery(column).data('column-id');
|
||||
if (columnId === 'ignore') {
|
||||
return;
|
||||
}
|
||||
subscribers[columnId] = [];
|
||||
_.each(importData.step1.subscribers, function (subsciber) {
|
||||
subscribers[columnId].push(
|
||||
_.chain(subsciber)
|
||||
.pick(index)
|
||||
.toArray()
|
||||
.flatten()
|
||||
.value()
|
||||
);
|
||||
});
|
||||
subscribers[columnId] = _.flatten(subscribers[columnId]);
|
||||
columns[columnId] = columnIndex;
|
||||
});
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
_.each(subscribers, function () {
|
||||
queue.add(function (queue) {
|
||||
queue.pause();
|
||||
MailPoet.Ajax
|
||||
.post({
|
||||
endpoint: 'ImportExport',
|
||||
action: 'processImport',
|
||||
data: JSON.stringify({
|
||||
subscribers: subscribers,
|
||||
columns: columns,
|
||||
subscribers: subscribers[batchNumber],
|
||||
timestamp: timestamp,
|
||||
segments: segmentSelectElement.val(),
|
||||
updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false
|
||||
})
|
||||
}).done(function (response) {
|
||||
MailPoet.Modal.loading(false);
|
||||
})
|
||||
.done(function (response) {
|
||||
if (response.result === false) {
|
||||
MailPoet.Notice.error(response.errors, {
|
||||
timeout: 3000,
|
||||
});
|
||||
importResults.errors.push(response.errors);
|
||||
} else {
|
||||
mailpoetSegments = response.data.segments;
|
||||
response.data.segments = _.map(segmentSelectElement.select2('data'),
|
||||
importResults.created = response.data.created;
|
||||
importResults.updated = response.data.updated;
|
||||
importResults.segments = response.data.segments;
|
||||
}
|
||||
queue.run();
|
||||
})
|
||||
.error(function (error) {
|
||||
importResults.errors.push(
|
||||
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
|
||||
);
|
||||
queue.run();
|
||||
});
|
||||
batchNumber++;
|
||||
})
|
||||
});
|
||||
|
||||
queue.run();
|
||||
|
||||
queue.onComplete(function () {
|
||||
MailPoet.Modal.loading(false);
|
||||
if (importResults.errors.length > 0 && !importResults.updated && !importResults.created) {
|
||||
MailPoet.Notice.error(_.flatten(importResults.errors), {
|
||||
timeout: noticeTimeout,
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
mailpoetSegments = importResults.segments;
|
||||
importResults.segments = _.map(segmentSelectElement.select2('data'),
|
||||
function (data) {
|
||||
return data.name;
|
||||
});
|
||||
importData.step2 = response.data;
|
||||
importData.step2 = importResults;
|
||||
enableSegmentSelection(mailpoetSegments);
|
||||
router.navigate('step3', {trigger: true});
|
||||
}
|
||||
}).error(function (error) {
|
||||
MailPoet.Modal.loading(false);
|
||||
MailPoet.Notice.error(
|
||||
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
|
||||
timeout: 3000,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
filterSubscribers();
|
||||
enableSegmentSelection(mailpoetSegments);
|
||||
|
||||
});
|
||||
|
||||
router.on('route:step3', function () {
|
||||
@ -1118,6 +1157,12 @@ define(
|
||||
|
||||
showCurrentStep();
|
||||
|
||||
if (importData.step2.errors.length > 0) {
|
||||
MailPoet.Notice.error(_.flatten(importData.step2.errors), {
|
||||
timeout: noticeTimeout,
|
||||
});
|
||||
}
|
||||
|
||||
// display statistics
|
||||
var subscribersDataImportResultsTemplate =
|
||||
Handlebars
|
||||
|
@ -208,6 +208,16 @@ const bulk_actions = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sendConfirmationEmail',
|
||||
label: 'Resend confirmation email',
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
'%$1d confirmation emails have been sent.'
|
||||
.replace('%$1d', ~~response)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
@ -231,6 +241,15 @@ const item_actions = [
|
||||
];
|
||||
|
||||
const SubscriberList = React.createClass({
|
||||
getSegmentFromId: function(segment_id) {
|
||||
let result = false;
|
||||
mailpoet_segments.map(function(segment) {
|
||||
if (segment.id === segment_id) {
|
||||
result = segment;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
renderItem: function(subscriber, actions) {
|
||||
let row_classes = classNames(
|
||||
'manage-column',
|
||||
@ -255,11 +274,41 @@ const SubscriberList = React.createClass({
|
||||
break;
|
||||
}
|
||||
|
||||
let segments = mailpoet_segments.filter(function(segment) {
|
||||
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
|
||||
}).map(function(segment) {
|
||||
return segment.name;
|
||||
}).join(', ');
|
||||
let segments = false;
|
||||
|
||||
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="Lists to which the subscriber was subscribed."
|
||||
>
|
||||
{ unsubscribed_segments.join(', ') }
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let avatar = false;
|
||||
if(subscriber.avatar_url) {
|
||||
@ -292,10 +341,10 @@ const SubscriberList = React.createClass({
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed on">
|
||||
<abbr>{ subscriber.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Last modified on">
|
||||
<abbr>{ subscriber.updated_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
79
assets/js/src/vendor/jquery.asyncqueue.js
vendored
Normal file
79
assets/js/src/vendor/jquery.asyncqueue.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of the jquery plugin "asyncQueue".
|
||||
*
|
||||
* (c) Sebastien Roch <roch.sebastien@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
(function($){
|
||||
$.AsyncQueue = function() {
|
||||
var that = this,
|
||||
queue = [],
|
||||
failureFunc,
|
||||
completeFunc,
|
||||
paused = false,
|
||||
lastCallbackData,
|
||||
_run;
|
||||
|
||||
_run = function() {
|
||||
var f = queue.shift();
|
||||
|
||||
if (f) {
|
||||
f.apply(that, [that]);
|
||||
if (paused === false) {
|
||||
_run();
|
||||
}
|
||||
} else {
|
||||
if(completeFunc){
|
||||
completeFunc.apply(that);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.onFailure = function(func) {
|
||||
failureFunc = func;
|
||||
}
|
||||
|
||||
this.onComplete = function(func) {
|
||||
completeFunc = func;
|
||||
}
|
||||
|
||||
this.add = function(func) {
|
||||
queue.push(func);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.storeData = function(dataObject) {
|
||||
lastCallbackData = dataObject;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.lastCallbackData = function () {
|
||||
return lastCallbackData;
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
paused = false;
|
||||
_run();
|
||||
}
|
||||
|
||||
this.pause = function () {
|
||||
paused = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.failure = function() {
|
||||
paused = true;
|
||||
if (failureFunc) {
|
||||
var args = [that];
|
||||
for(i = 0; i < arguments.length; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
failureFunc.apply(that, args);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
})(jQuery);
|
@ -9,6 +9,7 @@ settings:
|
||||
bootstrap: _bootstrap.php
|
||||
colors: true
|
||||
memory_limit: 1024M
|
||||
log: true
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunFailed
|
||||
|
@ -9,7 +9,7 @@
|
||||
"j4mie/paris": "1.5.4",
|
||||
"swiftmailer/swiftmailer": "^5.4",
|
||||
"phpseclib/phpseclib": "*",
|
||||
"mtdowling/cron-expression": "^1.0",
|
||||
"mtdowling/cron-expression": "^1.1",
|
||||
"nesbot/carbon": "^1.21",
|
||||
"soundasleep/html2text": "^0.3.0"
|
||||
},
|
||||
|
10
composer.lock
generated
10
composer.lock
generated
@ -4,8 +4,8 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "db2edea5fb720fcb8013ac470e924c19",
|
||||
"content-hash": "85119d1ccd5193b6b08fda305a2427e7",
|
||||
"hash": "2bed8395d84740d7c0ae644a6c6216fd",
|
||||
"content-hash": "7b66e221814f3d5839ed4faabd2f50ad",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cerdic/css-tidy",
|
||||
@ -2294,7 +2294,7 @@
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/41ee6c70758f40fa1dbf90d019ae0a66c4a09e74",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/ee4cdda66aff834c8125e8f2c15932461667c521",
|
||||
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74",
|
||||
"shasum": ""
|
||||
},
|
||||
@ -2671,7 +2671,7 @@
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/form/zipball/7fd5e4034cb8e215887136f5e176430bbf5ef085",
|
||||
"url": "https://api.github.com/repos/symfony/form/zipball/b629051c77a4f37c625651d03002760385df4575",
|
||||
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085",
|
||||
"shasum": ""
|
||||
},
|
||||
@ -2929,7 +2929,7 @@
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/d9d21cfcc3e202ee34777d6da38897695d4d208d",
|
||||
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac",
|
||||
"shasum": ""
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,12 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use \MailPoet\Models\Setting;
|
||||
use \MailPoet\Util\Url;
|
||||
|
||||
class Changelog {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
|
||||
|
||||
@ -42,20 +46,7 @@ class Changelog {
|
||||
// save version number
|
||||
Setting::setValue('version', Env::$version);
|
||||
|
||||
global $wp;
|
||||
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
|
||||
|
||||
if($redirect_url !== $current_url) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
'mailpoet_redirect' => urlencode($current_url)
|
||||
),
|
||||
$redirect_url
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
Url::redirectWithReferer($redirect_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use MailPoet\Cron\Workers\Scheduler;
|
||||
use MailPoet\Cron\Workers\SendingQueue;
|
||||
use \MailPoet\Models\Setting;
|
||||
|
||||
class Hooks {
|
||||
@ -11,6 +13,7 @@ class Hooks {
|
||||
$this->setupWPUsers();
|
||||
$this->setupImageSize();
|
||||
$this->setupListing();
|
||||
$this->setupCronWorkers();
|
||||
}
|
||||
|
||||
function setupSubscribe() {
|
||||
@ -144,4 +147,19 @@ class Hooks {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
function setupCronWorkers() {
|
||||
add_action('mailpoet_cron_worker', array($this, 'runSchedulerWorker'), 10, 1);
|
||||
add_action('mailpoet_cron_worker', array($this, 'runSendingQueueWorker'), 10, 1);
|
||||
}
|
||||
|
||||
function runSchedulerWorker($timer) {
|
||||
$scheduler = new Scheduler($timer);
|
||||
$scheduler->process();
|
||||
|
||||
}
|
||||
function runSendingQueueWorker($timer) {
|
||||
$sending_queue = new SendingQueue($timer);
|
||||
$sending_queue->process();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ namespace MailPoet\Config;
|
||||
use MailPoet\Models;
|
||||
use MailPoet\Cron\Supervisor;
|
||||
use MailPoet\Router;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Settings\Pages;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -26,6 +24,7 @@ class Initializer {
|
||||
register_activation_hook(Env::$file, array($this, 'runPopulator'));
|
||||
|
||||
add_action('plugins_loaded', array($this, 'setup'));
|
||||
add_action('init', array($this, 'onInit'));
|
||||
add_action('widgets_init', array($this, 'setupWidget'));
|
||||
}
|
||||
|
||||
@ -34,16 +33,14 @@ class Initializer {
|
||||
$this->setupRenderer();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
$this->setupRouter();
|
||||
$this->setupPermissions();
|
||||
$this->setupPublicAPI();
|
||||
$this->setupAnalytics();
|
||||
$this->setupChangelog();
|
||||
$this->runQueueSupervisor();
|
||||
$this->setupShortcodes();
|
||||
$this->setupHooks();
|
||||
$this->setupPages();
|
||||
$this->setupImages();
|
||||
$this->setupPublicAPI();
|
||||
$this->runQueueSupervisor();
|
||||
} catch(\Exception $e) {
|
||||
// if anything goes wrong during init
|
||||
// automatically deactivate the plugin
|
||||
@ -51,11 +48,20 @@ class Initializer {
|
||||
}
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
$this->setupRouter();
|
||||
$this->setupPages();
|
||||
$this->runQueueSupervisor();
|
||||
}
|
||||
|
||||
function setupDB() {
|
||||
\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('logger', function($query, $time) {
|
||||
// error_log("\n".$query."\n");
|
||||
});
|
||||
|
||||
\ORM::configure('driver_options', array(
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
@ -144,8 +150,11 @@ class Initializer {
|
||||
}
|
||||
|
||||
function setupPages() {
|
||||
$pages = new Pages();
|
||||
$pages = new \MailPoet\Settings\Pages();
|
||||
$pages->init();
|
||||
|
||||
$subscription_pages = new \MailPoet\Subscription\Pages();
|
||||
$subscription_pages->init();
|
||||
}
|
||||
|
||||
function setupShortcodes() {
|
||||
|
@ -380,11 +380,12 @@ class Menu {
|
||||
|
||||
$data = array(
|
||||
'customFields' => $custom_fields,
|
||||
'settings' => Setting::getAll(),
|
||||
);
|
||||
wp_enqueue_media();
|
||||
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
|
||||
wp_enqueue_style('editor', includes_url('css/editor.css'));
|
||||
echo $this->renderer->render('newsletter/form.html', $data);
|
||||
echo $this->renderer->render('newsletter/editor.html', $data);
|
||||
}
|
||||
|
||||
function import() {
|
||||
|
@ -221,6 +221,7 @@ class Migrator {
|
||||
'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,',
|
||||
|
@ -70,8 +70,13 @@ class Populator {
|
||||
|
||||
if($page === null) {
|
||||
$mailpoet_page_id = Pages::createMailPoetPage();
|
||||
Setting::setValue('subscription.page', $mailpoet_page_id);
|
||||
} else {
|
||||
$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);
|
||||
}
|
||||
|
||||
private function createDefaultSettings() {
|
||||
@ -175,6 +180,14 @@ class Populator {
|
||||
'name' => 'nthWeekDay',
|
||||
'newsletter_type' => 'notification',
|
||||
),
|
||||
array(
|
||||
'name' => 'schedule',
|
||||
'newsletter_type' => 'notification',
|
||||
),
|
||||
array(
|
||||
'name' => 'lastSentData',
|
||||
'newsletter_type' => 'notification',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ class BlankTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "footer",
|
||||
"text" => "<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
|
||||
"text" => "<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "transparent"
|
||||
|
@ -46,7 +46,7 @@ class FranksRoastHouseTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "header",
|
||||
"text" => __("Display problems? <a href=\"[viewInBrowserUrl]\">View it in your browser</a>"),
|
||||
"text" => __("Display problems? <a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "#ccc6c6"
|
||||
@ -280,7 +280,7 @@ class FranksRoastHouseTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "footer",
|
||||
"text" => __("<p><a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
|
||||
"text" => __("<p><a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "#a9a7a7"
|
||||
|
@ -242,7 +242,7 @@ class PostNotificationsBlankTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "footer",
|
||||
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
|
||||
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "transparent"
|
||||
|
@ -46,7 +46,7 @@ class WelcomeTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "header",
|
||||
"text" => __("Display problems? <a href=\"[viewInBrowserUrl]\">View it in your browser</a>"),
|
||||
"text" => __("Display problems? <a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "transparent"
|
||||
@ -224,7 +224,7 @@ class WelcomeTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "footer",
|
||||
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
|
||||
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "transparent"
|
||||
|
@ -68,4 +68,11 @@ class CronHelper {
|
||||
// throw an error if all connection attempts failed
|
||||
throw new \Exception(__('Site URL is unreachable.'));
|
||||
}
|
||||
|
||||
static function checkExecutionTimer($timer) {
|
||||
$elapsed_time = microtime(true) - $timer;
|
||||
if($elapsed_time >= self::daemon_execution_limit) {
|
||||
throw new \Exception(__('Maximum execution time reached.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Cron\Workers\Scheduler;
|
||||
use MailPoet\Cron\Workers\SendingQueue;
|
||||
use MailPoet\Models\Newsletter;
|
||||
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
|
||||
@ -35,8 +37,7 @@ class Daemon {
|
||||
}
|
||||
$this->abortIfStopped($daemon);
|
||||
try {
|
||||
$sending_queue = new SendingQueue($this->timer);
|
||||
$sending_queue->process();
|
||||
do_action('mailpoet_cron_worker', $this->timer);
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
$elapsed_time = microtime(true) - $this->timer;
|
||||
|
27
lib/Cron/Workers/Scheduler.php
Normal file
27
lib/Cron/Workers/Scheduler.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Scheduler {
|
||||
public $timer;
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
}
|
||||
|
||||
function process() {
|
||||
}
|
||||
|
||||
function checkExecutionTimer() {
|
||||
$elapsed_time = microtime(true) - $this->timer;
|
||||
if($elapsed_time >= CronHelper::daemon_execution_limit) {
|
||||
throw new \Exception(__('Maximum execution time reached.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -27,6 +28,7 @@ class SendingQueue {
|
||||
'processBulkSubscribers' :
|
||||
'processIndividualSubscriber';
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
}
|
||||
|
||||
function process() {
|
||||
@ -102,7 +104,7 @@ class SendingQueue {
|
||||
}
|
||||
$this->updateQueue($queue);
|
||||
$this->checkSendingLimit();
|
||||
$this->checkExecutionTimer();
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
return $queue->subscribers;
|
||||
}
|
||||
|
||||
@ -129,7 +131,7 @@ class SendingQueue {
|
||||
$this->updateNewsletterStatistics($newsletter_statistics);
|
||||
}
|
||||
$this->updateQueue($queue);
|
||||
$this->checkExecutionTimer();
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
}
|
||||
return $queue->subscribers;
|
||||
}
|
||||
@ -201,7 +203,9 @@ class SendingQueue {
|
||||
$queue->subscribers->failed
|
||||
)
|
||||
);
|
||||
$queue->subscribers->to_process = array_values($queue->subscribers->to_process);
|
||||
$queue->subscribers->to_process = array_values(
|
||||
$queue->subscribers->to_process
|
||||
);
|
||||
$queue->count_processed =
|
||||
count($queue->subscribers->processed) + count($queue->subscribers->failed);
|
||||
$queue->count_to_process = count($queue->subscribers->to_process);
|
||||
@ -259,11 +263,4 @@ class SendingQueue {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function checkExecutionTimer() {
|
||||
$elapsed_time = microtime(true) - $this->timer;
|
||||
if($elapsed_time >= CronHelper::daemon_execution_limit) {
|
||||
throw new \Exception(__('Maximum execution time reached.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -112,4 +112,17 @@ abstract class Base {
|
||||
&& strlen(trim($block['params']['value'])) > 0)
|
||||
? esc_attr(trim($block['params']['value'])) : '';
|
||||
}
|
||||
|
||||
protected static function getInputModifiers($block = array()) {
|
||||
$modifiers = array();
|
||||
|
||||
if(isset($block['params']['readonly'])) {
|
||||
$modifiers[] = 'readonly';
|
||||
}
|
||||
|
||||
if(isset($block['params']['disabled'])) {
|
||||
$modifiers[] = 'disabled';
|
||||
}
|
||||
return join(' ', $modifiers);
|
||||
}
|
||||
}
|
@ -20,18 +20,22 @@ class Checkbox extends Base {
|
||||
|
||||
foreach($options as $option) {
|
||||
$html .= '<label class="mailpoet_checkbox_label">';
|
||||
|
||||
$html .= '<input type="hidden" name="'.$field_name.'" value="" />';
|
||||
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
|
||||
|
||||
$html .= 'name="'.$field_name.'" ';
|
||||
|
||||
$html .= 'value="1" ';
|
||||
|
||||
$html .= (isset($option['is_checked']) && $option['is_checked'])
|
||||
? 'checked="checked"' : '';
|
||||
$html .= (
|
||||
(isset($option['is_checked']) && $option['is_checked'])
|
||||
||
|
||||
(self::getFieldValue($block))
|
||||
) ? 'checked="checked"' : '';
|
||||
|
||||
$html .= $field_validation;
|
||||
|
||||
$html .= ' />'.$option['value'];
|
||||
$html .= ' /> '.esc_attr($option['value']);
|
||||
|
||||
$html .= '</label>';
|
||||
}
|
||||
|
@ -33,18 +33,37 @@ class Date extends Base {
|
||||
// generate an array of selectors based on date format
|
||||
$date_selectors = explode('/', $date_format);
|
||||
|
||||
// format value if present
|
||||
$value = self::getFieldValue($block);
|
||||
$day = null;
|
||||
$month = null;
|
||||
$year = null;
|
||||
|
||||
if($value) {
|
||||
$day = (int)strftime('%d', $value);
|
||||
$month = (int)strftime('%m', $value);
|
||||
$year = (int)strftime('%Y', $value);
|
||||
} else if(!empty($block['params']['is_default_today'])) {
|
||||
$day = (int)strftime('%d');
|
||||
$month = (int)strftime('%m');
|
||||
$year = (int)strftime('%Y');
|
||||
}
|
||||
|
||||
foreach($date_selectors as $date_selector) {
|
||||
if($date_selector === 'dd') {
|
||||
$block['selected'] = $day;
|
||||
$html .= '<select class="mailpoet_date_day" ';
|
||||
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
|
||||
$html .= static::getDays($block);
|
||||
$html .= '</select>';
|
||||
} else if($date_selector === 'mm') {
|
||||
$block['selected'] = $month;
|
||||
$html .= '<select class="mailpoet_date_month" ';
|
||||
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
|
||||
$html .= static::getMonths($block);
|
||||
$html .= '</select>';
|
||||
} else if($date_selector === 'yyyy') {
|
||||
$block['selected'] = $year;
|
||||
$html .= '<select class="mailpoet_date_year" ';
|
||||
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
|
||||
$html .= static::getYears($block);
|
||||
@ -84,11 +103,6 @@ class Date extends Base {
|
||||
'selected' => null
|
||||
);
|
||||
|
||||
// is default today
|
||||
if(!empty($block['params']['is_default_today'])) {
|
||||
$defaults['selected'] = (int)strftime('%m');
|
||||
}
|
||||
|
||||
// merge block with defaults
|
||||
$block = array_merge($defaults, $block);
|
||||
|
||||
|
@ -25,14 +25,24 @@ class Radio extends Base {
|
||||
|
||||
$html .= 'name="'.$field_name.'" ';
|
||||
|
||||
$html .= 'value="'.esc_attr($option['value']).'" ';
|
||||
if(is_array($option['value'])) {
|
||||
$value = key($option['value']);
|
||||
$label = reset($option['value']);
|
||||
} else {
|
||||
$value = $option['value'];
|
||||
$label = $option['value'];
|
||||
}
|
||||
|
||||
$html .= 'value="'.esc_attr($value).'" ';
|
||||
|
||||
$html .= (
|
||||
(isset($option['is_checked']) && $option['is_checked'])
|
||||
||
|
||||
(self::getFieldValue($block) === $value)
|
||||
) ? 'checked="checked"' : '';
|
||||
|
||||
$html .= (isset($option['is_checked']) && $option['is_checked'])
|
||||
? 'checked="checked"' : '';
|
||||
$html .= $field_validation;
|
||||
|
||||
$html .= ' /> '.esc_attr($option['value']);
|
||||
|
||||
$html .= ' /> '.esc_attr($label);
|
||||
$html .= '</label>';
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class Segment extends Base {
|
||||
$html .= 'name="'.$field_name.'[]" ';
|
||||
$html .= 'value="'.$option['id'].'" '.$is_checked.' ';
|
||||
$html .= $field_validation;
|
||||
$html .= ' />'.$option['name'];
|
||||
$html .= ' /> '.esc_attr($option['name']);
|
||||
$html .= '</label>';
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,7 @@ class Select extends Base {
|
||||
$field_validation = static::getInputValidation($block);
|
||||
|
||||
$html .= '<p class="mailpoet_paragraph">';
|
||||
|
||||
$html .= static::renderLabel($block);
|
||||
|
||||
$html .= '<select class="mailpoet_select" name="'.$field_name.'">';
|
||||
|
||||
if(isset($block['params']['label_within'])
|
||||
@ -20,11 +18,28 @@ class Select extends Base {
|
||||
$html .= '<option value="">'.static::getFieldLabel($block).'</option>';
|
||||
}
|
||||
|
||||
foreach($block['params']['values'] as $option) {
|
||||
$is_selected = (isset($option['is_checked']) && $option['is_checked'])
|
||||
? 'selected="selected"' : '';
|
||||
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>';
|
||||
$html .= $option['value'];
|
||||
$options = (!empty($block['params']['values'])
|
||||
? $block['params']['values']
|
||||
: array()
|
||||
);
|
||||
|
||||
foreach($options as $option) {
|
||||
$is_selected = (
|
||||
(isset($option['is_checked']) && $option['is_checked'])
|
||||
||
|
||||
(self::getFieldValue($block) === $option['value'])
|
||||
) ? 'selected="selected"' : '';
|
||||
|
||||
if(is_array($option['value'])) {
|
||||
$value = key($option['value']);
|
||||
$label = reset($option['value']);
|
||||
} else {
|
||||
$value = $option['value'];
|
||||
$label = $option['value'];
|
||||
}
|
||||
|
||||
$html .= '<option value="'.$value.'" '.$is_selected.'>';
|
||||
$html .= esc_attr($label);
|
||||
$html .= '</option>';
|
||||
}
|
||||
$html .= '</select>';
|
||||
|
@ -27,6 +27,8 @@ class Text extends Base {
|
||||
|
||||
$html .= static::getInputValidation($block);
|
||||
|
||||
$html .= static::getInputModifiers($block);
|
||||
|
||||
$html .= '/>';
|
||||
|
||||
$html .= '</p>';
|
||||
|
@ -19,6 +19,8 @@ class Textarea extends Base {
|
||||
|
||||
$html .= static::getInputValidation($block);
|
||||
|
||||
$html .= static::getInputModifiers($block);
|
||||
|
||||
$html .= '></textarea>';
|
||||
|
||||
$html .= '</p>';
|
||||
|
@ -39,8 +39,7 @@ class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
// private: rendering methods
|
||||
private static function renderBlocks($blocks = array()) {
|
||||
static function renderBlocks($blocks = array()) {
|
||||
$html = '';
|
||||
foreach ($blocks as $key => $block) {
|
||||
$html .= static::renderBlock($block)."\n";
|
||||
@ -49,7 +48,7 @@ class Renderer {
|
||||
return $html;
|
||||
}
|
||||
|
||||
private static function renderBlock($block = array()) {
|
||||
static function renderBlock($block = array()) {
|
||||
$html = '';
|
||||
switch($block['type']) {
|
||||
case 'html':
|
||||
|
@ -22,19 +22,21 @@ class Export {
|
||||
), site_url());
|
||||
|
||||
// generate iframe
|
||||
return '<iframe '.
|
||||
'width="100%" '.
|
||||
'scrolling="no" '.
|
||||
'frameborder="0" '.
|
||||
'src="'.$iframe_url.'" '.
|
||||
'class="mailpoet_form_iframe" '.
|
||||
'vspace="0" '.
|
||||
'tabindex="0" '.
|
||||
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"'.
|
||||
'marginwidth="0" '.
|
||||
'marginheight="0" '.
|
||||
'hspace="0" '.
|
||||
'allowtransparency="true"></iframe>';
|
||||
return join(' ', array(
|
||||
'<iframe',
|
||||
'width="100%"',
|
||||
'scrolling="no"',
|
||||
'frameborder="0"',
|
||||
'src="'.$iframe_url.'"',
|
||||
'class="mailpoet_form_iframe"',
|
||||
'vspace="0"',
|
||||
'tabindex="0"',
|
||||
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"',
|
||||
'marginwidth="0"',
|
||||
'marginheight="0"',
|
||||
'hspace="0"',
|
||||
'allowtransparency="true"></iframe>'
|
||||
));
|
||||
break;
|
||||
|
||||
case 'php':
|
||||
|
@ -17,7 +17,7 @@ class Styles {
|
||||
}
|
||||
|
||||
/* labels */
|
||||
.mailpoet_input_label,
|
||||
.mailpoet_text_label,
|
||||
.mailpoet_textarea_label,
|
||||
.mailpoet_select_label,
|
||||
.mailpoet_radio_label,
|
||||
@ -28,7 +28,7 @@ class Styles {
|
||||
}
|
||||
|
||||
/* inputs */
|
||||
.mailpoet_input,
|
||||
.mailpoet_text,
|
||||
.mailpoet_textarea,
|
||||
.mailpoet_select,
|
||||
.mailpoet_date {
|
||||
@ -36,9 +36,7 @@ class Styles {
|
||||
}
|
||||
|
||||
.mailpoet_checkbox {
|
||||
display:inline;
|
||||
margin-right: 5px;
|
||||
vertical-align:middle;
|
||||
|
||||
}
|
||||
|
||||
.mailpoet_validate_success {
|
||||
|
@ -89,11 +89,11 @@ class Handler {
|
||||
$items = $this->model
|
||||
->offset($this->data['offset'])
|
||||
->limit($this->data['limit'])
|
||||
->findArray();
|
||||
->findMany();
|
||||
|
||||
return array(
|
||||
'count' => $count,
|
||||
'filters' => $this->model->filter('filters'),
|
||||
'filters' => $this->model->filter('filters', $this->data['group']),
|
||||
'groups' => $this->model->filter('groups'),
|
||||
'items' => $items
|
||||
);
|
||||
|
@ -25,8 +25,7 @@ class PHPMail {
|
||||
}
|
||||
|
||||
function buildMailer() {
|
||||
$transport = \Swift_SmtpTransport::newInstance();
|
||||
$transport->setTimeout(10);
|
||||
$transport = \Swift_MailTransport::newInstance();
|
||||
return \Swift_Mailer::newInstance($transport);
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class Model extends \Sudzy\ValidModel {
|
||||
$total = $orm->count();
|
||||
|
||||
if($total > 0) {
|
||||
$models = $orm->select('id')
|
||||
$models = $orm->select(static::$_table.'.id')
|
||||
->offset(null)
|
||||
->limit(null)
|
||||
->findArray();
|
||||
|
@ -52,6 +52,11 @@ class Newsletter extends Model {
|
||||
);
|
||||
}
|
||||
|
||||
function withSegments() {
|
||||
$this->segments = $this->segments()->findArray();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function options() {
|
||||
return $this->has_many_through(
|
||||
__NAMESPACE__.'\NewsletterOptionField',
|
||||
@ -67,11 +72,21 @@ class Newsletter extends Model {
|
||||
->findOne();
|
||||
}
|
||||
|
||||
function withSendingQueue() {
|
||||
$queue = $this->getQueue();
|
||||
if($queue === false) {
|
||||
$this->queue = false;
|
||||
} else {
|
||||
$this->queue = $queue->asArray();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
static function search($orm, $search = '') {
|
||||
return $orm->where_like('subject', '%' . $search . '%');
|
||||
}
|
||||
|
||||
static function filters() {
|
||||
static function filters($orm, $group = 'all') {
|
||||
$segments = Segment::orderByAsc('name')->findMany();
|
||||
$segment_list = array();
|
||||
$segment_list[] = array(
|
||||
@ -80,7 +95,9 @@ class Newsletter extends Model {
|
||||
);
|
||||
|
||||
foreach($segments as $segment) {
|
||||
$newsletters_count = $segment->newsletters()->count();
|
||||
$newsletters_count = $segment->newsletters()
|
||||
->filter('groupBy', $group)
|
||||
->count();
|
||||
if($newsletters_count > 0) {
|
||||
$segment_list[] = array(
|
||||
'label' => sprintf('%s (%d)', $segment->name, $newsletters_count),
|
||||
|
@ -35,16 +35,7 @@ class Segment extends Model {
|
||||
__NAMESPACE__.'\SubscriberSegment',
|
||||
'segment_id',
|
||||
'subscriber_id'
|
||||
);
|
||||
}
|
||||
|
||||
function segmentFilters() {
|
||||
return $this->has_many_through(
|
||||
__NAMESPACE__.'\Filter',
|
||||
__NAMESPACE__.'\SegmentFilter',
|
||||
'segment_id',
|
||||
'filter_id'
|
||||
);
|
||||
)->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', 'subscribed');
|
||||
}
|
||||
|
||||
function duplicate($data = array()) {
|
||||
@ -76,6 +67,32 @@ class Segment extends Model {
|
||||
->delete();
|
||||
}
|
||||
|
||||
function withSubscribersCount() {
|
||||
$this->subscribers_count = SubscriberSegment::table_alias('relation')
|
||||
->where('relation.segment_id', $this->id)
|
||||
->join(
|
||||
MP_SUBSCRIBERS_TABLE,
|
||||
'subscribers.id = relation.subscriber_id',
|
||||
'subscribers'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
|
||||
'subscribed'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
|
||||
'unsubscribed'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
|
||||
'unconfirmed'
|
||||
)
|
||||
->findOne()
|
||||
->asArray();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
static function getWPUsers() {
|
||||
return self::where('type', 'wp_users')->findOne();
|
||||
}
|
||||
@ -169,6 +186,6 @@ class Segment extends Model {
|
||||
}
|
||||
|
||||
static function getPublic() {
|
||||
return self::getPublished()->where('type', 'default');
|
||||
return self::getPublished()->where('type', 'default')->orderByAsc('name');
|
||||
}
|
||||
}
|
@ -6,6 +6,13 @@ if (!defined('ABSPATH')) exit;
|
||||
class Setting extends Model {
|
||||
public static $_table = MP_SETTINGS_TABLE;
|
||||
|
||||
public static $defaults = null;
|
||||
|
||||
const DEFAULT_SENDING_METHOD_GROUP = 'website';
|
||||
const DEFAULT_SENDING_METHOD = 'PHPMail';
|
||||
const DEFAULT_SENDING_FREQUENCY_EMAILS = 25;
|
||||
const DEFAULT_SENDING_FREQUENCY_INTERVAL = 15; // in minutes
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
@ -14,8 +21,34 @@ class Setting extends Model {
|
||||
));
|
||||
}
|
||||
|
||||
public static function getDefaults() {
|
||||
if(self::$defaults === null) {
|
||||
self::loadDefaults();
|
||||
}
|
||||
return self::$defaults;
|
||||
}
|
||||
|
||||
public static function loadDefaults() {
|
||||
self::$defaults = array(
|
||||
'mta_group' => self::DEFAULT_SENDING_METHOD_GROUP,
|
||||
'mta' => array(
|
||||
'method' => self::DEFAULT_SENDING_METHOD,
|
||||
'frequency' => array(
|
||||
'emails' => self::DEFAULT_SENDING_FREQUENCY_EMAILS,
|
||||
'interval' => self::DEFAULT_SENDING_FREQUENCY_INTERVAL
|
||||
)
|
||||
),
|
||||
'signup_confirmation' => array(
|
||||
'enabled' => true,
|
||||
'subject' => sprintf(__('Confirm your subscription to %1$s'), get_option('blogname')),
|
||||
'body' => __("Hello!\n\nHurray! You've subscribed to our site.\nWe need you to activate your subscription to the list(s): [lists_to_confirm] by clicking the link below: \n\n[activation_link]Click here to confirm your subscription.[/activation_link]\n\nThank you,\n\nThe team!")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getValue($key, $default = null) {
|
||||
$keys = explode('.', $key);
|
||||
$defaults = self::getDefaults();
|
||||
|
||||
if(count($keys) === 1) {
|
||||
$setting = Setting::where('name', $key)->findOne();
|
||||
@ -23,9 +56,14 @@ class Setting extends Model {
|
||||
return $default;
|
||||
} else {
|
||||
if(is_serialized($setting->value)) {
|
||||
return unserialize($setting->value);
|
||||
$value = unserialize($setting->value);
|
||||
} else {
|
||||
return $setting->value;
|
||||
$value = $setting->value;
|
||||
}
|
||||
if(is_array($value) && array_key_exists($key, $defaults)) {
|
||||
return array_replace_recursive($defaults[$key], $value);
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -93,7 +131,7 @@ class Setting extends Model {
|
||||
$settings[$setting->name] = $value;
|
||||
}
|
||||
}
|
||||
return $settings;
|
||||
return array_replace_recursive(self::getDefaults(), $settings);
|
||||
}
|
||||
|
||||
public static function createOrUpdate($data = array()) {
|
||||
|
@ -1,12 +1,19 @@
|
||||
<?php
|
||||
namespace MailPoet\Models;
|
||||
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Subscription;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Subscriber extends Model {
|
||||
public static $_table = MP_SUBSCRIBERS_TABLE;
|
||||
|
||||
const STATUS_SUBSCRIBED = 'subscribed';
|
||||
const STATUS_UNSUBSCRIBED = 'unsubscribed';
|
||||
const STATUS_UNCONFIRMED = 'unconfirmed';
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
@ -16,13 +23,22 @@ class Subscriber extends Model {
|
||||
));
|
||||
}
|
||||
|
||||
static function findOne($id = null) {
|
||||
if(is_int($id) || (string)(int)$id === $id) {
|
||||
return parent::findOne($id);
|
||||
} else {
|
||||
return parent::where('email', $id)->findOne();
|
||||
}
|
||||
}
|
||||
|
||||
function segments() {
|
||||
return $this->has_many_through(
|
||||
__NAMESPACE__.'\Segment',
|
||||
__NAMESPACE__.'\SubscriberSegment',
|
||||
'subscriber_id',
|
||||
'segment_id'
|
||||
);
|
||||
)
|
||||
->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', self::STATUS_SUBSCRIBED);
|
||||
}
|
||||
|
||||
function delete() {
|
||||
@ -57,9 +73,79 @@ class Subscriber extends Model {
|
||||
}
|
||||
|
||||
function sendConfirmationEmail() {
|
||||
$this->set('status', 'unconfirmed');
|
||||
if($this->status === self::STATUS_UNCONFIRMED) {
|
||||
$signup_confirmation = Setting::getValue('signup_confirmation');
|
||||
|
||||
// TODO
|
||||
$segments = $this->segments()->findMany();
|
||||
$segment_names = array_map(function($segment) {
|
||||
return $segment->name;
|
||||
}, $segments);
|
||||
|
||||
$body = nl2br($signup_confirmation['body']);
|
||||
|
||||
// replace list of segments shortcode
|
||||
$body = str_replace(
|
||||
'[lists_to_confirm]',
|
||||
'<strong>'.join(', ', $segment_names).'</strong>',
|
||||
$body
|
||||
);
|
||||
|
||||
// replace activation link
|
||||
$body = str_replace(
|
||||
array(
|
||||
'[activation_link]',
|
||||
'[/activation_link]'
|
||||
),
|
||||
array(
|
||||
'<a href="'.esc_attr(Subscription\Url::getConfirmationUrl($this)).'">',
|
||||
'</a>'
|
||||
),
|
||||
$body
|
||||
);
|
||||
|
||||
// build email data
|
||||
$email = array(
|
||||
'subject' => $signup_confirmation['subject'],
|
||||
'body' => array(
|
||||
'html' => $body,
|
||||
'text' => $body
|
||||
)
|
||||
);
|
||||
|
||||
// convert subscriber to array
|
||||
$subscriber = $this->asArray();
|
||||
|
||||
// set from
|
||||
$from = (
|
||||
!empty($signup_confirmation['from'])
|
||||
&& !empty($signup_confirmation['from']['email'])
|
||||
) ? $signup_confirmation['from']
|
||||
: false;
|
||||
|
||||
// set reply to
|
||||
$reply_to = (
|
||||
!empty($signup_confirmation['reply_to'])
|
||||
&& !empty($signup_confirmation['reply_to']['email'])
|
||||
) ? $signup_confirmation['reply_to']
|
||||
: false;
|
||||
|
||||
// send email
|
||||
try {
|
||||
$mailer = new Mailer(false, $from, $reply_to);
|
||||
return $mailer->send($email, $subscriber);
|
||||
} catch(\Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function generateToken($email = null) {
|
||||
if($email !== null) {
|
||||
return md5(AUTH_KEY.$email);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
|
||||
@ -68,23 +154,27 @@ class Subscriber extends Model {
|
||||
}
|
||||
|
||||
$subscriber = self::createOrUpdate($subscriber_data);
|
||||
$errors = $subscriber->getErrors();
|
||||
|
||||
if($errors === false && $subscriber->id > 0) {
|
||||
$subscriber = self::findOne($subscriber->id);
|
||||
|
||||
if($subscriber !== false && $subscriber->id() > 0) {
|
||||
// restore deleted subscriber
|
||||
if($subscriber->deleted_at !== NULL) {
|
||||
$subscriber->setExpr('deleted_at', 'NULL');
|
||||
}
|
||||
|
||||
if((bool)Setting::getValue('signup_confirmation.enabled')) {
|
||||
if($subscriber->status !== 'subscribed') {
|
||||
if($subscriber->status !== self::STATUS_SUBSCRIBED) {
|
||||
$subscriber->sendConfirmationEmail();
|
||||
}
|
||||
} else {
|
||||
$subscriber->set('status', 'subscribed');
|
||||
$subscriber->set('status', self::STATUS_SUBSCRIBED);
|
||||
}
|
||||
|
||||
if($subscriber->save()) {
|
||||
$subscriber->addToSegments($segment_ids);
|
||||
Scheduler::welcomeForSegmentSubscription($subscriber->id, $segment_ids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +192,7 @@ class Subscriber extends Model {
|
||||
);
|
||||
}
|
||||
|
||||
static function filters() {
|
||||
static function filters($orm, $group = 'all') {
|
||||
$segments = Segment::orderByAsc('name')->findMany();
|
||||
$segment_list = array();
|
||||
$segment_list[] = array(
|
||||
@ -119,8 +209,9 @@ class Subscriber extends Model {
|
||||
|
||||
foreach($segments as $segment) {
|
||||
$subscribers_count = $segment->subscribers()
|
||||
->whereNull('deleted_at')
|
||||
->filter('groupBy', $group)
|
||||
->count();
|
||||
|
||||
$segment_list[] = array(
|
||||
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
|
||||
'value' => $segment->id()
|
||||
@ -161,19 +252,19 @@ class Subscriber extends Model {
|
||||
'count' => self::getPublished()->count()
|
||||
),
|
||||
array(
|
||||
'name' => 'subscribed',
|
||||
'name' => self::STATUS_SUBSCRIBED,
|
||||
'label' => __('Subscribed'),
|
||||
'count' => self::filter('subscribed')->count()
|
||||
'count' => self::filter(self::STATUS_SUBSCRIBED)->count()
|
||||
),
|
||||
array(
|
||||
'name' => 'unconfirmed',
|
||||
'name' => self::STATUS_UNCONFIRMED,
|
||||
'label' => __('Unconfirmed'),
|
||||
'count' => self::filter('unconfirmed')->count()
|
||||
'count' => self::filter(self::STATUS_UNCONFIRMED)->count()
|
||||
),
|
||||
array(
|
||||
'name' => 'unsubscribed',
|
||||
'name' => self::STATUS_UNSUBSCRIBED,
|
||||
'label' => __('Unsubscribed'),
|
||||
'count' => self::filter('unsubscribed')->count()
|
||||
'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count()
|
||||
),
|
||||
array(
|
||||
'name' => 'trash',
|
||||
@ -247,6 +338,9 @@ class Subscriber extends Model {
|
||||
|
||||
static function createOrUpdate($data = array()) {
|
||||
$subscriber = false;
|
||||
if(is_array($data) && !empty($data)) {
|
||||
$data = stripslashes_deep($data);
|
||||
}
|
||||
|
||||
if(isset($data['id']) && (int)$data['id'] > 0) {
|
||||
$subscriber = self::findOne((int)$data['id']);
|
||||
@ -272,16 +366,25 @@ class Subscriber extends Model {
|
||||
|
||||
foreach($data as $key => $value) {
|
||||
if(strpos($key, 'cf_') === 0) {
|
||||
if(is_array($value)) {
|
||||
$value = array_filter($value);
|
||||
$value = reset($value);
|
||||
}
|
||||
$custom_fields[(int)substr($key, 3)] = $value;
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$old_status = false;
|
||||
$new_status = false;
|
||||
|
||||
if($subscriber === false) {
|
||||
$subscriber = self::create();
|
||||
$subscriber->hydrate($data);
|
||||
} else {
|
||||
$old_status = $subscriber->status;
|
||||
$subscriber->set($data);
|
||||
$new_status = $subscriber->status;
|
||||
}
|
||||
|
||||
if($subscriber->save()) {
|
||||
@ -290,8 +393,19 @@ class Subscriber extends Model {
|
||||
$subscriber->setCustomField($custom_field_id, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// check for status change
|
||||
if(
|
||||
($old_status === self::STATUS_SUBSCRIBED)
|
||||
&&
|
||||
($new_status === self::STATUS_UNSUBSCRIBED)
|
||||
) {
|
||||
// make sure we unsubscribe the user from all lists
|
||||
SubscriberSegment::setSubscriptions($subscriber, array());
|
||||
} else {
|
||||
if($segment_ids !== false) {
|
||||
$subscriber->addToSegments($segment_ids);
|
||||
SubscriberSegment::setSubscriptions($subscriber, $segment_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $subscriber;
|
||||
@ -314,6 +428,17 @@ class Subscriber extends Model {
|
||||
return $this;
|
||||
}
|
||||
|
||||
function withSegments() {
|
||||
$this->segments = $this->segments()->findArray();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function withSubscriptions() {
|
||||
$this->subscriptions = SubscriberSegment::where('subscriber_id', $this->id())
|
||||
->findArray();
|
||||
return $this;
|
||||
}
|
||||
|
||||
function getCustomField($custom_field_id, $default = null) {
|
||||
$custom_field = SubscriberCustomField::select('value')
|
||||
->where('custom_field_id', $custom_field_id)
|
||||
@ -401,21 +526,23 @@ class Subscriber extends Model {
|
||||
|
||||
static function bulkConfirmUnconfirmed($orm) {
|
||||
$subscribers = $orm->findResultSet();
|
||||
$subscribers->set('status', 'subscribed')->save();
|
||||
$subscribers->set('status', self::STATUS_SUBSCRIBED)->save();
|
||||
return $subscribers->count();
|
||||
}
|
||||
|
||||
static function bulkResendConfirmationEmail($orm) {
|
||||
static function bulkSendConfirmationEmail($orm) {
|
||||
$subscribers = $orm
|
||||
->where('status', 'unconfirmed')
|
||||
->findResultSet();
|
||||
->where('status', self::STATUS_UNCONFIRMED)
|
||||
->findMany();
|
||||
|
||||
$emails_sent = 0;
|
||||
if(!empty($subscribers)) {
|
||||
foreach($subscribers as $subscriber) {
|
||||
$subscriber->sendConfirmationEmail();
|
||||
if($subscriber->sendConfirmationEmail()) {
|
||||
$emails_sent++;
|
||||
}
|
||||
|
||||
return $subscribers->count();
|
||||
}
|
||||
return $emails_sent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -456,19 +583,19 @@ class Subscriber extends Model {
|
||||
static function subscribed($orm) {
|
||||
return $orm
|
||||
->whereNull('deleted_at')
|
||||
->where('status', 'subscribed');
|
||||
->where('status', self::STATUS_SUBSCRIBED);
|
||||
}
|
||||
|
||||
static function unsubscribed($orm) {
|
||||
return $orm
|
||||
->whereNull('deleted_at')
|
||||
->where('status', 'unsubscribed');
|
||||
->where('status', self::STATUS_UNSUBSCRIBED);
|
||||
}
|
||||
|
||||
static function unconfirmed($orm) {
|
||||
return $orm
|
||||
->whereNull('deleted_at')
|
||||
->where('status', 'unconfirmed');
|
||||
->where('status', self::STATUS_UNCONFIRMED);
|
||||
}
|
||||
|
||||
static function withoutSegments($orm) {
|
||||
@ -498,7 +625,7 @@ class Subscriber extends Model {
|
||||
);
|
||||
}
|
||||
|
||||
static function updateMultiple($columns, $subscribers, $currentTime = false) {
|
||||
static function updateMultiple($columns, $subscribers, $updated_at = false) {
|
||||
$ignoreColumnsOnUpdate = array(
|
||||
'email',
|
||||
'created_at'
|
||||
@ -538,7 +665,7 @@ class Subscriber extends Model {
|
||||
return self::rawExecute(
|
||||
'UPDATE `' . self::$_table . '` ' .
|
||||
'SET ' . implode(', ', $sql('statement')) . ' '.
|
||||
(($currentTime) ? ', updated_at = "' . $currentTime . '" ' : '') .
|
||||
(($updated_at) ? ', updated_at = "' . $updated_at . '" ' : '') .
|
||||
'WHERE email IN ' .
|
||||
'(' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')',
|
||||
array_merge(
|
||||
|
@ -12,29 +12,55 @@ class SubscriberSegment extends Model {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
static function filterWithCustomFields($orm) {
|
||||
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
|
||||
$customFields = CustomField::findArray();
|
||||
foreach ($customFields as $customField) {
|
||||
$orm = $orm->select_expr(
|
||||
'CASE WHEN ' .
|
||||
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
|
||||
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
|
||||
static function setSubscriptions($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
|
||||
));
|
||||
}
|
||||
$orm = $orm
|
||||
->left_outer_join(
|
||||
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
|
||||
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
|
||||
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
|
||||
->left_outer_join(
|
||||
MP_CUSTOM_FIELDS_TABLE,
|
||||
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
|
||||
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
|
||||
return $orm;
|
||||
}
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
static function subscribed($orm) {
|
||||
return $orm->where('status', 'subscribed');
|
||||
return $orm->where('status', Subscriber::STATUS_SUBSCRIBED);
|
||||
}
|
||||
|
||||
static function createOrUpdate($data = array()) {
|
||||
$subscription = false;
|
||||
|
||||
if(isset($data['id']) && (int)$data['id'] > 0) {
|
||||
$subscription = self::findOne((int)$data['id']);
|
||||
}
|
||||
|
||||
if(isset($data['subscriber_id']) && isset($data['segment_id'])) {
|
||||
$subscription = self::where('subscriber_id', (int)$data['subscriber_id'])
|
||||
->where('segment_id', (int)$data['segment_id'])
|
||||
->findOne();
|
||||
}
|
||||
|
||||
if($subscription === false) {
|
||||
$subscription = self::create();
|
||||
$subscription->hydrate($data);
|
||||
} else {
|
||||
unset($data['id']);
|
||||
$subscription->set($data);
|
||||
}
|
||||
|
||||
return $subscription->save();
|
||||
}
|
||||
|
||||
static function createMultiple($segmnets, $subscribers) {
|
||||
|
@ -22,23 +22,35 @@ class PostTransformer {
|
||||
$content = $content_manager->filterContent($content);
|
||||
|
||||
$structure_transformer = new StructureTransformer();
|
||||
$structure = $structure_transformer->transform($content, $this->args['imageFullWidth'] === 'true');
|
||||
$structure = $structure_transformer->transform($content, $this->args['imageFullWidth'] === true);
|
||||
|
||||
if($this->args['featuredImagePosition'] === 'aboveTitle') {
|
||||
$structure = $this->appendPostTitle($post, $structure);
|
||||
$structure = $this->appendFeaturedImage(
|
||||
$post,
|
||||
$this->args['displayType'],
|
||||
$this->args['imageFullWidth'] === 'true',
|
||||
$structure
|
||||
);
|
||||
} else {
|
||||
if($this->args['featuredImagePosition'] === 'belowTitle') {
|
||||
$structure = $this->appendFeaturedImage(
|
||||
$post,
|
||||
$this->args['displayType'],
|
||||
$this->args['imageFullWidth'] === 'true',
|
||||
$structure
|
||||
);
|
||||
}
|
||||
$structure = $this->appendPostTitle($post, $structure);
|
||||
}
|
||||
$structure = $this->appendReadMore($post->ID, $structure);
|
||||
|
||||
return $structure;
|
||||
}
|
||||
|
||||
private function appendFeaturedImage($post, $display_type, $image_full_width, $structure) {
|
||||
if ($display_type === 'full') {
|
||||
// No featured images for full posts
|
||||
if($display_type !== 'excerpt') {
|
||||
// Append featured images only on excerpts
|
||||
return $structure;
|
||||
}
|
||||
|
||||
@ -48,7 +60,7 @@ class PostTransformer {
|
||||
(bool)$image_full_width
|
||||
);
|
||||
|
||||
if (is_array($featured_image)) {
|
||||
if(is_array($featured_image)) {
|
||||
return array_merge(array($featured_image), $structure);
|
||||
}
|
||||
|
||||
@ -96,25 +108,12 @@ class PostTransformer {
|
||||
private function appendPostTitle($post, $structure) {
|
||||
$title = $this->getPostTitle($post);
|
||||
|
||||
if ($this->args['titlePosition'] === 'inTextBlock') {
|
||||
// Attach title to the first text block
|
||||
$text_block_index = null;
|
||||
foreach ($structure as $index => $block) {
|
||||
if ($block['type'] === 'text') {
|
||||
$text_block_index = $index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Append title always at the top of the post structure
|
||||
// Reuse an existing text block if needed
|
||||
|
||||
if ($text_block_index === null) {
|
||||
$structure[] = array(
|
||||
'type' => 'text',
|
||||
'text' => $title,
|
||||
);
|
||||
if(count($structure) > 0 && $structure[0]['type'] === 'text') {
|
||||
$structure[0]['text'] = $title . $structure[0]['text'];
|
||||
} else {
|
||||
$structure[$text_block_index]['text'] = $title . $structure[$text_block_index]['text'];
|
||||
}
|
||||
} elseif ($this->args['titlePosition'] === 'aboveBlock') {
|
||||
array_unshift(
|
||||
$structure,
|
||||
array(
|
||||
@ -128,19 +127,26 @@ class PostTransformer {
|
||||
}
|
||||
|
||||
private function appendReadMore($post_id, $structure) {
|
||||
if ($this->args['readMoreType'] === 'button') {
|
||||
if($this->args['readMoreType'] === 'button') {
|
||||
$button = $this->args['readMoreButton'];
|
||||
$button['url'] = get_permalink($post_id);
|
||||
$structure[] = $button;
|
||||
} else {
|
||||
$structure[] = array(
|
||||
'type' => 'text',
|
||||
'text' => sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$total_blocks = count($structure);
|
||||
$read_more_text = sprintf(
|
||||
'<p><a href="%s">%s</a></p>',
|
||||
get_permalink($post_id),
|
||||
$this->args['readMoreText']
|
||||
),
|
||||
);
|
||||
|
||||
if($structure[$total_blocks - 1]['type'] === 'text') {
|
||||
$structure[$total_blocks - 1]['text'] .= $read_more_text;
|
||||
} else {
|
||||
$structure[] = array(
|
||||
'type' => 'text',
|
||||
'text' => $read_more_text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $structure;
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter\Renderer\Blocks;
|
||||
|
||||
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
|
||||
use MailPoet\Newsletter\Renderer\StylesHelper;
|
||||
|
||||
class Button {
|
||||
static function render($element) {
|
||||
static function render($element, $column_count) {
|
||||
$element['styles']['block']['width'] = self::calculateWidth($element, $column_count);
|
||||
$template = '
|
||||
<tr>
|
||||
<td class="mailpoet_padded" valign="top">
|
||||
@ -37,4 +39,13 @@ class Button {
|
||||
</tr>';
|
||||
return $template;
|
||||
}
|
||||
|
||||
static function calculateWidth($element, $column_count) {
|
||||
$column_width = ColumnsHelper::columnWidth($column_count);
|
||||
$column_width = $column_width - (StylesHelper::$padding_width * 2);
|
||||
$column_width = ((int) $element['styles']['block']['width'] > $column_width) ?
|
||||
$column_width . 'px' :
|
||||
$element['styles']['block']['width'];
|
||||
return $column_width;
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class Footer {
|
||||
}
|
||||
$template = '
|
||||
<tr>
|
||||
<td class="mailpoet_padded_header_footer mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
|
||||
<td class="mailpoet_header_footer_padded mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
|
||||
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
|
||||
' . $DOM->html() . '
|
||||
</td>
|
||||
|
@ -19,7 +19,7 @@ class Header {
|
||||
}
|
||||
$template = '
|
||||
<tr>
|
||||
<td class="mailpoet_padded_header_footer mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
|
||||
<td class="mailpoet_header_footer_padded mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
|
||||
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
|
||||
' . $DOM->html() . '
|
||||
</td>
|
||||
|
@ -5,15 +5,21 @@ use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
|
||||
use MailPoet\Newsletter\Renderer\StylesHelper;
|
||||
|
||||
class Image {
|
||||
static function render($element, $columnCount) {
|
||||
static function render($element, $column_count) {
|
||||
$element['width'] = (int) $element['width'];
|
||||
$element['height'] = (int) $element['height'];
|
||||
$element = self::adjustImageDimensions($element, $columnCount);
|
||||
$element = self::adjustImageDimensions($element, $column_count);
|
||||
$image_template = '
|
||||
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
|
||||
width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/>
|
||||
';
|
||||
if(!empty($element['link'])) {
|
||||
$image_template = '<a href="' . $element['link'] . '">' . $image_template . '</a>';
|
||||
}
|
||||
$template = '
|
||||
<tr>
|
||||
<td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded' : '') . '" align="center" valign="top">
|
||||
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
|
||||
width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/>
|
||||
' . $image_template . '
|
||||
</td>
|
||||
</tr>';
|
||||
return $template;
|
||||
|
@ -3,9 +3,13 @@ namespace MailPoet\Newsletter\Renderer\Blocks;
|
||||
|
||||
class Spacer {
|
||||
static function render($element) {
|
||||
$height = (int) $element['styles']['block']['height'];
|
||||
$background_color = $element['styles']['block']['backgroundColor'];
|
||||
$template = '
|
||||
<tr>
|
||||
<td class="mailpoet_spacer" height="' . (int) $element['styles']['block']['height'] . '" valign="top"></td>
|
||||
<td class="mailpoet_spacer" ' .
|
||||
(($background_color !== 'transparent') ? 'bgcolor="' . $background_color . '" ' : ' ') .
|
||||
'height="' . $height . '" valign="top"></td>
|
||||
</tr>';
|
||||
return $template;
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ class Renderer {
|
||||
$template = ($columns_count === 1) ?
|
||||
$this->getOneColumnTemplate($styles, $class) :
|
||||
$this->getMultipleColumnsTemplate($styles, $width, $alignment, $class);
|
||||
$result = array_map(function ($content) use ($template) {
|
||||
$result = array_map(function($content) use ($template) {
|
||||
$content = self::removePaddingFromLastElement($content);
|
||||
return $template['content_start'] . $content . $template['content_end'];
|
||||
}, $columns_data);
|
||||
$result = implode('', $result);
|
||||
@ -75,4 +76,8 @@ class Renderer {
|
||||
</tr>';
|
||||
return $template;
|
||||
}
|
||||
|
||||
function removePaddingFromLastElement($element) {
|
||||
return preg_replace('/mailpoet_padded(?!.*mailpoet_padded)/ism', '', $element);
|
||||
}
|
||||
}
|
@ -43,9 +43,12 @@ class Renderer {
|
||||
}
|
||||
|
||||
function renderBody($content) {
|
||||
$content = array_map(function ($content_block) {
|
||||
$content = array_map(function($content_block) {
|
||||
$column_count = count($content_block['blocks']);
|
||||
$column_data = $this->blocks_renderer->render($content_block, $column_count);
|
||||
$column_data = $this->blocks_renderer->render(
|
||||
$content_block,
|
||||
$column_count
|
||||
);
|
||||
return $this->columns_renderer->render(
|
||||
$content_block['styles'],
|
||||
$column_count,
|
||||
|
@ -11,6 +11,7 @@ class StylesHelper {
|
||||
'textDecoration' => 'text-decoration',
|
||||
'textAlign' => 'text-align',
|
||||
'fontSize' => 'font-size',
|
||||
'fontWeight' => 'font-weight',
|
||||
'borderWidth' => 'border-width',
|
||||
'borderStyle' => 'border-style',
|
||||
'borderColor' => 'border-color',
|
||||
|
@ -40,7 +40,7 @@
|
||||
padding-right: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.mailpoet_padded_header_footer {
|
||||
.mailpoet_header_footer_padded {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
@media screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) {
|
||||
|
114
lib/Newsletter/Scheduler/Scheduler.php
Normal file
114
lib/Newsletter/Scheduler/Scheduler.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter\Scheduler;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterOption;
|
||||
use MailPoet\Models\NewsletterOptionField;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
|
||||
class Scheduler {
|
||||
const seconds_in_hour = 3600;
|
||||
const last_weekday_format = 'L';
|
||||
|
||||
static function postNotification($newsletter_id) {
|
||||
$newsletter = Newsletter::filter('filterWithOptions')
|
||||
->findOne($newsletter_id)
|
||||
->asArray();
|
||||
$interval_type = $newsletter['intervalType'];
|
||||
$hour = (int) $newsletter['timeOfDay'] / self::seconds_in_hour;
|
||||
$week_day = $newsletter['weekDay'];
|
||||
$month_day = $newsletter['monthDay'];
|
||||
$nth_week_day = ($newsletter['nthWeekDay'] === self::last_weekday_format) ?
|
||||
$newsletter['nthWeekDay'] :
|
||||
'#' . $newsletter['nthWeekDay'];
|
||||
switch($interval_type) {
|
||||
case 'immediately':
|
||||
$cron = '* * * * *';
|
||||
break;
|
||||
case 'immediate': //daily
|
||||
$cron = sprintf('0 %s * * *', $hour);
|
||||
break;
|
||||
case 'weekly':
|
||||
$cron = sprintf('0 %s * * %s', $hour, $week_day);
|
||||
break;
|
||||
case 'monthly':
|
||||
$cron = sprintf('0 %s %s * *', $hour, $month_day);
|
||||
break;
|
||||
case 'nthWeekDay':
|
||||
$cron = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day);
|
||||
break;
|
||||
}
|
||||
$option_field = NewsletterOptionField::where('name', 'schedule')
|
||||
->findOne()
|
||||
->asArray();
|
||||
$relation = NewsletterOption::create();
|
||||
$relation->newsletter_id = $newsletter['id'];
|
||||
$relation->option_field_id = $option_field['id'];
|
||||
$relation->value = $cron;
|
||||
$relation->save();
|
||||
}
|
||||
|
||||
static function welcomeForSegmentSubscription($subscriber_id, array $segments) {
|
||||
$newsletters = self::getWelcomeNewsletters();
|
||||
if(!count($newsletters)) return;
|
||||
foreach($newsletters as $newsletter) {
|
||||
if($newsletter['event'] === 'segment' &&
|
||||
in_array($newsletter['segment'], $segments)
|
||||
) {
|
||||
self::createSendingQueueEntry($newsletter, $subscriber_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function welcomeForNewWPUser($subscriber_id, array $wp_user) {
|
||||
$newsletters = self::getWelcomeNewsletters();
|
||||
if(!count($newsletters)) return;
|
||||
foreach($newsletters as $newsletter) {
|
||||
if($newsletter['event'] === 'user' &&
|
||||
in_array($newsletter['role'], $wp_user['roles'])
|
||||
) {
|
||||
self::createSendingQueueEntry($newsletter, $subscriber_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function getWelcomeNewsletters() {
|
||||
return Newsletter::where('type', 'welcome')
|
||||
->filter('filterWithOptions')
|
||||
->findArray();
|
||||
}
|
||||
|
||||
private static function createSendingQueueEntry($newsletter, $subscriber_id) {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->newsletter_id = $newsletter['id'];
|
||||
$queue->subscribers = serialize(
|
||||
array(
|
||||
'to_process' => array($subscriber_id)
|
||||
)
|
||||
);
|
||||
$queue->count_total = $queue->count_to_process = 1;
|
||||
$after_time_type = $newsletter['afterTimeType'];
|
||||
$after_time_number = $newsletter['afterTimeNumber'];
|
||||
$scheduled_at = null;
|
||||
switch($after_time_type) {
|
||||
case 'hours':
|
||||
$scheduled_at = Carbon::now()
|
||||
->addHours($after_time_number);
|
||||
break;
|
||||
case 'days':
|
||||
$scheduled_at = Carbon::now()
|
||||
->addDays($after_time_number);
|
||||
break;
|
||||
case 'weeks':
|
||||
$scheduled_at = Carbon::now()
|
||||
->addWeeks($after_time_number);
|
||||
break;
|
||||
}
|
||||
if($scheduled_at) {
|
||||
$queue->status = 'scheduled';
|
||||
$queue->scheduled_at = $scheduled_at;
|
||||
}
|
||||
$queue->save();
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter\Shortcodes\Categories;
|
||||
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
|
||||
class Link {
|
||||
/*
|
||||
{
|
||||
text: '<%= __('Unsubscribe link') %>',
|
||||
shortcode: 'global:unsubscribe',
|
||||
},
|
||||
{
|
||||
text: '<%= __('Edit subscription page link') %>',
|
||||
shortcode: 'global:manage',
|
||||
},
|
||||
{
|
||||
text: '<%= __('View in browser link') %>',
|
||||
shortcode: 'global:browser',
|
||||
}
|
||||
*/
|
||||
static function process($action) {
|
||||
// TODO: implement
|
||||
$actions = array(
|
||||
'unsubscribe' => '',
|
||||
'manage' => '',
|
||||
'browser' => ''
|
||||
);
|
||||
return (isset($actions[$action])) ? $actions[$action] : false;
|
||||
}
|
||||
}
|
@ -18,6 +18,14 @@ class Newsletter {
|
||||
{
|
||||
text: '<%= __('Issue number') %>',
|
||||
shortcode: 'newsletter:number',
|
||||
},
|
||||
{
|
||||
text: '<%= __('Issue number') %>',
|
||||
shortcode: 'newsletter:number',
|
||||
},
|
||||
{
|
||||
text: '<%= __('View in browser link') %>',
|
||||
shortcode: 'newsletter:view_in_browser',
|
||||
}
|
||||
*/
|
||||
static function process($action, $default_value = false, $newsletter) {
|
||||
@ -27,17 +35,34 @@ class Newsletter {
|
||||
switch($action) {
|
||||
case 'subject':
|
||||
return ($newsletter) ? $newsletter['subject'] : false;
|
||||
break;
|
||||
|
||||
case 'total':
|
||||
$posts = wp_count_posts();
|
||||
return $posts->publish;
|
||||
break;
|
||||
|
||||
case 'post_title':
|
||||
$post = wp_get_recent_posts(array('numberposts' => 1));
|
||||
return (isset($post[0])) ? $post[0]['post_title'] : false;
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
// TODO: implement
|
||||
return;
|
||||
return 1;
|
||||
break;
|
||||
|
||||
case 'view_in_browser':
|
||||
return '<a href="#TODO">'.__('View in your browser').'</a>';
|
||||
break;
|
||||
|
||||
case 'view_in_browser_url':
|
||||
return '#TODO';
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
48
lib/Newsletter/Shortcodes/Categories/Subscription.php
Normal file
48
lib/Newsletter/Shortcodes/Categories/Subscription.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter\Shortcodes\Categories;
|
||||
use MailPoet\Subscription\Url as SubscriptionUrl;
|
||||
|
||||
class Subscription {
|
||||
/*
|
||||
{
|
||||
text: '<%= __('Unsubscribe') %>',-
|
||||
shortcode: 'subscription:unsubscribe',
|
||||
},
|
||||
{
|
||||
text: '<%= __('Manage subscriptions') %>',
|
||||
shortcode: 'subscription:manage',
|
||||
},
|
||||
*/
|
||||
static function process(
|
||||
$action,
|
||||
$default_value = false,
|
||||
$newsletter = false,
|
||||
$subscriber = false
|
||||
) {
|
||||
switch($action) {
|
||||
case 'unsubscribe':
|
||||
return '<a target="_blank" href="'.
|
||||
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
|
||||
.'">'.__('Unsubscribe').'</a>';
|
||||
break;
|
||||
|
||||
case 'unsubscribe_url':
|
||||
return SubscriptionUrl::getUnsubscribeUrl($subscriber);
|
||||
break;
|
||||
|
||||
case 'manage':
|
||||
return '<a target="_blank" href="'.
|
||||
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
|
||||
.'">'.__('Manage subscription').'</a>';
|
||||
break;
|
||||
|
||||
case 'manage_url':
|
||||
return SubscriptionUrl::getManageUrl($subscriber);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,20 +35,31 @@ class User {
|
||||
switch($action) {
|
||||
case 'firstname':
|
||||
return ($subscriber) ? $subscriber['first_name'] : $default_value;
|
||||
break;
|
||||
|
||||
case 'lastname':
|
||||
return ($subscriber) ? $subscriber['last_name'] : $default_value;
|
||||
break;
|
||||
|
||||
case 'email':
|
||||
return ($subscriber) ? $subscriber['email'] : false;
|
||||
break;
|
||||
|
||||
case 'displayname':
|
||||
if($subscriber && $subscriber['wp_user_id']) {
|
||||
$wp_user = get_userdata($subscriber['wp_user_id']);
|
||||
return $wp_user->user_login;
|
||||
};
|
||||
}
|
||||
return $default_value;
|
||||
break;
|
||||
|
||||
case 'count':
|
||||
return Subscriber::count();
|
||||
return Subscriber::filter('subscribed')->count();
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ class Shortcodes {
|
||||
function __construct(
|
||||
$rendered_newsletter,
|
||||
$newsletter = false,
|
||||
$subscriber = false) {
|
||||
$subscriber = false
|
||||
) {
|
||||
$this->rendered_newsletter = $rendered_newsletter;
|
||||
$this->newsletter = $newsletter;
|
||||
$this->subscriber = $subscriber;
|
||||
@ -23,13 +24,12 @@ class Shortcodes {
|
||||
function process($shortcodes) {
|
||||
$processed_shortcodes = array_map(
|
||||
function ($shortcode) {
|
||||
// TODO: discuss renaming "global". It is a reserved name in PHP.
|
||||
if($shortcode === 'global') $shortcode = 'link';
|
||||
preg_match(
|
||||
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
|
||||
$shortcode,
|
||||
$shortcode_details
|
||||
);
|
||||
|
||||
$shortcode_class =
|
||||
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
|
||||
if(!class_exists($shortcode_class)) return false;
|
||||
@ -41,7 +41,7 @@ class Shortcodes {
|
||||
$this->subscriber
|
||||
);
|
||||
}, $shortcodes);
|
||||
return array_filter($processed_shortcodes);
|
||||
return $processed_shortcodes;
|
||||
}
|
||||
|
||||
function replace() {
|
||||
|
@ -13,11 +13,10 @@ class Forms {
|
||||
|
||||
function get($id = false) {
|
||||
$form = Form::findOne($id);
|
||||
if($form === false) {
|
||||
return false;
|
||||
} else {
|
||||
return $form->asArray();
|
||||
if($form !== false) {
|
||||
$form = $form->asArray();
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
function listing($data = array()) {
|
||||
@ -29,19 +28,14 @@ class Forms {
|
||||
$listing_data = $listing->get();
|
||||
|
||||
// fetch segments relations for each returned item
|
||||
foreach($listing_data['items'] as &$item) {
|
||||
// form's segments
|
||||
$form_settings = (
|
||||
(is_serialized($item['settings']))
|
||||
? unserialize($item['settings'])
|
||||
: array()
|
||||
);
|
||||
|
||||
$item['segments'] = (
|
||||
!empty($form_settings['segments'])
|
||||
? $form_settings['segments']
|
||||
foreach($listing_data['items'] as $key => $form) {
|
||||
$form = $form->asArray();
|
||||
$form['segments'] = (
|
||||
!empty($form['settings']['segments'])
|
||||
? $form['settings']['segments']
|
||||
: array()
|
||||
);
|
||||
$listing_data['items'][$key] = $form;
|
||||
}
|
||||
|
||||
return $listing_data;
|
||||
|
@ -14,6 +14,7 @@ use MailPoet\Models\NewsletterOptionField;
|
||||
use MailPoet\Models\NewsletterOption;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -206,20 +207,11 @@ class Newsletters {
|
||||
|
||||
$listing_data = $listing->get();
|
||||
|
||||
foreach($listing_data['items'] as &$item) {
|
||||
// get segments
|
||||
$segments = NewsletterSegment::select('segment_id')
|
||||
->where('newsletter_id', $item['id'])
|
||||
->findMany();
|
||||
$item['segments'] = array_map(function($relation) {
|
||||
return $relation->segment_id;
|
||||
}, $segments);
|
||||
|
||||
// get queue
|
||||
$queue = SendingQueue::where('newsletter_id', $item['id'])
|
||||
->orderByDesc('updated_at')
|
||||
->findOne();
|
||||
$item['queue'] = ($queue !== false) ? $queue->asArray() : null;
|
||||
foreach($listing_data['items'] as $key => $newsletter) {
|
||||
$listing_data['items'][$key] = $newsletter
|
||||
->withSegments()
|
||||
->withSendingQueue()
|
||||
->asArray();
|
||||
}
|
||||
|
||||
return $listing_data;
|
||||
@ -274,6 +266,12 @@ class Newsletters {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!isset($data['id']) &&
|
||||
isset($data['type']) &&
|
||||
$data['type'] === 'notification'
|
||||
) {
|
||||
Scheduler::postNotification($newsletter->id);
|
||||
}
|
||||
return array(
|
||||
'result' => true,
|
||||
'newsletter' => $newsletter->asArray()
|
||||
|
@ -15,12 +15,26 @@ class Router {
|
||||
);
|
||||
add_action(
|
||||
'wp_ajax_mailpoet',
|
||||
array($this, 'setup')
|
||||
array($this, 'setupAdmin')
|
||||
);
|
||||
add_action(
|
||||
'wp_ajax_nopriv_mailpoet',
|
||||
array($this, 'setupPublic')
|
||||
);
|
||||
}
|
||||
|
||||
function setup() {
|
||||
$this->securityCheck();
|
||||
function setupAdmin() {
|
||||
$this->verifyToken();
|
||||
$this->checkPermissions();
|
||||
return $this->processRoute();
|
||||
}
|
||||
|
||||
function setupPublic() {
|
||||
$this->verifyToken();
|
||||
return $this->processRoute();
|
||||
}
|
||||
|
||||
function processRoute() {
|
||||
$class = ucfirst($_POST['endpoint']);
|
||||
$endpoint = __NAMESPACE__ . "\\" . $class;
|
||||
$method = $_POST['method'];
|
||||
@ -43,8 +57,11 @@ class Router {
|
||||
echo $global;
|
||||
}
|
||||
|
||||
function securityCheck() {
|
||||
function checkPermissions() {
|
||||
if(!current_user_can('manage_options')) { die(); }
|
||||
}
|
||||
|
||||
function verifyToken() {
|
||||
if(!wp_verify_nonce($_POST['token'], 'mailpoet_token')) { die(); }
|
||||
}
|
||||
}
|
||||
|
@ -30,36 +30,14 @@ class Segments {
|
||||
$listing_data = $listing->get();
|
||||
|
||||
// fetch segments relations for each returned item
|
||||
foreach($listing_data['items'] as &$item) {
|
||||
$stats = SubscriberSegment::table_alias('relation')
|
||||
->where(
|
||||
'relation.segment_id',
|
||||
$item['id']
|
||||
)
|
||||
->join(
|
||||
MP_SUBSCRIBERS_TABLE,
|
||||
'subscribers.id = relation.subscriber_id',
|
||||
'subscribers'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
|
||||
'subscribed'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
|
||||
'unsubscribed'
|
||||
)
|
||||
->select_expr(
|
||||
'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
|
||||
'unconfirmed'
|
||||
)
|
||||
->findOne()->asArray();
|
||||
|
||||
$item = array_merge($item, $stats);
|
||||
|
||||
$item['subscribers_url'] = admin_url(
|
||||
'admin.php?page=mailpoet-subscribers#/filter[segment='.$item['id'].']'
|
||||
foreach($listing_data['items'] as $key => $segment) {
|
||||
$segment->subscribers_url = admin_url(
|
||||
'admin.php?page=mailpoet-subscribers#/filter[segment='.$segment->id.']'
|
||||
);
|
||||
|
||||
$listing_data['items'][$key] = $segment
|
||||
->withSubscribersCount()
|
||||
->asArray();
|
||||
}
|
||||
|
||||
return $listing_data;
|
||||
|
@ -15,17 +15,14 @@ class Subscribers {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function get($id = false) {
|
||||
function get($id = null) {
|
||||
$subscriber = Subscriber::findOne($id);
|
||||
if($subscriber !== false && $subscriber->id() > 0) {
|
||||
$segments = $subscriber->segments()->findArray();
|
||||
|
||||
$subscriber = $subscriber->withCustomFields()->asArray();
|
||||
$subscriber['segments'] = array_map(function($segment) {
|
||||
return $segment['id'];
|
||||
}, $segments);
|
||||
if($subscriber !== false) {
|
||||
$subscriber = $subscriber
|
||||
->withCustomFields()
|
||||
->withSubscriptions()
|
||||
->asArray();
|
||||
}
|
||||
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
@ -38,19 +35,10 @@ class Subscribers {
|
||||
$listing_data = $listing->get();
|
||||
|
||||
// fetch segments relations for each returned item
|
||||
foreach($listing_data['items'] as &$item) {
|
||||
// avatar
|
||||
$item['avatar_url'] = get_avatar_url($item['email'], array(
|
||||
'size' => 32
|
||||
));
|
||||
|
||||
// subscriber's segments
|
||||
$relations = SubscriberSegment::select('segment_id')
|
||||
->where('subscriber_id', $item['id'])
|
||||
->findMany();
|
||||
$item['segments'] = array_map(function($relation) {
|
||||
return $relation->segment_id;
|
||||
}, $relations);
|
||||
foreach($listing_data['items'] as $key => $subscriber) {
|
||||
$listing_data['items'][$key] = $subscriber
|
||||
->withSubscriptions()
|
||||
->asArray();
|
||||
}
|
||||
|
||||
return $listing_data;
|
||||
@ -100,18 +88,10 @@ class Subscribers {
|
||||
}
|
||||
|
||||
$subscriber = Subscriber::subscribe($data, $segment_ids);
|
||||
|
||||
$result = false;
|
||||
if($subscriber === false || !$subscriber->id()) {
|
||||
$errors = array_merge($errors, $subscriber->getValidationErrors());
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
if(!empty($errors)) {
|
||||
if($subscriber->getErrors() !== false) {
|
||||
return array(
|
||||
'result' => false,
|
||||
'errors' => $errors
|
||||
'errors' => $subscriber->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,15 @@
|
||||
namespace MailPoet\Segments;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Models\Segment;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
|
||||
class WP {
|
||||
static function synchronizeUser($wp_user_id) {
|
||||
$wpUser = \get_userdata($wp_user_id);
|
||||
$wp_user = \get_userdata($wp_user_id);
|
||||
$segment = Segment::getWPUsers();
|
||||
|
||||
if($wpUser === false or $segment === false) return;
|
||||
|
||||
$subscriber = Subscriber::where('wp_user_id', $wpUser->ID)
|
||||
if($wp_user === false or $segment === false) return;
|
||||
$subscriber = Subscriber::where('wp_user_id', $wp_user->ID)
|
||||
->findOne();
|
||||
|
||||
switch(current_filter()) {
|
||||
case 'delete_user':
|
||||
case 'deleted_user':
|
||||
@ -21,22 +19,21 @@ class WP {
|
||||
$subscriber->delete();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user_register':
|
||||
$new_user = (!$subscriber) ? true : false;
|
||||
case 'added_existing_user':
|
||||
case 'profile_update':
|
||||
default:
|
||||
// get first name & last name
|
||||
$first_name = $wpUser->first_name;
|
||||
$last_name = $wpUser->last_name;
|
||||
if(empty($wpUser->first_name) && empty($wpUser->last_name)) {
|
||||
$first_name = $wpUser->display_name;
|
||||
$first_name = $wp_user->first_name;
|
||||
$last_name = $wp_user->last_name;
|
||||
if(empty($wp_user->first_name) && empty($wp_user->last_name)) {
|
||||
$first_name = $wp_user->display_name;
|
||||
}
|
||||
|
||||
// subscriber data
|
||||
$data = array(
|
||||
'wp_user_id'=> $wpUser->ID,
|
||||
'email' => $wpUser->user_email,
|
||||
'wp_user_id' => $wp_user->ID,
|
||||
'email' => $wp_user->user_email,
|
||||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
'status' => 'subscribed'
|
||||
@ -46,11 +43,16 @@ class WP {
|
||||
$data['id'] = $subscriber->id();
|
||||
}
|
||||
$subscriber = Subscriber::createOrUpdate($data);
|
||||
|
||||
if($subscriber->getErrors() === false && $subscriber->id > 0) {
|
||||
if($segment !== false) {
|
||||
$segment->addSubscriber($subscriber->id);
|
||||
}
|
||||
if(isset($new_user) && $new_user === true) {
|
||||
Scheduler::welcomeForNewWPUser(
|
||||
$subscriber->id,
|
||||
(array) $wp_user
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -13,13 +13,13 @@ class Pages {
|
||||
),
|
||||
'public' => true,
|
||||
'has_archive' => false,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => false,
|
||||
'show_ui' => WP_DEBUG,
|
||||
'show_in_menu' => WP_DEBUG,
|
||||
'rewrite' => false,
|
||||
'show_in_nav_menus'=>false,
|
||||
'can_export'=>false,
|
||||
'publicly_queryable'=>true,
|
||||
'exclude_from_search'=>true
|
||||
'show_in_nav_menus' => false,
|
||||
'can_export' => false,
|
||||
'publicly_queryable' => true,
|
||||
'exclude_from_search' => true
|
||||
));
|
||||
}
|
||||
|
||||
@ -65,8 +65,9 @@ class Pages {
|
||||
return array(
|
||||
'id' => $page->ID,
|
||||
'title' => $page->post_title,
|
||||
'preview_url' => get_permalink($page->ID),
|
||||
'edit_url' => get_edit_post_link($page->ID)
|
||||
'preview_url' => add_query_arg(array(
|
||||
'mailpoet_preview' => 1
|
||||
), get_permalink($page->ID))
|
||||
);
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ class BootStrapMenu {
|
||||
Segment::getSegmentsWithSubscriberCount() :
|
||||
Segment::getSegmentsForExport($with_confirmed_subscribers);
|
||||
return array_map(function($segment) {
|
||||
if (!$segment['name']) $segment['name'] = __('Not In Segment');
|
||||
if (!$segment['id']) $segment['id'] = 0;
|
||||
return array(
|
||||
'id' => $segment['id'],
|
||||
'name' => $segment['name'],
|
||||
@ -31,7 +33,7 @@ class BootStrapMenu {
|
||||
'first_name' => __('First name'),
|
||||
'last_name' => __('Last name'),
|
||||
'status' => __('Status')
|
||||
// TODO: add additional fiels from MP2
|
||||
// TODO: add additional fields from MP2
|
||||
/*
|
||||
'confirmed_ip' => __('IP address')
|
||||
'confirmed_at' => __('Subscription date')
|
||||
|
@ -17,22 +17,30 @@ class Export {
|
||||
public $segments;
|
||||
public $subscribers_without_segment;
|
||||
public $subscriber_fields;
|
||||
public $subscriber_custom_fields;
|
||||
public $formatted_subscriber_fields;
|
||||
public $export_path;
|
||||
public $export_file;
|
||||
public $export_file_URL;
|
||||
public $profiler_start;
|
||||
public $subscriber_batch_size;
|
||||
|
||||
public function __construct($data) {
|
||||
set_time_limit(0);
|
||||
$this->export_confirmed_option = $data['export_confirmed_option'];
|
||||
$this->export_format_option = $data['export_format_option'];
|
||||
$this->group_by_segment_option = $data['group_by_segment_option'];
|
||||
$this->segments = $data['segments'];
|
||||
$this->subscribers_without_segment = array_search(0, $this->segments);
|
||||
$this->subscriber_fields = $data['subscriber_fields'];
|
||||
$this->subscriber_custom_fields = $this->getSubscriberCustomFields();
|
||||
$this->formatted_subscriber_fields = $this->formatSubscriberFields(
|
||||
$this->subscriber_fields,
|
||||
$this->subscriber_custom_fields
|
||||
);
|
||||
$this->export_path = Env::$temp_path;
|
||||
$this->export_file = $this->getExportFile($this->export_format_option);
|
||||
$this->export_file_URL = $this->getExportFileURL($this->export_file);
|
||||
$this->profiler_start = microtime(true);
|
||||
$this->subscriber_batch_size = 15000;
|
||||
}
|
||||
|
||||
function process() {
|
||||
@ -40,13 +48,31 @@ class Export {
|
||||
if(is_writable($this->export_path) === false) {
|
||||
throw new \Exception(__("Couldn't save export file on the server."));
|
||||
}
|
||||
$subscribers = $this->getSubscribers();
|
||||
$subscriber_custom_fields = $this->getSubscriberCustomFields();
|
||||
$formatted_subscriber_fields = $this->formatSubscriberFields(
|
||||
$this->subscriber_fields,
|
||||
$subscriber_custom_fields
|
||||
$processed_subscribers = call_user_func(
|
||||
array(
|
||||
$this,
|
||||
'generate' . strtoupper($this->export_format_option)
|
||||
)
|
||||
);
|
||||
if($this->export_format_option === 'csv') {
|
||||
} catch(\Exception $e) {
|
||||
return array(
|
||||
'result' => false,
|
||||
'errors' => array($e->getMessage())
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'result' => true,
|
||||
'data' => array(
|
||||
'totalExported' => $processed_subscribers,
|
||||
'exportFileURL' => $this->export_file_URL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function generateCSV() {
|
||||
$processed_subscribers = 0;
|
||||
$offset = 0;
|
||||
$formatted_subscriber_fields = $this->formatted_subscriber_fields;
|
||||
$CSV_file = fopen($this->export_file, 'w');
|
||||
$format_CSV = function($row) {
|
||||
return '"' . str_replace('"', '\"', $row) . '"';
|
||||
@ -65,8 +91,11 @@ class Export {
|
||||
$format_CSV,
|
||||
$formatted_subscriber_fields
|
||||
)
|
||||
) . "\n"
|
||||
) . PHP_EOL
|
||||
);
|
||||
do {
|
||||
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
|
||||
$processed_subscribers += count($subscribers);
|
||||
foreach($subscribers as $subscriber) {
|
||||
$row = $this->formatSubscriberData($subscriber);
|
||||
if($this->group_by_segment_option) {
|
||||
@ -74,58 +103,78 @@ class Export {
|
||||
}
|
||||
fwrite($CSV_file, implode(',', array_map($format_CSV, $row)) . "\n");
|
||||
}
|
||||
$offset += $this->subscriber_batch_size;
|
||||
} while(count($subscribers) === $this->subscriber_batch_size);
|
||||
fclose($CSV_file);
|
||||
} else {
|
||||
$writer = new XLSXWriter();
|
||||
$writer->setAuthor('MailPoet (www.mailpoet.com)');
|
||||
$header_row = array($formatted_subscriber_fields);
|
||||
$last_segment = false;
|
||||
$rows = array();
|
||||
foreach($subscribers as $subscriber) {
|
||||
if($last_segment && $last_segment !== $subscriber['segment_name'] &&
|
||||
$this->group_by_segment_option
|
||||
) {
|
||||
$writer->writeSheet(
|
||||
array_merge($header_row, $rows), ucwords($last_segment)
|
||||
);
|
||||
$rows = array();
|
||||
return $processed_subscribers;
|
||||
}
|
||||
|
||||
function generateXLSX() {
|
||||
$processed_subscribers = 0;
|
||||
$offset = 0;
|
||||
$XLSX_writer = new XLSXWriter();
|
||||
$XLSX_writer->setAuthor('MailPoet (www.mailpoet.com)');
|
||||
$last_segment = false;
|
||||
$processed_segments = array();
|
||||
do {
|
||||
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
|
||||
$processed_subscribers += count($subscribers);
|
||||
foreach($subscribers as $i => $subscriber) {
|
||||
$current_segment = ucwords($subscriber['segment_name']);
|
||||
// Sheet header (1st row) will be written only if:
|
||||
// * This is the first time we're processing a segment
|
||||
// * "Group by subscriber option" is turned AND the previous subscriber's
|
||||
// segment is different from the current subscriber's segment
|
||||
// Header will NOT be written if:
|
||||
// * We have already processed the segment. Because SQL results are not
|
||||
// sorted by segment name (due to slow queries when using ORDER BY and LIMIT),
|
||||
// we need to keep track of processed segments so that we do not create header
|
||||
// multiple times when switching from one segment to another and back.
|
||||
if((!count($processed_segments) ||
|
||||
($last_segment !== $current_segment && $this->group_by_segment_option)
|
||||
) &&
|
||||
(!in_array($last_segment, $processed_segments) ||
|
||||
!in_array($current_segment, $processed_segments)
|
||||
)
|
||||
) {
|
||||
$this->writeXLSX(
|
||||
$XLSX_writer,
|
||||
$subscriber['segment_name'],
|
||||
$this->formatted_subscriber_fields
|
||||
);
|
||||
$processed_segments[] = $current_segment;
|
||||
}
|
||||
$last_segment = ucwords($subscriber['segment_name']);
|
||||
// detect RTL language and set Excel to properly display the sheet
|
||||
$RTL_regex = '/\p{Arabic}|\p{Hebrew}/u';
|
||||
if(!$writer->rtl && (
|
||||
if(!$XLSX_writer->rtl && (
|
||||
preg_grep($RTL_regex, $subscriber) ||
|
||||
preg_grep($RTL_regex, $formatted_subscriber_fields))
|
||||
preg_grep($RTL_regex, $this->formatted_subscriber_fields))
|
||||
) {
|
||||
$writer->rtl = true;
|
||||
$XLSX_writer->rtl = true;
|
||||
}
|
||||
$rows[] = $this->formatSubscriberData($subscriber);
|
||||
$last_segment = $subscriber['segment_name'];
|
||||
$this->writeXLSX(
|
||||
$XLSX_writer,
|
||||
$last_segment,
|
||||
$this->formatSubscriberData($subscriber)
|
||||
);
|
||||
}
|
||||
$writer->writeSheet(
|
||||
array_merge($header_row, $rows),
|
||||
$offset += $this->subscriber_batch_size;
|
||||
} while(count($subscribers) === $this->subscriber_batch_size);
|
||||
$XLSX_writer->writeToFile($this->export_file);
|
||||
return $processed_subscribers;
|
||||
}
|
||||
|
||||
function writeXLSX($XLSX_writer, $segment, $data) {
|
||||
return $XLSX_writer->writeSheetRow(
|
||||
($this->group_by_segment_option) ?
|
||||
ucwords($subscriber['segment_name']) :
|
||||
__('All Segments')
|
||||
);
|
||||
$writer->writeToFile($this->export_file);
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
return array(
|
||||
'result' => false,
|
||||
'errors' => array($e->getMessage())
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'result' => true,
|
||||
'data' => array(
|
||||
'totalExported' => count($subscribers),
|
||||
'exportFileURL' => $this->export_file_URL
|
||||
),
|
||||
'profiler' => $this->timeExecution()
|
||||
ucwords($segment) :
|
||||
__('All Segments'),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
function getSubscribers() {
|
||||
function getSubscribers($offset, $limit) {
|
||||
$subscribers = Subscriber::
|
||||
left_outer_join(
|
||||
SubscriberSegment::$_table,
|
||||
@ -141,7 +190,6 @@ class Export {
|
||||
'=',
|
||||
SubscriberSegment::$_table . '.segment_id'
|
||||
))
|
||||
->orderByAsc('segment_name')
|
||||
->filter('filterWithCustomFieldsForExport');
|
||||
if($this->subscribers_without_segment !== false) {
|
||||
$subscribers = $subscribers
|
||||
@ -168,9 +216,11 @@ class Export {
|
||||
$subscribers =
|
||||
$subscribers->where(Subscriber::$_table . '.status', 'subscribed');
|
||||
}
|
||||
$subscribers = $subscribers->whereNull(Subscriber::$_table . '.deleted_at');
|
||||
|
||||
return $subscribers->findArray();
|
||||
$subscribers = $subscribers
|
||||
->whereNull(Subscriber::$_table . '.deleted_at')
|
||||
->limit(sprintf('%d, %d', $offset, $limit))
|
||||
->findArray();
|
||||
return $subscribers;
|
||||
}
|
||||
|
||||
function getExportFileURL($file) {
|
||||
@ -216,9 +266,4 @@ class Export {
|
||||
return $subscriber[$field];
|
||||
}, $this->subscriber_fields);
|
||||
}
|
||||
|
||||
function timeExecution() {
|
||||
$profiler_end = microtime(true);
|
||||
return ($profiler_end - $this->profiler_start) / 60;
|
||||
}
|
||||
}
|
@ -14,21 +14,26 @@ class Import {
|
||||
public $subscriber_fields;
|
||||
public $subscriber_custom_fields;
|
||||
public $subscribers_count;
|
||||
public $import_time;
|
||||
public $created_at;
|
||||
public $updated_at;
|
||||
public $profiler_start;
|
||||
|
||||
public function __construct($data) {
|
||||
$this->subscribers_data = $data['subscribers'];
|
||||
$this->subscribers_data = $this->transformSubscribersData(
|
||||
$data['subscribers'],
|
||||
$data['columns']
|
||||
);
|
||||
$this->segments = $data['segments'];
|
||||
$this->update_subscribers = $data['updateSubscribers'];
|
||||
$this->subscriber_fields = $this->getSubscriberFields(
|
||||
array_keys($this->subscribers_data)
|
||||
array_keys($data['columns'])
|
||||
);
|
||||
$this->subscriber_custom_fields = $this->getCustomSubscriberFields(
|
||||
array_keys($this->subscribers_data)
|
||||
array_keys($data['columns'])
|
||||
);
|
||||
$this->subscribers_count = count(reset($this->subscribers_data));
|
||||
$this->import_time = date('Y-m-d H:i:s');
|
||||
$this->created_at = date('Y-m-d H:i:s', (int) $data['timestamp']);
|
||||
$this->updated_at = date('Y-m-d H:i:s', (int) $data['timestamp'] + 1);
|
||||
$this->profiler_start = microtime(true);
|
||||
}
|
||||
|
||||
@ -63,14 +68,6 @@ class Import {
|
||||
$subscriber_fields,
|
||||
$subscriber_custom_fields
|
||||
);
|
||||
if($created_subscribers) {
|
||||
// subtract added from updated subscribers when DB operation takes <1s
|
||||
$updated_subscribers = array_diff_key(
|
||||
$updated_subscribers,
|
||||
$created_subscribers,
|
||||
$subscriber_custom_fields
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch(\PDOException $e) {
|
||||
return array(
|
||||
@ -90,14 +87,22 @@ class Import {
|
||||
);
|
||||
}
|
||||
|
||||
function transformSubscribersData($subscribers, $columns) {
|
||||
foreach($columns as $column => $index) {
|
||||
$transformed_subscribers[$column] = Helpers::arrayColumn($subscribers, $index);
|
||||
}
|
||||
return $transformed_subscribers;
|
||||
}
|
||||
|
||||
function filterExistingAndNewSubscribers($subscribers_data) {
|
||||
$chunk_size = 200;
|
||||
$existing_records = array_filter(
|
||||
array_map(function($subscriber_emails) {
|
||||
return Subscriber::selectMany(array('email'))
|
||||
->whereIn('email', $subscriber_emails)
|
||||
->whereNull('deleted_at')
|
||||
->findArray();
|
||||
}, array_chunk($subscribers_data['email'], 200))
|
||||
}, array_chunk($subscribers_data['email'], $chunk_size))
|
||||
);
|
||||
if(!$existing_records) {
|
||||
return array(
|
||||
@ -144,17 +149,19 @@ class Import {
|
||||
}
|
||||
|
||||
function deleteExistingTrashedSubscribers($subscribers_data) {
|
||||
$chunk_size = 200;
|
||||
$existing_trashed_records = array_filter(
|
||||
array_map(function($subscriber_emails) {
|
||||
return Subscriber::selectMany(array('id'))
|
||||
->whereIn('email', $subscriber_emails)
|
||||
->whereNotNull('deleted_at')
|
||||
->findArray();
|
||||
}, array_chunk($subscribers_data['email'], 200))
|
||||
}, array_chunk($subscribers_data['email'], $chunk_size))
|
||||
);
|
||||
if(!$existing_trashed_records) return;
|
||||
$existing_trashed_records = Helpers::flattenArray($existing_trashed_records);
|
||||
foreach(array_chunk($existing_trashed_records, 200) as $subscriber_ids) {
|
||||
foreach(array_chunk($existing_trashed_records, $chunk_size) as
|
||||
$subscriber_ids) {
|
||||
Subscriber::whereIn('id', $subscriber_ids)
|
||||
->deleteMany();
|
||||
SubscriberSegment::whereIn('subscriber_id', $subscriber_ids)
|
||||
@ -163,7 +170,8 @@ class Import {
|
||||
}
|
||||
|
||||
function extendSubscribersAndFields($subscribers_data, $subscriber_fields) {
|
||||
$subscribers_data['created_at'] = $this->filterSubscriberCreatedAtDate();
|
||||
$subscribers_data['created_at'] =
|
||||
array_fill(0, $this->subscribers_count, $this->created_at);
|
||||
$subscriber_fields[] = 'created_at';
|
||||
return array(
|
||||
$subscribers_data,
|
||||
@ -191,10 +199,6 @@ class Import {
|
||||
);
|
||||
}
|
||||
|
||||
function filterSubscriberCreatedAtDate() {
|
||||
return array_fill(0, $this->subscribers_count, $this->import_time);
|
||||
}
|
||||
|
||||
function filterSubscriberStatus($subscribers_data, $subscriber_fields) {
|
||||
if(!in_array('status', $subscriber_fields)) {
|
||||
$subscribers_data['status'] =
|
||||
@ -249,14 +253,14 @@ class Import {
|
||||
$subscriber_fields,
|
||||
$subscriber_custom_fields
|
||||
) {
|
||||
$chunk_size = 100;
|
||||
$subscribers_count = count(reset($subscribers_data)) - 1;
|
||||
$subscribers = array_map(function($index) use ($subscribers_data, $subscriber_fields) {
|
||||
return array_map(function($field) use ($index, $subscribers_data) {
|
||||
return $subscribers_data[$field][$index];
|
||||
}, $subscriber_fields);
|
||||
}, range(0, $subscribers_count));
|
||||
$import_time = ($action === 'update') ? date('Y-m-d H:i:s') : $this->import_time;
|
||||
foreach(array_chunk($subscribers, 100) as $data) {
|
||||
foreach(array_chunk($subscribers, $chunk_size) as $data) {
|
||||
if($action == 'create') {
|
||||
Subscriber::createMultiple(
|
||||
$subscriber_fields,
|
||||
@ -267,18 +271,20 @@ class Import {
|
||||
Subscriber::updateMultiple(
|
||||
$subscriber_fields,
|
||||
$data,
|
||||
$import_time
|
||||
$this->updated_at
|
||||
);
|
||||
}
|
||||
}
|
||||
$result = Helpers::arrayColumn( // return id=>email array of results
|
||||
Subscriber::selectMany(
|
||||
$query = Subscriber::selectMany(
|
||||
array(
|
||||
'id',
|
||||
'email'
|
||||
))
|
||||
->where(($action === 'create') ? 'created_at' : 'updated_at', $import_time)
|
||||
->findArray(),
|
||||
));
|
||||
$query = ($action === 'update') ?
|
||||
$query->where('updated_at', $this->updated_at) :
|
||||
$query->where('created_at', $this->created_at);
|
||||
$result = Helpers::arrayColumn(
|
||||
$query->findArray(),
|
||||
'email', 'id'
|
||||
);
|
||||
if($subscriber_custom_fields) {
|
||||
|
345
lib/Subscription/Pages.php
Normal file
345
lib/Subscription/Pages.php
Normal file
@ -0,0 +1,345 @@
|
||||
<?php
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
use \MailPoet\Router\Subscribers;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Models\CustomField;
|
||||
use \MailPoet\Models\Setting;
|
||||
use \MailPoet\Models\Segment;
|
||||
use \MailPoet\Util\Helpers;
|
||||
use \MailPoet\Util\Url;
|
||||
use \MailPoet\Subscription;
|
||||
|
||||
class Pages {
|
||||
const DEMO_EMAIL = 'demo@mailpoet.com';
|
||||
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
$action = $this->getAction();
|
||||
if($action !== null) {
|
||||
add_filter('document_title_parts', array($this,'setWindowTitle'), 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')
|
||||
);
|
||||
}
|
||||
|
||||
function subscriberSave() {
|
||||
$action = (isset($_POST['action']) ? $_POST['action'] : null);
|
||||
if($action !== 'mailpoet_subscriber_save') {
|
||||
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'] !== self::DEMO_EMAIL) {
|
||||
$subscriber = Subscriber::createOrUpdate($subscriber_data);
|
||||
$errors = $subscriber->getErrors();
|
||||
}
|
||||
}
|
||||
// TBD: success/error messages (not present in MP2)
|
||||
|
||||
Url::redirectBack();
|
||||
}
|
||||
|
||||
function isPreview() {
|
||||
return (array_key_exists('mailpoet_preview', $_GET));
|
||||
}
|
||||
|
||||
function setWindowTitle($meta = array()) {
|
||||
$meta['title'] = $this->setPageTitle($meta['title']);
|
||||
return $meta;
|
||||
}
|
||||
|
||||
function setPageTitle($page_title = '') {
|
||||
global $post;
|
||||
|
||||
if($post->post_type === 'mailpoet_page') {
|
||||
$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]') {
|
||||
$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) {
|
||||
if($this->isPreview()) {
|
||||
$title = sprintf(
|
||||
__("You've subscribed to: %s"),
|
||||
'demo 1, demo 2'
|
||||
);
|
||||
} else if($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());
|
||||
|
||||
if(empty($segment_names)) {
|
||||
$title = __("You've subscribed!");
|
||||
} else {
|
||||
$title = sprintf(
|
||||
__("You've subscribed to: %s"),
|
||||
join(', ', $segment_names)
|
||||
);
|
||||
}
|
||||
}
|
||||
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 getUnsubscribeTitle($subscriber) {
|
||||
if($this->isPreview() || $subscriber !== false) {
|
||||
return __("You've unsubscribed!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getConfirmContent($subscriber) {
|
||||
if($this->isPreview() || $subscriber !== false) {
|
||||
return __("Yup, we've added you to our list. You'll hear from us shortly.");
|
||||
}
|
||||
}
|
||||
|
||||
private function getManageContent($subscriber) {
|
||||
if($this->isPreview()) {
|
||||
$subscriber = Subscriber::create();
|
||||
$subscriber->hydrate(array(
|
||||
'email' => self::DEMO_EMAIL
|
||||
));
|
||||
} else if($subscriber !== false) {
|
||||
$subscriber = $subscriber
|
||||
->withCustomFields()
|
||||
->withSubscriptions();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
$custom_fields = array_map(function($custom_field) use($subscriber) {
|
||||
$custom_field->id = 'cf_'.$custom_field->id;
|
||||
$custom_field = $custom_field->asArray();
|
||||
$custom_field['params']['value'] = $subscriber->{$custom_field['id']};
|
||||
return $custom_field;
|
||||
}, CustomField::findMany());
|
||||
|
||||
$segment_ids = Setting::getValue('subscription.segments', array());
|
||||
if(!empty($segment_ids)) {
|
||||
$segments = Segment::getPublic()
|
||||
->whereIn('id', $segment_ids)
|
||||
->findMany();
|
||||
} else {
|
||||
$segments = Segment::getPublic()
|
||||
->findMany();
|
||||
}
|
||||
$subscribed_segment_ids = array();
|
||||
if(!empty($subscriber->subscriptions)) {
|
||||
foreach ($subscriber->subscriptions as $subscription) {
|
||||
if($subscription['status'] === Subscriber::STATUS_SUBSCRIBED) {
|
||||
$subscribed_segment_ids[] = $subscription['segment_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$segments = array_map(function($segment) use($subscribed_segment_ids) {
|
||||
return array(
|
||||
'id' => $segment->id,
|
||||
'name' => $segment->name,
|
||||
'is_checked' => in_array($segment->id, $subscribed_segment_ids)
|
||||
);
|
||||
}, $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',
|
||||
'params' => array(
|
||||
'label' => __('First name'),
|
||||
'value' => $subscriber->first_name
|
||||
)
|
||||
),
|
||||
array(
|
||||
'id' => 'last_name',
|
||||
'type' => 'text',
|
||||
'params' => array(
|
||||
'label' => __('Last name'),
|
||||
'value' => $subscriber->last_name
|
||||
)
|
||||
),
|
||||
array(
|
||||
'id' => 'status',
|
||||
'type' => 'select',
|
||||
'params' => array(
|
||||
'label' => __('Status'),
|
||||
'values' => array(
|
||||
array(
|
||||
'value' => array(
|
||||
Subscriber::STATUS_SUBSCRIBED => __('Subscribed')
|
||||
),
|
||||
'is_checked' => (
|
||||
$subscriber->status === Subscriber::STATUS_SUBSCRIBED
|
||||
)
|
||||
),
|
||||
array(
|
||||
'value' => array(
|
||||
Subscriber::STATUS_UNSUBSCRIBED => __('Unsubscribed')
|
||||
),
|
||||
'is_checked' => (
|
||||
$subscriber->status === Subscriber::STATUS_UNSUBSCRIBED
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$form = array_merge(
|
||||
$fields,
|
||||
$custom_fields,
|
||||
array(
|
||||
array(
|
||||
'id' => 'segments',
|
||||
'type' => 'segment',
|
||||
'params' => array(
|
||||
'label' => __('Your lists'),
|
||||
'values' => $segments
|
||||
)
|
||||
),
|
||||
array(
|
||||
'id' => 'submit',
|
||||
'type' => 'submit',
|
||||
'params' => array(
|
||||
'label' => __('Save')
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$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="segments" value="" />';
|
||||
$form_html .= '<input type="hidden" name="mailpoet_redirect" '.
|
||||
'value="'.Url::getCurrentUrl().'" />';
|
||||
$form_html .= \MailPoet\Form\Renderer::renderBlocks($form);
|
||||
$form_html .= '</form>';
|
||||
return $form_html;
|
||||
}
|
||||
|
||||
private function getUnsubscribeContent($subscriber) {
|
||||
$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>';
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getSubscriber() {
|
||||
$token = (isset($_GET['mailpoet_token']))
|
||||
? $_GET['mailpoet_token']
|
||||
: null;
|
||||
$email = (isset($_GET['mailpoet_email']))
|
||||
? $_GET['mailpoet_email']
|
||||
: null;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
57
lib/Subscription/Url.php
Normal file
57
lib/Subscription/Url.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Models\Setting;
|
||||
|
||||
class Url {
|
||||
static function getConfirmationUrl($subscriber = false) {
|
||||
$post = get_post(Setting::getValue('subscription.confirmation_page'));
|
||||
return self::getSubscriptionUrl($post, 'confirm', $subscriber);
|
||||
}
|
||||
|
||||
static function getManageUrl($subscriber = false) {
|
||||
$post = get_post(Setting::getValue('subscription.manage_page'));
|
||||
return self::getSubscriptionUrl($post, 'manage', $subscriber);
|
||||
}
|
||||
|
||||
static function getUnsubscribeUrl($subscriber = false) {
|
||||
$post = get_post(Setting::getValue('subscription.unsubscribe_page'));
|
||||
return self::getSubscriptionUrl($post, 'unsubscribe', $subscriber);
|
||||
}
|
||||
|
||||
private static function getSubscriptionUrl(
|
||||
$post = null, $action = null, $subscriber = false
|
||||
) {
|
||||
if($post === null || $action === null) return;
|
||||
|
||||
$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']
|
||||
);
|
||||
} else {
|
||||
$params = array(
|
||||
'mailpoet_action='.$action,
|
||||
'mailpoet_preview=1'
|
||||
);
|
||||
}
|
||||
// add parameters
|
||||
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?').join('&', $params);
|
||||
|
||||
$url_params = parse_url($url);
|
||||
if(empty($url_params['scheme'])) {
|
||||
$url = get_bloginfo('url').$url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
@ -18,7 +18,13 @@ class i18n extends \Twig_Extension {
|
||||
// twig custom functions
|
||||
$twig_functions = array();
|
||||
// list of WP functions to map
|
||||
$functions = array('localize', '__', '_n');
|
||||
$functions = array(
|
||||
'localize',
|
||||
'__',
|
||||
'_n',
|
||||
'date',
|
||||
'date_format'
|
||||
);
|
||||
|
||||
foreach($functions as $function) {
|
||||
$twig_functions[] = new \Twig_SimpleFunction(
|
||||
@ -57,6 +63,23 @@ class i18n extends \Twig_Extension {
|
||||
return call_user_func_array('_n', $this->setTextDomain($args));
|
||||
}
|
||||
|
||||
function date() {
|
||||
$args = func_get_args();
|
||||
$date = (isset($args[0])) ? $args[0] : null;
|
||||
$date_format = (isset($args[1])) ? $args[1] : get_option('date_format');
|
||||
|
||||
if(empty($date)) return;
|
||||
|
||||
// check if it's an int passed as a string
|
||||
if((string)(int)$date === $date) {
|
||||
$date = (int)$date;
|
||||
} else if(!is_int($date)) {
|
||||
$date = strtotime($date);
|
||||
}
|
||||
|
||||
return get_date_from_gmt(date('Y-m-d H:i:s', $date), $date_format);
|
||||
}
|
||||
|
||||
private function setTextDomain($args = array()) {
|
||||
// make sure that the last argument is our text domain
|
||||
if($args[count($args) - 1] !== $this->_text_domain) {
|
||||
|
62
lib/Util/Url.php
Normal file
62
lib/Util/Url.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace MailPoet\Util;
|
||||
|
||||
class Url {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
static function getCurrentUrl() {
|
||||
global $wp;
|
||||
return home_url(
|
||||
add_query_arg(
|
||||
$wp->query_string,
|
||||
$wp->request
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static function redirectTo($url = null) {
|
||||
wp_safe_redirect($url);
|
||||
exit();
|
||||
}
|
||||
|
||||
static function redirectBack() {
|
||||
// check mailpoet_redirect parameter
|
||||
$referer = (isset($_POST['mailpoet_redirect'])
|
||||
? $_POST['mailpoet_redirect']
|
||||
: null
|
||||
);
|
||||
|
||||
// fallback: http referer
|
||||
if($referer === null) {
|
||||
if(!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$referer = $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: home_url
|
||||
if($referer === null) {
|
||||
$referer = home_url();
|
||||
}
|
||||
|
||||
if($referer !== null) {
|
||||
self::redirectTo($referer);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
static function redirectWithReferer($url = null) {
|
||||
$current_url = self::getCurrentUrl();
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'mailpoet_redirect' => urlencode($current_url)
|
||||
),
|
||||
$url
|
||||
);
|
||||
|
||||
if($url !== $current_url) {
|
||||
self::redirectTo($url);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
|
||||
use \MailPoet\Config\Initializer;
|
||||
/*
|
||||
* Plugin Name: MailPoet
|
||||
* Version: 0.0.17
|
||||
* Version: 0.0.20
|
||||
* 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.17');
|
||||
define('MAILPOET_VERSION', '0.0.20');
|
||||
|
||||
$initializer = new Initializer(array(
|
||||
'file' => __FILE__,
|
||||
|
@ -34,7 +34,8 @@
|
||||
"spectrum-colorpicker": "^1.6.2",
|
||||
"tinymce": "4.1.10",
|
||||
"underscore": "1.8.3",
|
||||
"velocity-animate": "1.2.3"
|
||||
"velocity-animate": "1.2.3",
|
||||
"xss": "^0.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expose-loader": "latest",
|
||||
@ -45,7 +46,7 @@
|
||||
"amd-inject-loader": "latest",
|
||||
"chai": "2.2.0",
|
||||
"chai-jq": "0.0.8",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-cli": "latest",
|
||||
"jquery": "2.1.4",
|
||||
"jsdom": "3.1.2",
|
||||
"mocha": "2.2.1",
|
||||
|
@ -60,6 +60,9 @@ module.exports = function (grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
grunt.loadNpmTasks( 'grunt-wp-i18n' );
|
||||
|
||||
// set base
|
||||
grunt.file.setBase(base_path);
|
||||
|
||||
|
3
tasks/makepot/node_modules/grunt-wp-i18n/vendor/wp-i18n-tools/extract.php
generated
vendored
3
tasks/makepot/node_modules/grunt-wp-i18n/vendor/wp-i18n-tools/extract.php
generated
vendored
@ -178,9 +178,10 @@ class StringExtractor {
|
||||
* - line - line number
|
||||
*/
|
||||
public function find_function_calls( $function_names, $code, $extension = '.php') {
|
||||
$function_calls = array();
|
||||
|
||||
if($extension === 'php') {
|
||||
$tokens = token_get_all( $code );
|
||||
$function_calls = array();
|
||||
$latest_comment = false;
|
||||
$in_func = false;
|
||||
foreach( $tokens as $token ) {
|
||||
|
@ -13,6 +13,8 @@ $models = array(
|
||||
'Newsletter',
|
||||
'NewsletterSegment',
|
||||
'NewsletterTemplate',
|
||||
'NewsletterOption',
|
||||
'NewsletterOptionField',
|
||||
'Segment',
|
||||
'SendingQueue',
|
||||
'Setting',
|
||||
@ -29,3 +31,60 @@ $destroy = function ($model) {
|
||||
$db->commit();
|
||||
};
|
||||
array_map($destroy, $models);
|
||||
|
||||
abstract class MailPoetTest extends \Codeception\TestCase\Test {
|
||||
protected $backupGlobals = true;
|
||||
protected $backupGlobalsBlacklist = array(
|
||||
'app',
|
||||
'post',
|
||||
'authordata',
|
||||
'currentday',
|
||||
'currentmonth',
|
||||
'page',
|
||||
'pages',
|
||||
'multipage',
|
||||
'more',
|
||||
'numpages',
|
||||
'is_iphone',
|
||||
'is_chrome',
|
||||
'is_safari',
|
||||
'is_NS4',
|
||||
'is_opera',
|
||||
'is_macIE',
|
||||
'is_winIE',
|
||||
'is_gecko',
|
||||
'is_lynx',
|
||||
'is_IE',
|
||||
'is_apache',
|
||||
'is_IIS',
|
||||
'is_iis7',
|
||||
'wp_version',
|
||||
'wp_db_version',
|
||||
'tinymce_version',
|
||||
'manifest_version',
|
||||
'required_php_version',
|
||||
'required_mysql_version',
|
||||
'super_admins',
|
||||
'wp_query',
|
||||
'wp_rewrite',
|
||||
'wp',
|
||||
'wpdb',
|
||||
'wp_locale',
|
||||
'wp_admin_bar',
|
||||
'wp_roles',
|
||||
'wp_meta_boxes',
|
||||
'wp_registered_sidebars',
|
||||
'wp_registered_widgets',
|
||||
'wp_registered_widget_controls',
|
||||
'wp_registered_widget_updates',
|
||||
'pagenow',
|
||||
'post_type',
|
||||
'allowedposttags',
|
||||
'allowedtags',
|
||||
'menu'
|
||||
);
|
||||
protected $backupStaticAttributes = false;
|
||||
protected $runTestInSeparateProcess = false;
|
||||
protected $preserveGlobalState = false;
|
||||
protected $inIsolation = false;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user