Compare commits
267 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4213437e9 | |||
386bdceed3 | |||
87eda71931 | |||
cef9f1dcf8 | |||
c78b2088eb | |||
150364de3a | |||
1cd9f3eb67 | |||
89253125af | |||
e7ee356f90 | |||
a88017400b | |||
f557881462 | |||
8dba4727c4 | |||
8ec094089f | |||
3c353e715b | |||
ab33a9c352 | |||
406b509ac4 | |||
bd814baf28 | |||
2db681d908 | |||
37e3af584e | |||
5fc863bd82 | |||
76649f9590 | |||
cb2faec8b2 | |||
e8604284fe | |||
d2ccdef6c7 | |||
38199dc96f | |||
630b219e96 | |||
64155bc121 | |||
7fb45a15ee | |||
ed5294477f | |||
d152b073a6 | |||
f8efb3934b | |||
5a21d3fdc8 | |||
710ede15ce | |||
150286ab6b | |||
9e758e8a33 | |||
059165e5d2 | |||
fe154d9251 | |||
a8ffbc2d0e | |||
9de3a245b0 | |||
5eef709af5 | |||
7b0c130d0a | |||
7f265675b0 | |||
ba15db9829 | |||
d15473a8e4 | |||
d9f93dc6e7 | |||
634c5b699d | |||
23e8ce38dd | |||
c62ae2ce80 | |||
d0813bb4e2 | |||
0ac701eb20 | |||
607395be6f | |||
55d48df8a4 | |||
e0282ae45b | |||
235fdea00f | |||
b8c6d54f48 | |||
67661e3aad | |||
c03facdc45 | |||
9ddc1ef555 | |||
9cfc2fd940 | |||
24e108bce7 | |||
48f0c03425 | |||
0bfbe6dc79 | |||
ad0a9838bc | |||
81ec293e54 | |||
b8b3d76a1d | |||
805e641d40 | |||
18326f9df1 | |||
46dda84012 | |||
9979261cb6 | |||
e8887e2aa5 | |||
4a91fae984 | |||
0fe975f614 | |||
c7fd7b8a32 | |||
b7e3c3ae81 | |||
b2c3206185 | |||
b7d8d482fe | |||
8a9d14319b | |||
c396254e64 | |||
e4dbeca664 | |||
168540d6d2 | |||
c62cd6c023 | |||
033e0581f1 | |||
9f978d3362 | |||
841340a42d | |||
9595e9629f | |||
56ba543f8d | |||
1cead6c6cd | |||
f5f7ce4c42 | |||
b13075b8f2 | |||
c4db9e3227 | |||
f47bfb5439 | |||
cc4639cb23 | |||
69094f57fd | |||
ffe7b80888 | |||
fc846b808e | |||
1cbf6b67b2 | |||
286c02bdd9 | |||
5f1d76225b | |||
c05ea1b968 | |||
2d45ab2e88 | |||
ca9b1e25a7 | |||
2927875e16 | |||
486a97fa30 | |||
c22d434dff | |||
306cdeb68f | |||
7ee83dad06 | |||
d414313749 | |||
66d329f630 | |||
f524ffcb28 | |||
264b7e180b | |||
88dc7f4199 | |||
9652f75028 | |||
36c32db2d1 | |||
fd12bd557e | |||
7bd8ed4639 | |||
ca0e511efd | |||
e5f3fabcda | |||
efc9bac760 | |||
ce6327c3d5 | |||
f32d6bb331 | |||
e807aad814 | |||
b87754ca30 | |||
22dfb372ec | |||
674bbd728e | |||
68c09b8678 | |||
c83ab0886f | |||
999a0b3ede | |||
6daecd6466 | |||
7af2775972 | |||
4bb1acf493 | |||
c8cd3d3eb5 | |||
2360c4d6e4 | |||
36e9168eef | |||
fb79d189d7 | |||
12330d6d34 | |||
5efbcfd9c1 | |||
75240fc2e1 | |||
b6fabcc739 | |||
269ddae93a | |||
90c3f0e4e4 | |||
dd8c54aae3 | |||
aa3a46b941 | |||
744455f0df | |||
9aa25446d1 | |||
6199caea29 | |||
d6a68dd4d0 | |||
ee6e261c42 | |||
cabfd8a946 | |||
cf712636ed | |||
873c3d15a0 | |||
bc1bd3bad1 | |||
9f971632c9 | |||
91bc0505ac | |||
90c94624cc | |||
cd412894c6 | |||
ecf15d53d9 | |||
a593347336 | |||
22566869cb | |||
c959e7ec96 | |||
86a2846215 | |||
3b97a26a8a | |||
dc6c973574 | |||
2a3a561464 | |||
e5f45fb7ad | |||
f22cadd319 | |||
5ea25ec697 | |||
c0a250fc0f | |||
e69aa792c4 | |||
3b4ac4d2d2 | |||
781973777e | |||
8698d2c6ba | |||
47c15eca83 | |||
64f4bed080 | |||
fe47ba8a38 | |||
eb02adc7ba | |||
f257b503e9 | |||
bfdabe3554 | |||
77dd71935a | |||
4b418f041b | |||
c8a0e006a0 | |||
359119d896 | |||
1a3c767601 | |||
6a97e82d42 | |||
3edfd32879 | |||
33bdde1156 | |||
710cab64c3 | |||
ed707b1738 | |||
398903e8b8 | |||
d590f5ea98 | |||
d6cbe5aac8 | |||
08e6430c7d | |||
945fe66bbb | |||
8e3eb2b795 | |||
52fbc0ee8a | |||
a355228b93 | |||
2cb0b3b071 | |||
bc1fb235d3 | |||
713dda913e | |||
d182638971 | |||
a5c620acf3 | |||
c176ad1d16 | |||
14c2b4d90f | |||
03eb4ad0fc | |||
ba9cd15651 | |||
329ec63dfd | |||
4925c7868e | |||
13d28d0aa7 | |||
c7d3c79fe3 | |||
1e9da724ea | |||
645d4e15ab | |||
cad5b242b2 | |||
99a81042c1 | |||
61987a204e | |||
a208104fc8 | |||
00ccc8adf4 | |||
df0ed9ce53 | |||
26d9b915a2 | |||
16cb91990b | |||
9642d3e672 | |||
aed60e6905 | |||
da7615ba4c | |||
3eb6a21980 | |||
b4e371302c | |||
e6724b1d4a | |||
2b6e87c3a7 | |||
b01ee80ec2 | |||
5d48ecac80 | |||
ebdb826011 | |||
9dc725e34d | |||
f47c331a5b | |||
b45c70f32b | |||
cf33d6f066 | |||
8292e9a744 | |||
3c46a5b434 | |||
4a4c4e093a | |||
4fa8a650b8 | |||
da755b7902 | |||
ceebb18bdf | |||
d10a29598d | |||
8c56c8da5e | |||
c4ddb38d18 | |||
15a21e5745 | |||
7df1a856ea | |||
69381205a2 | |||
9e0d8056b3 | |||
4b85c57436 | |||
377498be1d | |||
142421ad48 | |||
768115b794 | |||
8b9d76db8a | |||
f17c78fda2 | |||
3d45a8b7d4 | |||
3888241cbd | |||
603b6749de | |||
22918ecfd1 | |||
70ded73b51 | |||
da147047ec | |||
0e24174373 | |||
bc9b4eeb19 | |||
c6b13c5175 | |||
f754b1d1b2 | |||
bd5300d69a | |||
9996f3ef41 | |||
0f95d7bc8a | |||
14098643ae | |||
7c2d5a45c5 | |||
9d5902e179 |
20
RoboFile.php
20
RoboFile.php
@ -141,6 +141,26 @@ class RoboFile extends \Robo\Tasks {
|
||||
$this->_exec('vendor/bin/codecept run -g failed');
|
||||
}
|
||||
|
||||
function qaLint() {
|
||||
$this->_exec('./tasks/php_lint.sh lib/ tests/');
|
||||
}
|
||||
|
||||
function qaCodeSniffer($severity='errors') {
|
||||
if ($severity === 'all') {
|
||||
$severityFlag = '-w';
|
||||
} else {
|
||||
$severityFlag = '-n';
|
||||
}
|
||||
$this->_exec(
|
||||
'./vendor/bin/phpcs '.
|
||||
'--standard=./tasks/code_sniffer/MailPoet '.
|
||||
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
|
||||
'./lib/Config/PopulatorData/Templates/* '.
|
||||
'lib/ '.
|
||||
$severityFlag
|
||||
);
|
||||
}
|
||||
|
||||
protected function loadEnv() {
|
||||
$dotenv = new Dotenv\Dotenv(__DIR__);
|
||||
$dotenv->load();
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
@require 'form_editor'
|
||||
@require 'listing'
|
||||
@require 'listing/newsletters'
|
||||
|
||||
@require 'box'
|
||||
@require 'breadcrumb'
|
||||
|
||||
|
@ -27,13 +27,12 @@
|
||||
|
||||
img
|
||||
min-width: 150px
|
||||
min-height: 150px
|
||||
height: auto
|
||||
width: 110%
|
||||
position: relative
|
||||
top: 0
|
||||
top: 50%
|
||||
left: 50%
|
||||
transform: translate(-50%, 0%)
|
||||
transform: translate(-50%, -50%)
|
||||
|
||||
.mailpoet_overlay
|
||||
position: absolute
|
||||
|
3
assets/css/src/listing/newsletters.styl
Normal file
3
assets/css/src/listing/newsletters.styl
Normal file
@ -0,0 +1,3 @@
|
||||
#newsletters_container
|
||||
h2.nav-tab-wrapper
|
||||
margin-bottom: 1rem
|
@ -46,6 +46,7 @@ $master-column-tool-width = 24px
|
||||
opacity: 0
|
||||
overflow: hidden
|
||||
display: block
|
||||
margin: 0
|
||||
|
||||
.mailpoet_delete_block_activated
|
||||
width: auto
|
||||
@ -125,7 +126,6 @@ $master-column-tool-width = 24px
|
||||
border-radius(3px)
|
||||
background-color: $warning-background-color
|
||||
padding: 3px 5px
|
||||
line-height: 1.2em
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
overflow: hidden
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
.mailpoet_form_field_title
|
||||
clear: both
|
||||
font-size: 1.1em
|
||||
margin-bottom: 5px
|
||||
|
||||
.mailpoet_form_field_title_small
|
||||
|
@ -17,6 +17,7 @@ $widget-icon-width = 30px
|
||||
border-left: $content-border-color
|
||||
border-bottom: $content-border-color
|
||||
color: $sidebar-text-color
|
||||
font-size: $sidebar-text-size
|
||||
|
||||
.mailpoet_sidebar_region
|
||||
margin-bottom: 0
|
||||
|
@ -3,6 +3,7 @@ $sidepanel-active-heading-color = $primary-active-color
|
||||
/* Sidepanel */
|
||||
.mailpoet_editor_settings
|
||||
color: $sidebar-text-color
|
||||
font-size: $sidebar-text-size
|
||||
|
||||
p
|
||||
font-size: 1em
|
||||
@ -18,7 +19,6 @@ $sidepanel-active-heading-color = $primary-active-color
|
||||
|
||||
.mailpoet_sidepanel_field_title
|
||||
clear: both
|
||||
font-size: 1.1em
|
||||
margin-bottom: 5px
|
||||
|
||||
.mailpoet_sidepanel_field_title_small
|
||||
|
@ -23,6 +23,7 @@ $block-text-line-height = $text-line-height
|
||||
border: 1px solid $transparent-color
|
||||
|
||||
&:hover > .mailpoet_block_highlight
|
||||
&.mailpoet_highlight > .mailpoet_block_highlight
|
||||
border: 1px dashed $block-hover-highlight-color
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
&:hover
|
||||
&:hover > .mailpoet_block_highlight
|
||||
border: 0
|
||||
|
||||
.mailpoet_container_vertical > *
|
||||
|
@ -31,7 +31,7 @@ div.mce-toolbar-grp.mce-container
|
||||
box-shadow(0px 0px 3px 1px rgba(0, 0, 0, 0.05))
|
||||
|
||||
.mce-window
|
||||
/* Fix TinyMCE mailpoet_custom_fields window lack of hiding overflow */
|
||||
/* Fix TinyMCE mailpoet_shortcodes window lack of hiding overflow */
|
||||
div.mce-container-body.mce-abs-layout
|
||||
overflow: hidden
|
||||
|
||||
@ -40,8 +40,8 @@ div.mce-toolbar-grp.mce-container
|
||||
width: -webkit-calc( 100% - 36px )
|
||||
width: calc( 100% - 36px )
|
||||
|
||||
/* TinyMCE mailpoet_custom_fields toolbar icon */
|
||||
.mce-i-mailpoet_custom_fields:before
|
||||
/* TinyMCE mailpoet_shortcodes toolbar icon */
|
||||
.mce-i-mailpoet_shortcodes:before
|
||||
font: 400 20px/1 dashicons!important
|
||||
content: "\f307"
|
||||
|
||||
@ -84,7 +84,7 @@ position: relative
|
||||
body
|
||||
overflow-x: auto
|
||||
|
||||
/* Hide the "Details" section of Wordpress Media manager */
|
||||
/* Hide the "Details" section of WordPress Media manager */
|
||||
.media-sidebar
|
||||
display: none
|
||||
|
||||
@ -98,7 +98,7 @@ body
|
||||
.attachments-browser .uploader-inline
|
||||
right: 0
|
||||
|
||||
/* Remove max width from date selector in Wordpress Media Manager */
|
||||
/* Remove max width from date selector in WordPress Media Manager */
|
||||
#media-attachment-date-filters
|
||||
max-width: calc(100% - 12px)
|
||||
|
||||
|
@ -26,3 +26,4 @@ $error-text-color = #d54e21
|
||||
$newsletter-width = 660px
|
||||
|
||||
$text-line-height = 1.6em
|
||||
$sidebar-text-size = 13px
|
||||
|
@ -18,6 +18,7 @@ textarea.parsley-error
|
||||
list-style-type none
|
||||
font-size 0.9em
|
||||
line-height 0.9em
|
||||
color #B94A48
|
||||
opacity 0
|
||||
transition all .3s ease-in
|
||||
-o-transition all .3s ease-in
|
||||
|
@ -1,14 +1,10 @@
|
||||
#mailpoet_settings
|
||||
// common
|
||||
.mailpoet_panel
|
||||
display: none
|
||||
display none
|
||||
|
||||
.form-table th
|
||||
width:20em
|
||||
|
||||
// advanced
|
||||
#mailpoet_role_permissions
|
||||
margin-top: 20px;
|
||||
width 20em
|
||||
|
||||
// sending methods
|
||||
.mailpoet_sending_methods
|
||||
@ -28,8 +24,7 @@
|
||||
line-height 54px
|
||||
font-size 1.5em
|
||||
.mailpoet_description
|
||||
line-height 1.5em
|
||||
font-size 1.1em
|
||||
font-size 14px
|
||||
.mailpoet_status
|
||||
background-color #2f2f2f
|
||||
color #fff
|
||||
@ -60,11 +55,11 @@
|
||||
// responsive
|
||||
@media screen and (max-width: 782px)
|
||||
.form-table th
|
||||
width: auto
|
||||
width auto
|
||||
|
||||
.mailpoet_sending_methods
|
||||
li
|
||||
float none
|
||||
width: auto
|
||||
margin-right: 0
|
||||
width auto
|
||||
margin-right 0
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
../src/newsletter_editor/tinymce/mailpoet_custom_fields
|
1
assets/js/lib/mailpoet_shortcodes
Symbolic link
1
assets/js/lib/mailpoet_shortcodes
Symbolic link
@ -0,0 +1 @@
|
||||
../src/newsletter_editor/tinymce/mailpoet_shortcodes
|
@ -43,8 +43,9 @@ define('date',
|
||||
options = options || {};
|
||||
this.init(options);
|
||||
|
||||
return Moment(date, this.convertFormat(options.parseFormat))
|
||||
.format(this.convertFormat(this.options.format));
|
||||
var date = Moment(date, this.convertFormat(options.parseFormat));
|
||||
if (options.offset === 0) date = date.utc();
|
||||
return date.format(this.convertFormat(this.options.format));
|
||||
},
|
||||
toDate: function(date, options) {
|
||||
options = options || {};
|
||||
@ -68,7 +69,7 @@ define('date',
|
||||
});
|
||||
},
|
||||
convertFormat: function(format) {
|
||||
const format_mappings = {
|
||||
var format_mappings = {
|
||||
date: {
|
||||
D: 'ddd',
|
||||
l: 'dddd',
|
||||
@ -124,9 +125,9 @@ define('date',
|
||||
|
||||
if (!format || format.length <= 0) return format;
|
||||
|
||||
const replacements = format_mappings['date'];
|
||||
var replacements = format_mappings['date'];
|
||||
|
||||
let outputFormat = '';
|
||||
var outputFormat = '';
|
||||
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (format.indexOf(key) !== -1) {
|
||||
|
@ -6,7 +6,7 @@ function(
|
||||
) {
|
||||
const FormFieldCheckbox = React.createClass({
|
||||
onValueChange: function(e) {
|
||||
e.target.value = this.refs.checkbox.checked ? '1' : '';
|
||||
e.target.value = this.refs.checkbox.checked ? '1' : '0';
|
||||
return this.props.onValueChange(e);
|
||||
},
|
||||
render: function() {
|
||||
@ -14,7 +14,9 @@ function(
|
||||
return false;
|
||||
}
|
||||
|
||||
const isChecked = !!(this.props.item[this.props.field.name]);
|
||||
// isChecked will be true only if the value is "1"
|
||||
// it will be false in case value is "0" or empty
|
||||
const isChecked = !!(~~(this.props.item[this.props.field.name]));
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
|
@ -27,7 +27,7 @@ define([
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[year]' }
|
||||
name={ `${this.props.name}[year]` }
|
||||
value={ this.props.year }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
@ -57,7 +57,7 @@ define([
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[month]' }
|
||||
name={ `${this.props.name}[month]` }
|
||||
value={ this.props.month }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
@ -88,7 +88,7 @@ define([
|
||||
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[day]' }
|
||||
name={ `${this.props.name}[day]` }
|
||||
value={ this.props.day }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
@ -102,46 +102,99 @@ define([
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
day: undefined
|
||||
year: '',
|
||||
month: '',
|
||||
day: ''
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
this.extractDateParts();
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
(this.props.item !== undefined && prevProps.item !== undefined)
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
this.extractTimeStamp();
|
||||
this.extractDateParts();
|
||||
}
|
||||
}
|
||||
extractTimeStamp() {
|
||||
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
|
||||
extractDateParts() {
|
||||
const value = (this.props.item[this.props.field.name] !== undefined)
|
||||
? this.props.item[this.props.field.name].trim()
|
||||
: '';
|
||||
|
||||
if(value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateType = this.props.field.params.date_type;
|
||||
const dateParts = value.split('-');
|
||||
let year = '';
|
||||
let month = '';
|
||||
let day = '';
|
||||
|
||||
switch(dateType) {
|
||||
case 'year_month_day':
|
||||
year = ~~(dateParts[0]);
|
||||
month = ~~(dateParts[1]);
|
||||
day = ~~(dateParts[2]);
|
||||
break;
|
||||
|
||||
case 'year_month':
|
||||
year = ~~(dateParts[0]);
|
||||
month = ~~(dateParts[1]);
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
month = ~~(dateParts[0]);
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
year = ~~(dateParts[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
year: Moment.unix(timeStamp).year(),
|
||||
// Moment returns the month as [0..11]
|
||||
// We increment it to match PHP's mktime() which expects [1..12]
|
||||
month: Moment.unix(timeStamp).month() + 1,
|
||||
day: Moment.unix(timeStamp).date()
|
||||
year: year,
|
||||
month: month,
|
||||
day: day
|
||||
});
|
||||
}
|
||||
updateTimeStamp(field) {
|
||||
let newTimeStamp = Moment(
|
||||
`${this.state.month}/${this.state.day}/${this.state.year}`,
|
||||
'M/D/YYYY'
|
||||
).valueOf();
|
||||
if (~~(newTimeStamp) > 0) {
|
||||
// convert milliseconds to seconds
|
||||
newTimeStamp /= 1000;
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: newTimeStamp
|
||||
}
|
||||
});
|
||||
formatValue() {
|
||||
const dateType = this.props.field.params.date_type;
|
||||
|
||||
let value;
|
||||
|
||||
switch(dateType) {
|
||||
case 'year_month_day':
|
||||
value = {
|
||||
'year': this.state.year,
|
||||
'month': this.state.month,
|
||||
'day': this.state.day
|
||||
};
|
||||
break;
|
||||
|
||||
case 'year_month':
|
||||
value = {
|
||||
'year': this.state.year,
|
||||
'month': this.state.month
|
||||
};
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
value = {
|
||||
'month': this.state.month
|
||||
};
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
value = {
|
||||
'year': this.state.year
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
onValueChange(e) {
|
||||
// extract property from name
|
||||
@ -153,25 +206,29 @@ define([
|
||||
field = matches[1];
|
||||
property = matches[2];
|
||||
|
||||
let value = parseInt(e.target.value, 10);
|
||||
let value = ~~(e.target.value);
|
||||
|
||||
this.setState({
|
||||
[`${property}`]: value
|
||||
}, () => {
|
||||
this.updateTimeStamp(field);
|
||||
this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: this.formatValue()
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const monthNames = window.mailpoet_month_names || [];
|
||||
|
||||
const dateFormats = window.mailpoet_date_formats || {};
|
||||
const dateType = this.props.field.params.date_type;
|
||||
|
||||
const dateSelects = dateType.split('_');
|
||||
const dateSelects = dateFormats[dateType][0].split('/');
|
||||
|
||||
const fields = dateSelects.map(type => {
|
||||
switch(type) {
|
||||
case 'year':
|
||||
case 'yyyy':
|
||||
return (<FormFieldDateYear
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'year' }
|
||||
@ -182,7 +239,7 @@ define([
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
case 'mm':
|
||||
return (<FormFieldDateMonth
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'month' }
|
||||
@ -194,7 +251,7 @@ define([
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
case 'dd':
|
||||
return (<FormFieldDateDay
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'day' }
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import _ from 'underscore'
|
||||
|
||||
const FormFieldSelect = React.createClass({
|
||||
render() {
|
||||
@ -8,6 +9,7 @@ const FormFieldSelect = React.createClass({
|
||||
|
||||
let filter = false;
|
||||
let placeholder = false;
|
||||
let sortBy = false;
|
||||
|
||||
if (this.props.field.placeholder !== undefined) {
|
||||
placeholder = (
|
||||
@ -19,7 +21,27 @@ const FormFieldSelect = React.createClass({
|
||||
filter = this.props.field.filter;
|
||||
}
|
||||
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
if (_.isFunction(this.props.field.sortBy)) {
|
||||
sortBy = this.props.field.sortBy;
|
||||
}
|
||||
|
||||
let keys;
|
||||
if (sortBy) {
|
||||
// Extract keys from sorted [key, value] select value pairs, sorted by
|
||||
// provided sorting order.
|
||||
keys =
|
||||
_.map(
|
||||
_.sortBy(
|
||||
_.pairs(this.props.field.values),
|
||||
(item) => sortBy(item[0], item[1])
|
||||
),
|
||||
(item) => item[0]
|
||||
);
|
||||
} else {
|
||||
keys = Object.keys(this.props.field.values)
|
||||
}
|
||||
|
||||
const options = keys.map(
|
||||
(value, index) => {
|
||||
|
||||
if (filter !== false && filter(this.props.item, value) === false) {
|
||||
|
@ -13,8 +13,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('segments'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('segments')
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
@ -90,7 +89,7 @@ const item_actions = [
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate_form',
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(item, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
@ -98,9 +97,11 @@ const item_actions = [
|
||||
action: 'duplicate',
|
||||
data: item.id
|
||||
}).done(function(response) {
|
||||
if (response !== false && response['name'] !== undefined) {
|
||||
MailPoet.Notice.success(
|
||||
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
|
||||
);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
@ -47,13 +47,13 @@ function(
|
||||
|
||||
data.action = this.state.action;
|
||||
|
||||
var callback = function() {};
|
||||
var onSuccess = function() {};
|
||||
if(action['onSuccess'] !== undefined) {
|
||||
callback = action.onSuccess;
|
||||
onSuccess = action.onSuccess;
|
||||
}
|
||||
|
||||
if(data.action) {
|
||||
this.props.onBulkAction(selected_ids, data, callback);
|
||||
this.props.onBulkAction(selected_ids, data).then(onSuccess);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -10,10 +10,10 @@ function(
|
||||
) {
|
||||
var ListingFilters = React.createClass({
|
||||
handleFilterAction: function() {
|
||||
let filters = {}
|
||||
let filters = {};
|
||||
this.getAvailableFilters().map((filter, i) => {
|
||||
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
|
||||
})
|
||||
});
|
||||
return this.props.onSelectFilter(filters);
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
@ -21,7 +21,6 @@ function(
|
||||
},
|
||||
getAvailableFilters: function() {
|
||||
let filters = this.props.filters;
|
||||
|
||||
return Object.keys(filters).filter(function(filter) {
|
||||
return !(
|
||||
filters[filter].length === 0
|
||||
@ -30,26 +29,29 @@ function(
|
||||
&& !filters[filter][0].value
|
||||
)
|
||||
);
|
||||
})
|
||||
});
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
const selected_filters = this.props.filter;
|
||||
const available_filters = this.getAvailableFilters().map(
|
||||
function(filter, i) {
|
||||
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
jQuery(this.refs['filter-'+i])
|
||||
.val(selected_filters[filter])
|
||||
.trigger('change');
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
const filters = this.props.filters;
|
||||
const selected_filters = this.props.filter;
|
||||
|
||||
const available_filters = this.getAvailableFilters()
|
||||
.map(function(filter, i) {
|
||||
let default_value = false;
|
||||
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
default_value = selected_filters[filter]
|
||||
} else {
|
||||
jQuery(`select[name="${filter}"]`).val('');
|
||||
}
|
||||
return (
|
||||
<select
|
||||
ref={ `filter-${i}` }
|
||||
key={ `filter-${i}` }
|
||||
name={ filter }
|
||||
defaultValue={ default_value }
|
||||
>
|
||||
{ filters[filter].map(function(option, j) {
|
||||
return (
|
||||
@ -63,7 +65,7 @@ function(
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
let button = false;
|
||||
let button;
|
||||
|
||||
if (available_filters.length > 0) {
|
||||
button = (
|
||||
@ -76,7 +78,7 @@ function(
|
||||
);
|
||||
}
|
||||
|
||||
let empty_trash = false;
|
||||
let empty_trash;
|
||||
if (this.props.group === 'trash') {
|
||||
empty_trash = (
|
||||
<input
|
||||
|
@ -1,21 +1,15 @@
|
||||
define([
|
||||
'react',
|
||||
'classnames',
|
||||
'mailpoet'
|
||||
], function(
|
||||
React,
|
||||
classNames,
|
||||
MailPoet
|
||||
) {
|
||||
import MailPoet from 'mailpoet'
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
var ListingHeader = React.createClass({
|
||||
const ListingHeader = React.createClass({
|
||||
handleSelectItems: function() {
|
||||
return this.props.onSelectItems(
|
||||
this.refs.toggle.checked
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
var columns = this.props.columns.map(function(column, index) {
|
||||
const columns = this.props.columns.map(function(column, index) {
|
||||
column.is_primary = (index === 0);
|
||||
column.sorted = (this.props.sort_by === column.name)
|
||||
? this.props.sort_order
|
||||
@ -29,7 +23,7 @@ define([
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
var checkbox = false;
|
||||
let checkbox;
|
||||
|
||||
if(this.props.is_selectable === true) {
|
||||
checkbox = (
|
||||
@ -57,21 +51,21 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
var ListingColumn = React.createClass({
|
||||
const ListingColumn = React.createClass({
|
||||
handleSort: function() {
|
||||
var sort_by = this.props.column.name,
|
||||
sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
||||
const sort_by = this.props.column.name;
|
||||
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
||||
this.props.onSort(sort_by, sort_order);
|
||||
},
|
||||
render: function() {
|
||||
var classes = classNames(
|
||||
const classes = classNames(
|
||||
'manage-column',
|
||||
{ 'column-primary': this.props.column.is_primary },
|
||||
{ 'sortable': this.props.column.sortable },
|
||||
this.props.column.sorted,
|
||||
{ 'sorted': (this.props.sort_by === this.props.column.name) }
|
||||
);
|
||||
var label;
|
||||
let label;
|
||||
|
||||
if(this.props.column.sortable === true) {
|
||||
label = (
|
||||
@ -87,12 +81,11 @@ define([
|
||||
<th
|
||||
className={ classes }
|
||||
id={this.props.column.name }
|
||||
scope="col">
|
||||
{label}
|
||||
</th>
|
||||
scope="col"
|
||||
width={ this.props.column.width || null }
|
||||
>{label}</th>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return ListingHeader;
|
||||
});
|
||||
module.exports = ListingHeader;
|
@ -1,33 +1,16 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet',
|
||||
'jquery',
|
||||
'react',
|
||||
'react-router',
|
||||
'classnames',
|
||||
'listing/bulk_actions.jsx',
|
||||
'listing/header.jsx',
|
||||
'listing/pages.jsx',
|
||||
'listing/search.jsx',
|
||||
'listing/groups.jsx',
|
||||
'listing/filters.jsx'
|
||||
],
|
||||
function(
|
||||
MailPoet,
|
||||
jQuery,
|
||||
React,
|
||||
Router,
|
||||
classNames,
|
||||
ListingBulkActions,
|
||||
ListingHeader,
|
||||
ListingPages,
|
||||
ListingSearch,
|
||||
ListingGroups,
|
||||
ListingFilters
|
||||
) {
|
||||
var Link = Router.Link;
|
||||
import MailPoet from 'mailpoet'
|
||||
import jQuery from 'jquery'
|
||||
import React from 'react'
|
||||
import { Router, Link } from 'react-router'
|
||||
import classNames from 'classnames'
|
||||
import ListingBulkActions from 'listing/bulk_actions.jsx'
|
||||
import ListingHeader from 'listing/header.jsx'
|
||||
import ListingPages from 'listing/pages.jsx'
|
||||
import ListingSearch from 'listing/search.jsx'
|
||||
import ListingGroups from 'listing/groups.jsx'
|
||||
import ListingFilters from 'listing/filters.jsx'
|
||||
|
||||
var ListingItem = React.createClass({
|
||||
const ListingItem = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
toggled: true
|
||||
@ -150,8 +133,10 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
let actions;
|
||||
|
||||
if (this.props.group === 'trash') {
|
||||
var actions = (
|
||||
actions = (
|
||||
<div>
|
||||
<div className="row-actions">
|
||||
<span>
|
||||
@ -183,7 +168,7 @@ define(
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var actions = (
|
||||
actions = (
|
||||
<div>
|
||||
<div className="row-actions">
|
||||
{ item_actions }
|
||||
@ -197,7 +182,7 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
var row_classes = classNames({ 'is-expanded': !this.state.toggled })
|
||||
const row_classes = classNames({ 'is-expanded': !this.state.toggled });
|
||||
|
||||
return (
|
||||
<tr className={ row_classes }>
|
||||
@ -209,7 +194,7 @@ define(
|
||||
});
|
||||
|
||||
|
||||
var ListingItems = React.createClass({
|
||||
const ListingItems = React.createClass({
|
||||
render: function() {
|
||||
if (this.props.items.length === 0) {
|
||||
return (
|
||||
@ -231,8 +216,7 @@ define(
|
||||
</tbody>
|
||||
);
|
||||
} else {
|
||||
|
||||
var selectAllClasses = classNames(
|
||||
const select_all_classes = classNames(
|
||||
'mailpoet_select_all',
|
||||
{ 'mailpoet_hidden': (
|
||||
this.props.selection === false
|
||||
@ -243,7 +227,7 @@ define(
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
<tr className={ selectAllClasses }>
|
||||
<tr className={ select_all_classes }>
|
||||
<td colSpan={
|
||||
this.props.columns.length
|
||||
+ (this.props.is_selectable ? 1 : 0)
|
||||
@ -294,7 +278,7 @@ define(
|
||||
}
|
||||
});
|
||||
|
||||
var Listing = React.createClass({
|
||||
const Listing = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
@ -305,8 +289,8 @@ define(
|
||||
page: 1,
|
||||
count: 0,
|
||||
limit: 10,
|
||||
sort_by: 'id',
|
||||
sort_order: 'desc',
|
||||
sort_by: null,
|
||||
sort_order: null,
|
||||
items: [],
|
||||
groups: [],
|
||||
group: 'all',
|
||||
@ -316,61 +300,57 @@ define(
|
||||
selection: false
|
||||
};
|
||||
},
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
// reset group to "all" if trash gets emptied
|
||||
if(
|
||||
// we were viewing the trash
|
||||
(prevState.group === 'trash' && prevState.count > 0)
|
||||
&&
|
||||
// we are still viewing the trash but there are no items left
|
||||
(this.state.group === 'trash' && this.state.count === 0)
|
||||
&&
|
||||
// only do this when no filter is set
|
||||
(Object.keys(this.state.filter).length === 0)
|
||||
) {
|
||||
this.handleGroup('all');
|
||||
}
|
||||
},
|
||||
getParam: function(param) {
|
||||
var regex = /(.*)\[(.*)\]/
|
||||
var matches = regex.exec(param)
|
||||
const regex = /(.*)\[(.*)\]/;
|
||||
const matches = regex.exec(param);
|
||||
return [matches[1], matches[2]]
|
||||
},
|
||||
initWithParams: function(params) {
|
||||
let state = this.state || {}
|
||||
let original_state = state
|
||||
let state = this.getInitialState();
|
||||
|
||||
// check for url params
|
||||
if (params.splat !== undefined) {
|
||||
params.splat.split('/').map(param => {
|
||||
let [key, value] = this.getParam(param);
|
||||
switch(key) {
|
||||
case 'filter':
|
||||
let filters = {}
|
||||
let filters = {};
|
||||
value.split('&').map(function(pair) {
|
||||
let [k, v] = pair.split('=')
|
||||
filters[k] = v
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
state.filter = filters
|
||||
state.filter = filters;
|
||||
break;
|
||||
default:
|
||||
state[key] = value
|
||||
state[key] = value;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// default overrides
|
||||
// limit per page
|
||||
if (this.props.limit !== undefined) {
|
||||
state.limit = Math.abs(~~this.props.limit);
|
||||
}
|
||||
|
||||
// sort by
|
||||
if (state.sort_by === null && this.props.sort_by !== undefined) {
|
||||
state.sort_by = this.props.sort_by;
|
||||
}
|
||||
|
||||
// sort order
|
||||
if (state.sort_order === null && this.props.sort_order !== undefined) {
|
||||
state.sort_order = this.props.sort_order;
|
||||
}
|
||||
|
||||
this.setState(state, function() {
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
setParams: function() {
|
||||
var params = Object.keys(this.state)
|
||||
if (this.props.location) {
|
||||
let params = Object.keys(this.state)
|
||||
.filter(key => {
|
||||
return (
|
||||
[
|
||||
@ -384,22 +364,27 @@ define(
|
||||
)
|
||||
})
|
||||
.map(key => {
|
||||
let value = this.state[key]
|
||||
let value = this.state[key];
|
||||
if (value === Object(value)) {
|
||||
value = jQuery.param(value)
|
||||
} else if (value === Boolean(value)) {
|
||||
value = value.toString()
|
||||
}
|
||||
|
||||
if(value !== '') {
|
||||
if (value !== '' && value !== null) {
|
||||
return `${key}[${value}]`
|
||||
}
|
||||
})
|
||||
.filter(key => { return (key !== undefined) })
|
||||
.join('/');
|
||||
params = '/'+params
|
||||
|
||||
if(this.props.location) {
|
||||
// prepend url with "tab" if specified
|
||||
if (this.props.tab !== undefined) {
|
||||
params = `/${ this.props.tab }/${ params }`;
|
||||
} else {
|
||||
params = `/${ params }`;
|
||||
}
|
||||
|
||||
if (this.props.location.pathname !== params) {
|
||||
this.context.router.push(`${params}`);
|
||||
}
|
||||
@ -407,8 +392,8 @@ define(
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if (this.isMounted()) {
|
||||
const params = this.props.params || {}
|
||||
this.initWithParams(params)
|
||||
const params = this.props.params || {};
|
||||
this.initWithParams(params);
|
||||
|
||||
if (this.props.auto_refresh) {
|
||||
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
|
||||
@ -418,8 +403,8 @@ define(
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
const params = nextProps.params || {}
|
||||
this.initWithParams(params)
|
||||
const params = nextProps.params || {};
|
||||
this.initWithParams(params);
|
||||
},
|
||||
getItems: function() {
|
||||
if (this.isMounted()) {
|
||||
@ -431,6 +416,7 @@ define(
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'listing',
|
||||
data: {
|
||||
tab: (this.props.tab) ? this.props.tab : '',
|
||||
offset: (this.state.page - 1) * this.state.limit,
|
||||
limit: this.state.limit,
|
||||
group: this.state.group,
|
||||
@ -448,9 +434,10 @@ define(
|
||||
loading: false
|
||||
}, function() {
|
||||
if (this.props['onGetItems'] !== undefined) {
|
||||
this.props.onGetItems(
|
||||
~~(this.state.groups[0]['count'])
|
||||
);
|
||||
const count = (response.groups[0] !== undefined)
|
||||
? ~~(response.groups[0].count)
|
||||
: 0;
|
||||
this.props.onGetItems(count);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
@ -517,16 +504,21 @@ define(
|
||||
}.bind(this));
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
this.handleBulkAction('all', {
|
||||
return this.handleBulkAction('all', {
|
||||
action: 'delete',
|
||||
group: 'trash'
|
||||
}, function(response) {
|
||||
}).then(function(response) {
|
||||
if (~~(response) > 0) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// redirect to default group
|
||||
this.handleGroup('all');
|
||||
}.bind(this));
|
||||
},
|
||||
handleBulkAction: function(selected_ids, params, callback) {
|
||||
handleBulkAction: function(selected_ids, params) {
|
||||
if (
|
||||
this.state.selection === false
|
||||
&& this.state.selected_ids.length === 0
|
||||
@ -539,6 +531,7 @@ define(
|
||||
|
||||
var data = params || {};
|
||||
data.listing = {
|
||||
tab: (this.props.tab) ? this.props.tab : '',
|
||||
offset: 0,
|
||||
limit: 0,
|
||||
filter: this.state.filter,
|
||||
@ -549,14 +542,13 @@ define(
|
||||
data.listing.selection = selected_ids;
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'bulkAction',
|
||||
data: data
|
||||
}).done(function(response) {
|
||||
}).done(() => {
|
||||
this.getItems();
|
||||
callback(response);
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
handleSearch: function(search) {
|
||||
this.setState({
|
||||
@ -566,7 +558,6 @@ define(
|
||||
selected_ids: []
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleSort: function(sort_by, sort_order = 'asc') {
|
||||
@ -575,7 +566,6 @@ define(
|
||||
sort_order: sort_order,
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleSelectItem: function(id, is_checked) {
|
||||
@ -635,7 +625,6 @@ define(
|
||||
page: 1
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleGroup: function(group) {
|
||||
@ -649,7 +638,6 @@ define(
|
||||
page: 1
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleSetPage: function(page) {
|
||||
@ -659,23 +647,28 @@ define(
|
||||
selected_ids: []
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleRenderItem: function(item, actions) {
|
||||
var render = this.props.onRenderItem(item, actions);
|
||||
const render = this.props.onRenderItem(item, actions);
|
||||
return render.props.children;
|
||||
},
|
||||
handleRefreshItems: function() {
|
||||
this.getItems();
|
||||
},
|
||||
render: function() {
|
||||
var items = this.state.items,
|
||||
sort_by = this.state.sort_by,
|
||||
sort_order = this.state.sort_order;
|
||||
const items = this.state.items;
|
||||
const sort_by = this.state.sort_by;
|
||||
const sort_order = this.state.sort_order;
|
||||
|
||||
// columns
|
||||
let columns = this.props.columns || [];
|
||||
columns = columns.filter(function(column) {
|
||||
return (column.display === undefined || !!(column.display) === true);
|
||||
});
|
||||
|
||||
// bulk actions
|
||||
var bulk_actions = this.props.bulk_actions || [];
|
||||
let bulk_actions = this.props.bulk_actions || [];
|
||||
|
||||
if (this.state.group === 'trash' && bulk_actions.length > 0) {
|
||||
bulk_actions = [
|
||||
@ -693,9 +686,9 @@ define(
|
||||
}
|
||||
|
||||
// item actions
|
||||
var item_actions = this.props.item_actions || [];
|
||||
const item_actions = this.props.item_actions || [];
|
||||
|
||||
var table_classes = classNames(
|
||||
const table_classes = classNames(
|
||||
'mailpoet_listing_table',
|
||||
'wp-list-table',
|
||||
'widefat',
|
||||
@ -705,7 +698,7 @@ define(
|
||||
);
|
||||
|
||||
// search
|
||||
var search = (
|
||||
let search = (
|
||||
<ListingSearch
|
||||
onSearch={ this.handleSearch }
|
||||
search={ this.state.search }
|
||||
@ -716,7 +709,7 @@ define(
|
||||
}
|
||||
|
||||
// groups
|
||||
var groups = (
|
||||
let groups = (
|
||||
<ListingGroups
|
||||
groups={ this.state.groups }
|
||||
group={ this.state.group }
|
||||
@ -759,7 +752,7 @@ define(
|
||||
selection={ this.state.selection }
|
||||
sort_by={ sort_by }
|
||||
sort_order={ sort_order }
|
||||
columns={ this.props.columns }
|
||||
columns={ columns }
|
||||
is_selectable={ bulk_actions.length > 0 } />
|
||||
</thead>
|
||||
|
||||
@ -769,7 +762,7 @@ define(
|
||||
onRestoreItem={ this.handleRestoreItem }
|
||||
onTrashItem={ this.handleTrashItem }
|
||||
onRefreshItems={ this.handleRefreshItems }
|
||||
columns={ this.props.columns }
|
||||
columns={ columns }
|
||||
is_selectable={ bulk_actions.length > 0 }
|
||||
onSelectItem={ this.handleSelectItem }
|
||||
onSelectAll={ this.handleSelectAll }
|
||||
@ -789,7 +782,7 @@ define(
|
||||
selection={ this.state.selection }
|
||||
sort_by={ sort_by }
|
||||
sort_order={ sort_order }
|
||||
columns={ this.props.columns }
|
||||
columns={ columns }
|
||||
is_selectable={ bulk_actions.length > 0 } />
|
||||
</tfoot>
|
||||
|
||||
@ -812,6 +805,4 @@ define(
|
||||
}
|
||||
});
|
||||
|
||||
return Listing;
|
||||
}
|
||||
);
|
||||
module.exports = Listing;
|
@ -112,7 +112,16 @@ define([
|
||||
}
|
||||
}
|
||||
},
|
||||
}).preventDefault('auto');
|
||||
})
|
||||
.preventDefault('auto')
|
||||
.actionChecker(function (pointer, event, action) {
|
||||
// Disable dragging with right click
|
||||
if (event.button !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
|
||||
if (this.options.drop !== undefined) {
|
||||
interactable.getDropModel = this.options.drop;
|
||||
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Highlight Editing Behavior
|
||||
*
|
||||
* Highlights a block that is being edited
|
||||
*/
|
||||
define([
|
||||
'backbone.marionette',
|
||||
'newsletter_editor/behaviors/BehaviorsLookup',
|
||||
], function(Marionette, BehaviorsLookup) {
|
||||
|
||||
BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({
|
||||
modelEvents: {
|
||||
'startEditing': 'enableHighlight',
|
||||
'stopEditing': 'disableHighlight',
|
||||
},
|
||||
enableHighlight: function() {
|
||||
this.$el.addClass('mailpoet_highlight');
|
||||
},
|
||||
disableHighlight: function() {
|
||||
this.$el.removeClass('mailpoet_highlight');
|
||||
},
|
||||
});
|
||||
});
|
@ -13,6 +13,7 @@ define([
|
||||
'newsletter_editor/blocks/divider',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'backbone.supermodel',
|
||||
'underscore',
|
||||
'jquery'
|
||||
], function(
|
||||
@ -22,6 +23,7 @@ define([
|
||||
DividerBlock,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
SuperModel,
|
||||
_,
|
||||
jQuery
|
||||
) {
|
||||
@ -31,6 +33,36 @@ define([
|
||||
var Module = {},
|
||||
base = BaseBlock;
|
||||
|
||||
Module.ALCSupervisor = SuperModel.extend({
|
||||
initialize: function() {
|
||||
this.listenTo(App.getChannel(), 'automatedLatestContentRefresh', this.refresh);
|
||||
},
|
||||
refresh: function() {
|
||||
var models = App.findModels(function(model) {
|
||||
return model.get('type') === 'automatedLatestContent';
|
||||
}) || [];
|
||||
|
||||
if (models.length === 0) return;
|
||||
var blocks = _.map(models, function(model) {
|
||||
return model.toJSON();
|
||||
});
|
||||
|
||||
CommunicationComponent.getBulkTransformedPosts({
|
||||
blocks: blocks,
|
||||
}).then(_.partial(this.refreshBlocks, models));
|
||||
},
|
||||
refreshBlocks: function(models, renderedBlocks) {
|
||||
_.each(
|
||||
_.zip(models, renderedBlocks),
|
||||
function(args) {
|
||||
var model = args[0],
|
||||
contents = args[1];
|
||||
model.trigger('refreshPosts', contents);
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Module.AutomatedLatestContentBlockModel = base.BlockModel.extend({
|
||||
stale: ['_container'],
|
||||
defaults: function() {
|
||||
@ -72,34 +104,32 @@ 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: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);
|
||||
},
|
||||
fetchPosts: function() {
|
||||
var that = this;
|
||||
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
|
||||
that.get('_container').get('blocks').reset(content, {parse: true});
|
||||
that.trigger('postsChanged');
|
||||
}).fail(function(error) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
|
||||
this.on('add remove update reset', function(model, collection, options) {
|
||||
App.getChannel().trigger('automatedLatestContentRefresh');
|
||||
});
|
||||
this.on('refreshPosts', this.updatePosts, this);
|
||||
},
|
||||
updatePosts: function(posts) {
|
||||
this.get('_container.blocks').reset(posts, {parse: true});
|
||||
},
|
||||
/**
|
||||
* Batch more changes during a specific time, instead of fetching
|
||||
* ALC posts on each model change
|
||||
*/
|
||||
_scheduleFetchPosts: function() {
|
||||
var timeout = 500,
|
||||
var TIMEOUT = 500,
|
||||
that = this;
|
||||
if (this._fetchPostsTimer !== undefined) {
|
||||
clearTimeout(this._fetchPostsTimer);
|
||||
}
|
||||
this._fetchPostsTimer = setTimeout(function() {
|
||||
that.fetchPosts();
|
||||
//that.fetchPosts();
|
||||
App.getChannel().trigger('automatedLatestContentRefresh');
|
||||
that._fetchPostsTimer = undefined;
|
||||
}, timeout);
|
||||
}, TIMEOUT);
|
||||
},
|
||||
});
|
||||
|
||||
@ -124,6 +154,7 @@ define([
|
||||
renderOptions = {
|
||||
disableTextEditor: true,
|
||||
disableDragAndDrop: true,
|
||||
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
|
||||
};
|
||||
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
@ -164,9 +195,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
@ -181,6 +209,7 @@ define([
|
||||
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: MailPoet.I18n.t('categoriesAndTags'),
|
||||
ajax: {
|
||||
data: function (params) {
|
||||
return {
|
||||
@ -287,9 +316,11 @@ define([
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_separator').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_separator').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -363,5 +394,10 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
App.on('start', function() {
|
||||
App._ALCSupervisor = new Module.ALCSupervisor();
|
||||
App._ALCSupervisor.refresh();
|
||||
});
|
||||
|
||||
return Module;
|
||||
});
|
||||
|
@ -40,6 +40,9 @@ define([
|
||||
// Remove stale attributes from resulting JSON object
|
||||
return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
|
||||
},
|
||||
getChildren: function() {
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
Module.BlockView = AugmentedView.extend({
|
||||
@ -77,6 +80,7 @@ define([
|
||||
}
|
||||
},
|
||||
},
|
||||
HighlightEditingBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
@ -215,8 +219,12 @@ define([
|
||||
|
||||
Module.BlockSettingsView = Marionette.LayoutView.extend({
|
||||
className: 'mailpoet_editor_settings',
|
||||
initialize: function() {
|
||||
MailPoet.Modal.panel({
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
initialize: function(params) {
|
||||
this.model.trigger('startEditing');
|
||||
var panelParams = {
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
@ -224,7 +232,13 @@ define([
|
||||
onCancel: function() {
|
||||
this.destroy();
|
||||
}.bind(this),
|
||||
});
|
||||
};
|
||||
this.renderOptions = params.renderOptions || {};
|
||||
if (this.renderOptions.displayFormat === 'subpanel') {
|
||||
MailPoet.Modal.subpanel(panelParams);
|
||||
} else {
|
||||
MailPoet.Modal.panel(panelParams);
|
||||
}
|
||||
},
|
||||
close: function(event) {
|
||||
this.destroy();
|
||||
@ -253,6 +267,7 @@ define([
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
MailPoet.Modal.close();
|
||||
this.model.trigger('stopEditing');
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -99,23 +99,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
initialize: function(params) {
|
||||
var panelParams = {
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
width: App.getConfig().get('sidepanelWidth'),
|
||||
};
|
||||
this.renderOptions = params.renderOptions || {};
|
||||
if (this.renderOptions.displayFormat === 'subpanel') {
|
||||
MailPoet.Modal.subpanel(panelParams);
|
||||
} else {
|
||||
MailPoet.Modal.panel(panelParams);
|
||||
}
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
|
@ -65,6 +65,13 @@ define([
|
||||
}
|
||||
return response;
|
||||
},
|
||||
getChildren: function() {
|
||||
var models = this.get('blocks').map(function(model, index, list) {
|
||||
return [model, model.getChildren()];
|
||||
});
|
||||
|
||||
return _.flatten(models);
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockView = Marionette.CompositeView.extend({
|
||||
@ -118,6 +125,7 @@ define([
|
||||
return view.renderOptions.depth === 1;
|
||||
},
|
||||
},
|
||||
HighlightEditingBehavior: {}
|
||||
},
|
||||
onDragSubstituteBy: function() {
|
||||
// For two and three column layouts display their respective widgets,
|
||||
@ -286,6 +294,7 @@ define([
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
isRoot: this.renderOptions.depth === 0,
|
||||
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '',
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -302,9 +311,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
regions: {
|
||||
columnsSettingsRegion: '.mailpoet_container_columns_settings',
|
||||
},
|
||||
|
@ -102,23 +102,6 @@ define([
|
||||
'change:styles.block.borderColor': 'repaintDividerStyleOptions',
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
initialize: function(params) {
|
||||
var panelParams = {
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
width: App.getConfig().get('sidepanelWidth'),
|
||||
};
|
||||
this.renderOptions = params.renderOptions || {};
|
||||
if (this.renderOptions.displayFormat === 'subpanel') {
|
||||
MailPoet.Modal.subpanel(panelParams);
|
||||
} else {
|
||||
MailPoet.Modal.panel(panelParams);
|
||||
}
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
|
@ -56,13 +56,15 @@ define([
|
||||
inline: true,
|
||||
|
||||
menubar: false,
|
||||
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
|
||||
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
block_formats: 'Paragraph=p',
|
||||
relative_urls: false,
|
||||
remove_script_host: false,
|
||||
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_shortcodes",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -78,8 +80,8 @@ define([
|
||||
});
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
|
||||
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -104,9 +106,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
|
@ -56,13 +56,15 @@ define([
|
||||
inline: true,
|
||||
|
||||
menubar: false,
|
||||
toolbar: "bold italic link unlink forecolor mailpoet_custom_fields",
|
||||
toolbar: "bold italic link unlink forecolor mailpoet_shortcodes",
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
block_formats: 'Paragraph=p',
|
||||
relative_urls: false,
|
||||
remove_script_host: false,
|
||||
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_shortcodes",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -78,8 +80,8 @@ define([
|
||||
});
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
|
||||
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -104,9 +106,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
|
@ -23,7 +23,19 @@ define([
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'select2'
|
||||
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
|
||||
], function(
|
||||
Backbone,
|
||||
Marionette,
|
||||
Radio,
|
||||
_,
|
||||
jQuery,
|
||||
MailPoet,
|
||||
App,
|
||||
CommunicationComponent,
|
||||
BaseBlock,
|
||||
ButtonBlock,
|
||||
DividerBlock
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -163,6 +175,7 @@ define([
|
||||
renderOptions = {
|
||||
disableTextEditor: true,
|
||||
disableDragAndDrop: true,
|
||||
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
|
||||
};
|
||||
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
|
||||
},
|
||||
@ -195,6 +208,7 @@ define([
|
||||
};
|
||||
},
|
||||
initialize: function() {
|
||||
this.model.trigger('startEditing');
|
||||
this.selectionView = new PostSelectionSettingsView({ model: this.model });
|
||||
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
|
||||
},
|
||||
@ -202,21 +216,23 @@ define([
|
||||
var that = this,
|
||||
blockView = this.model.request('blockView');
|
||||
|
||||
this.selectionRegion.show(this.selectionView);
|
||||
this.displayOptionsRegion.show(this.displayOptionsView);
|
||||
this.showChildView('selectionRegion', this.selectionView);
|
||||
this.showChildView('displayOptionsRegion', this.displayOptionsView);
|
||||
|
||||
MailPoet.Modal.panel({
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
overlay: true,
|
||||
highlight: blockView.$el,
|
||||
width: App.getConfig().get('sidepanelWidth'),
|
||||
onCancel: function() {
|
||||
// Self destroy the block if the user closes settings modal
|
||||
that.model.destroy();
|
||||
},
|
||||
});
|
||||
|
||||
// Inform child views that they have been attached to document
|
||||
this.selectionView.triggerMethod('attach');
|
||||
this.displayOptionsView.triggerMethod('attach');
|
||||
},
|
||||
switchToDisplayOptions: function() {
|
||||
// Switch content view
|
||||
@ -266,14 +282,19 @@ define([
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
},
|
||||
onRender: function() {
|
||||
// Dynamically update available post types
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
},
|
||||
onAttach: function() {
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
//CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_posts_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
placeholder: MailPoet.I18n.t('categoriesAndTags'),
|
||||
ajax: {
|
||||
data: function (params) {
|
||||
return {
|
||||
@ -405,9 +426,6 @@ define([
|
||||
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"),
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
@ -450,9 +468,11 @@ define([
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_separator').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_separator').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
|
@ -139,6 +139,7 @@ define([
|
||||
}
|
||||
},
|
||||
},
|
||||
HighlightEditingBehavior: {},
|
||||
},
|
||||
onDragSubstituteBy: function() { return Module.SocialWidgetView; },
|
||||
constructor: function() {
|
||||
|
@ -70,9 +70,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
},
|
||||
});
|
||||
|
||||
Module.SpacerWidgetView = base.WidgetView.extend({
|
||||
|
@ -53,14 +53,16 @@ define([
|
||||
|
||||
menubar: false,
|
||||
toolbar1: "formatselect bold italic forecolor | link unlink",
|
||||
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_custom_fields",
|
||||
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_shortcodes",
|
||||
|
||||
//forced_root_block: 'p',
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]",
|
||||
invalid_elements: "script",
|
||||
block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
|
||||
relative_urls: false,
|
||||
remove_script_host: false,
|
||||
|
||||
plugins: "link code textcolor colorpicker mailpoet_custom_fields",
|
||||
plugins: "link code textcolor colorpicker mailpoet_shortcodes",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
@ -76,8 +78,8 @@ define([
|
||||
});
|
||||
},
|
||||
|
||||
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
|
||||
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
|
||||
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
|
||||
mailpoet_shortcodes_window_title: MailPoet.I18n.t('shortcodesWindowTitle'),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -62,6 +62,13 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
Module.getBulkTransformedPosts = function(options) {
|
||||
return Module._query({
|
||||
action: 'getBulkTransformedPosts',
|
||||
options: options,
|
||||
});
|
||||
};
|
||||
|
||||
Module.saveNewsletter = function(options) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
|
@ -60,6 +60,11 @@ define([
|
||||
return Module.newsletter;
|
||||
};
|
||||
|
||||
Module.findModels = function(predicate) {
|
||||
var blocks = App._contentContainer.getChildren();
|
||||
return _.filter(blocks, predicate);
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
// Expose block methods globally
|
||||
App.registerBlockType = Module.registerBlockType;
|
||||
@ -68,6 +73,7 @@ define([
|
||||
App.toJSON = Module.toJSON;
|
||||
App.getBody = Module.getBody;
|
||||
App.getNewsletter = Module.getNewsletter;
|
||||
App.findModels = Module.findModels;
|
||||
|
||||
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
|
||||
});
|
||||
|
@ -283,8 +283,10 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
var contents = JSON.stringify(jsonObject);
|
||||
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
|
||||
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
|
||||
contents.indexOf("[link:subscription_unsubscribe_url]") < 0 &&
|
||||
contents.indexOf("[link:subscription_unsubscribe]") < 0) {
|
||||
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
|
||||
return;
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* wysija_custom_fields/plugin.js
|
||||
*
|
||||
* TinyMCE plugin for adding dynamic data placeholders to newsletters.
|
||||
*
|
||||
* This adds a button to the editor toolbar which displays a modal window of
|
||||
* available dynamic data placeholder buttons. On click each button inserts
|
||||
* its placeholder into editor text.
|
||||
*/
|
||||
|
||||
/*jshint unused:false */
|
||||
/*global tinymce:true */
|
||||
tinymce.PluginManager.add('mailpoet_custom_fields', function(editor, url) {
|
||||
var appendLabelAndClose = function(text) {
|
||||
editor.insertContent('[' + text + ']');
|
||||
editor.windowManager.close();
|
||||
},
|
||||
generateOnClickFunc = function(id) {
|
||||
return function() {
|
||||
appendLabelAndClose(id);
|
||||
};
|
||||
};
|
||||
|
||||
editor.addButton('mailpoet_custom_fields', {
|
||||
icon: 'mailpoet_custom_fields',
|
||||
onclick: function() {
|
||||
var customFields = [],
|
||||
configCustomFields = editor.settings.mailpoet_custom_fields;
|
||||
|
||||
for (var segment in configCustomFields) {
|
||||
if (configCustomFields.hasOwnProperty(segment)) {
|
||||
customFields.push({
|
||||
type: 'label',
|
||||
text: segment,
|
||||
});
|
||||
|
||||
for (var i = 0; i < configCustomFields[segment].length; i += 1) {
|
||||
customFields.push({
|
||||
type: 'button',
|
||||
text: configCustomFields[segment][i].text,
|
||||
onClick: generateOnClickFunc(configCustomFields[segment][i].shortcode)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open window
|
||||
editor.windowManager.open({
|
||||
height: parseInt(editor.getParam("plugin_mailpoet_custom_fields_height", 400)),
|
||||
width: parseInt(editor.getParam("plugin_mailpoet_custom_fields_width", 450)),
|
||||
autoScroll: true,
|
||||
title: editor.settings.mailpoet_custom_fields_window_title,
|
||||
body: customFields,
|
||||
buttons: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* wysija_shortcodes/plugin.js
|
||||
*
|
||||
* TinyMCE plugin for adding dynamic data placeholders to newsletters.
|
||||
*
|
||||
* This adds a button to the editor toolbar which displays a modal window of
|
||||
* available dynamic data placeholder buttons. On click each button inserts
|
||||
* its placeholder into editor text.
|
||||
*/
|
||||
|
||||
/*jshint unused:false */
|
||||
/*global tinymce:true */
|
||||
tinymce.PluginManager.add('mailpoet_shortcodes', function(editor, url) {
|
||||
var appendLabelAndClose = function(text) {
|
||||
editor.insertContent('[' + text + ']');
|
||||
editor.windowManager.close();
|
||||
},
|
||||
generateOnClickFunc = function(id) {
|
||||
return function() {
|
||||
appendLabelAndClose(id);
|
||||
};
|
||||
};
|
||||
|
||||
editor.addButton('mailpoet_shortcodes', {
|
||||
icon: 'mailpoet_shortcodes',
|
||||
onclick: function() {
|
||||
var shortcodes = [],
|
||||
configShortcodes = editor.settings.mailpoet_shortcodes;
|
||||
|
||||
for (var segment in configShortcodes) {
|
||||
if (configShortcodes.hasOwnProperty(segment)) {
|
||||
shortcodes.push({
|
||||
type: 'label',
|
||||
text: segment,
|
||||
});
|
||||
|
||||
for (var i = 0; i < configShortcodes[segment].length; i += 1) {
|
||||
shortcodes.push({
|
||||
type: 'button',
|
||||
text: configShortcodes[segment][i].text,
|
||||
onClick: generateOnClickFunc(configShortcodes[segment][i].shortcode)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open window
|
||||
editor.windowManager.open({
|
||||
height: parseInt(editor.getParam("plugin_mailpoet_shortcodes_height", 400)),
|
||||
width: parseInt(editor.getParam("plugin_mailpoet_shortcodes_width", 450)),
|
||||
autoScroll: true,
|
||||
title: editor.settings.mailpoet_shortcodes_window_title,
|
||||
body: shortcodes,
|
||||
buttons: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
@ -1,311 +0,0 @@
|
||||
define(
|
||||
[
|
||||
'react',
|
||||
'react-router',
|
||||
'listing/listing.jsx',
|
||||
'classnames',
|
||||
'jquery',
|
||||
'mailpoet'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
Router,
|
||||
Listing,
|
||||
classNames,
|
||||
jQuery,
|
||||
MailPoet
|
||||
) {
|
||||
var Link = Router.Link;
|
||||
|
||||
var columns = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subject'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: MailPoet.I18n.t('status')
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('lists')
|
||||
},
|
||||
{
|
||||
name: 'statistics',
|
||||
label: MailPoet.I18n.t('statistics')
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
label: MailPoet.I18n.t('createdOn'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
var messages = {
|
||||
onTrash: function(response) {
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterRestored')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
var bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: MailPoet.I18n.t('trash'),
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
|
||||
var item_actions = [
|
||||
{
|
||||
name: 'edit',
|
||||
link: function(item) {
|
||||
return (
|
||||
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
|
||||
{MailPoet.I18n.t('edit')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
var NewsletterList = React.createClass({
|
||||
pauseSending: function(item) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'pause',
|
||||
data: item.id
|
||||
}).done(function() {
|
||||
jQuery('#resume_'+item.id).show();
|
||||
jQuery('#pause_'+item.id).hide();
|
||||
});
|
||||
},
|
||||
resumeSending: function(item) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'resume',
|
||||
data: item.id
|
||||
}).done(function() {
|
||||
jQuery('#pause_'+item.id).show();
|
||||
jQuery('#resume_'+item.id).hide();
|
||||
});
|
||||
},
|
||||
renderStatus: function(item) {
|
||||
if(!item.queue) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
} else {
|
||||
if (item.queue.status === 'scheduled') {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(item.queue.scheduled_at) } </span>
|
||||
)
|
||||
}
|
||||
var progressClasses = classNames(
|
||||
'mailpoet_progress',
|
||||
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
|
||||
);
|
||||
|
||||
// calculate percentage done
|
||||
var percentage = Math.round(
|
||||
(item.queue.count_processed * 100) / (item.queue.count_total)
|
||||
);
|
||||
|
||||
var label = false;
|
||||
|
||||
if(item.queue.status === 'completed') {
|
||||
label = (
|
||||
<span>
|
||||
{
|
||||
MailPoet.I18n.t('newsletterQueueCompleted')
|
||||
.replace("%$1d", item.queue.count_processed - item.queue.count_failed)
|
||||
.replace("%$2d", item.queue.count_total)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
label = (
|
||||
<span>
|
||||
{ item.queue.count_processed } / { item.queue.count_total }
|
||||
|
||||
<a
|
||||
id={ 'resume_'+item.id }
|
||||
className="button"
|
||||
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.resumeSending.bind(null, item) }
|
||||
>{MailPoet.I18n.t('resume')}</a>
|
||||
<a
|
||||
id={ 'pause_'+item.id }
|
||||
className="button mailpoet_pause"
|
||||
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.pauseSending.bind(null, item) }
|
||||
>{MailPoet.I18n.t('pause')}</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ progressClasses }>
|
||||
<span
|
||||
className="mailpoet_progress_bar"
|
||||
style={ { width: percentage + "%"} }
|
||||
></span>
|
||||
<span className="mailpoet_progress_label">
|
||||
{ percentage + "%" }
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ textAlign:'center' }}>
|
||||
{ label }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderStatistics: function(item) {
|
||||
if(!item.statistics || !item.queue || item.queue.count_processed == 0 || item.queue.status === 'scheduled') {
|
||||
return (
|
||||
<span>
|
||||
{MailPoet.I18n.t('notSentYet')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
var percentage_clicked = Math.round(
|
||||
(item.statistics.clicked * 100) / (item.queue.count_processed)
|
||||
);
|
||||
var percentage_opened = Math.round(
|
||||
(item.statistics.opened * 100) / (item.queue.count_processed)
|
||||
);
|
||||
var percentage_unsubscribed = Math.round(
|
||||
(item.statistics.unsubscribed * 100) / (item.queue.count_processed)
|
||||
);
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
var rowClasses = classNames(
|
||||
'manage-column',
|
||||
'column-primary',
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
var segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
}).join(', ');
|
||||
|
||||
var statistics_column =
|
||||
(!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) ?
|
||||
false :
|
||||
<td className="column {statistics_class}" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
</td>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<td className={ rowClasses }>
|
||||
<strong>
|
||||
<a>{ newsletter.subject }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||
{ this.renderStatus(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
|
||||
{ segments }
|
||||
</td>
|
||||
{ statistics_column }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
if (!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) {
|
||||
columns = _.without(columns, _.findWhere(columns, {name: 'statistics'}));
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title">
|
||||
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
|
||||
</h1>
|
||||
|
||||
<Listing
|
||||
limit={ mailpoet_listing_per_page }
|
||||
params={ this.props.params }
|
||||
endpoint="newsletters"
|
||||
onRenderItem={this.renderItem}
|
||||
columns={columns}
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ item_actions }
|
||||
messages={ messages }
|
||||
auto_refresh={ true } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return NewsletterList;
|
||||
}
|
||||
);
|
317
assets/js/src/newsletters/listings/notification.jsx
Normal file
317
assets/js/src/newsletters/listings/notification.jsx
Normal file
@ -0,0 +1,317 @@
|
||||
import React from 'react'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
|
||||
import {
|
||||
timeOfDayValues,
|
||||
weekDayValues,
|
||||
monthDayValues,
|
||||
nthWeekDayValues
|
||||
} from 'newsletters/scheduling/common.jsx'
|
||||
|
||||
const messages = {
|
||||
onTrash(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterRestored')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subject'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: MailPoet.I18n.t('status'),
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
label: MailPoet.I18n.t('settings')
|
||||
},
|
||||
{
|
||||
name: 'history',
|
||||
label: MailPoet.I18n.t('history'),
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
const bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: MailPoet.I18n.t('trash'),
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ newsletter.preview_url } target="_blank">
|
||||
{MailPoet.I18n.t('preview')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
|
||||
{MailPoet.I18n.t('edit')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: newsletter.id
|
||||
}).done(function(response) {
|
||||
if (response !== false && response.subject !== undefined) {
|
||||
MailPoet.Notice.success(
|
||||
(MailPoet.I18n.t('newsletterDuplicated')).replace(
|
||||
'%$1s', response.subject
|
||||
)
|
||||
);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
const NewsletterListNotification = React.createClass({
|
||||
updateStatus: function(e) {
|
||||
// make the event persist so that we can still override the selected value
|
||||
// in the ajax callback
|
||||
e.persist();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'setStatus',
|
||||
data: {
|
||||
id: ~~(e.target.getAttribute('data-id')),
|
||||
status: e.target.value
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response.result === false) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('postNotificationActivationFailed'));
|
||||
|
||||
// reset value to actual newsletter's status
|
||||
e.target.value = response.status;
|
||||
} else {
|
||||
if (response.status === 'active') {
|
||||
MailPoet.Notice.success(MailPoet.I18n.t('postNotificationActivated'));
|
||||
}
|
||||
// force refresh of listing so that groups are updated
|
||||
this.forceUpdate();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
renderStatus: function(newsletter) {
|
||||
return (
|
||||
<select
|
||||
data-id={ newsletter.id }
|
||||
defaultValue={ newsletter.status }
|
||||
onChange={ this.updateStatus }
|
||||
>
|
||||
<option value="active">{ MailPoet.I18n.t('active') }</option>
|
||||
<option value="draft">{ MailPoet.I18n.t('inactive') }</option>
|
||||
</select>
|
||||
);
|
||||
},
|
||||
renderSettings: function(newsletter) {
|
||||
let sendingFrequency;
|
||||
let sendingToSegments;
|
||||
|
||||
// get list of segments' name
|
||||
const segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
});
|
||||
|
||||
// check if the user has specified segments to send to
|
||||
if(segments.length === 0) {
|
||||
return (
|
||||
<span className="mailpoet_error">
|
||||
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
|
||||
'%$1s', segments.join(', ')
|
||||
);
|
||||
|
||||
// set sending frequency
|
||||
switch (newsletter.options.intervalType) {
|
||||
case 'daily':
|
||||
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
|
||||
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'weekly':
|
||||
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
|
||||
'%$1s', weekDayValues[newsletter.options.weekDay]
|
||||
).replace(
|
||||
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'monthly':
|
||||
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
|
||||
'%$1s', monthDayValues[newsletter.options.monthDay]
|
||||
).replace(
|
||||
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'nthWeekDay':
|
||||
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
|
||||
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
|
||||
).replace(
|
||||
'%$2s', weekDayValues[newsletter.options.weekDay]
|
||||
).replace(
|
||||
'%$3s', timeOfDayValues[newsletter.options.timeOfDay]
|
||||
);
|
||||
break;
|
||||
|
||||
case 'immediately':
|
||||
sendingFrequency = MailPoet.I18n.t('sendImmediately');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ sendingFrequency } { sendingToSegments }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
const rowClasses = classNames(
|
||||
'manage-column',
|
||||
'column-primary',
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<td className={ rowClasses }>
|
||||
<strong>
|
||||
<a
|
||||
className="row-title"
|
||||
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
|
||||
>{ newsletter.subject }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||
{ this.renderStatus(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('settings') }>
|
||||
{ this.renderSettings(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('history') }>
|
||||
<a href="#TODO">{ MailPoet.I18n.t('viewHistory') }</a>
|
||||
</td>
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title">
|
||||
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
|
||||
</h1>
|
||||
|
||||
<ListingTabs tab="notification" />
|
||||
|
||||
<Listing
|
||||
limit={ mailpoet_listing_per_page }
|
||||
location={ this.props.location }
|
||||
params={ this.props.params }
|
||||
endpoint="newsletters"
|
||||
tab="notification"
|
||||
onRenderItem={ this.renderItem }
|
||||
columns={ columns }
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ newsletter_actions }
|
||||
messages={ messages }
|
||||
auto_refresh={ true }
|
||||
sort_by="updated_at"
|
||||
sort_order="desc"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewsletterListNotification;
|
339
assets/js/src/newsletters/listings/standard.jsx
Normal file
339
assets/js/src/newsletters/listings/standard.jsx
Normal file
@ -0,0 +1,339 @@
|
||||
import React from 'react'
|
||||
import { Router, Link } from 'react-router'
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
|
||||
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
|
||||
|
||||
const messages = {
|
||||
onTrash(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterRestored')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subject'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: MailPoet.I18n.t('status')
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('lists')
|
||||
},
|
||||
{
|
||||
name: 'statistics',
|
||||
label: MailPoet.I18n.t('statistics'),
|
||||
display: mailpoet_tracking_enabled
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
const bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: MailPoet.I18n.t('trash'),
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ newsletter.preview_url } target="_blank">
|
||||
{MailPoet.I18n.t('preview')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
|
||||
{MailPoet.I18n.t('edit')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: newsletter.id
|
||||
}).done(function(response) {
|
||||
if (response !== false && response.subject !== undefined) {
|
||||
MailPoet.Notice.success(
|
||||
(MailPoet.I18n.t('newsletterDuplicated')).replace(
|
||||
'%$1s', response.subject
|
||||
)
|
||||
);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
const NewsletterListStandard = React.createClass({
|
||||
pauseSending: function(newsletter) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'pause',
|
||||
data: newsletter.id
|
||||
}).done(function() {
|
||||
jQuery('#resume_'+newsletter.id).show();
|
||||
jQuery('#pause_'+newsletter.id).hide();
|
||||
});
|
||||
},
|
||||
resumeSending: function(newsletter) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'resume',
|
||||
data: newsletter.id
|
||||
}).done(function() {
|
||||
jQuery('#pause_'+newsletter.id).show();
|
||||
jQuery('#resume_'+newsletter.id).hide();
|
||||
});
|
||||
},
|
||||
renderStatus: function(newsletter) {
|
||||
if (!newsletter.queue) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
} else {
|
||||
if (newsletter.queue.status === 'scheduled') {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(newsletter.queue.scheduled_at) } </span>
|
||||
)
|
||||
}
|
||||
const progressClasses = classNames(
|
||||
'mailpoet_progress',
|
||||
{ 'mailpoet_progress_complete': newsletter.queue.status === 'completed'}
|
||||
);
|
||||
|
||||
// calculate percentage done
|
||||
const percentage = Math.round(
|
||||
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
|
||||
);
|
||||
|
||||
let label;
|
||||
|
||||
if (newsletter.queue.status === 'completed') {
|
||||
label = (
|
||||
<span>
|
||||
{
|
||||
MailPoet.I18n.t('newsletterQueueCompleted')
|
||||
.replace("%$1d", newsletter.queue.count_processed - newsletter.queue.count_failed)
|
||||
.replace("%$2d", newsletter.queue.count_total)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
label = (
|
||||
<span>
|
||||
{ newsletter.queue.count_processed } / { newsletter.queue.count_total }
|
||||
|
||||
<a
|
||||
id={ 'resume_'+newsletter.id }
|
||||
className="button"
|
||||
style={{ display: (newsletter.queue.status === 'paused') ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.resumeSending.bind(null, newsletter) }
|
||||
>{MailPoet.I18n.t('resume')}</a>
|
||||
<a
|
||||
id={ 'pause_'+newsletter.id }
|
||||
className="button mailpoet_pause"
|
||||
style={{ display: (newsletter.queue.status === null) ? 'inline-block': 'none' }}
|
||||
href="javascript:;"
|
||||
onClick={ this.pauseSending.bind(null, newsletter) }
|
||||
>{MailPoet.I18n.t('pause')}</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={ progressClasses }>
|
||||
<span
|
||||
className="mailpoet_progress_bar"
|
||||
style={ { width: percentage + "%"} }
|
||||
></span>
|
||||
<span className="mailpoet_progress_label">
|
||||
{ percentage + "%" }
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ textAlign:'center' }}>
|
||||
{ label }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderStatistics: function(newsletter) {
|
||||
if (mailpoet_tracking_enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newsletter.statistics && newsletter.queue && newsletter.queue.status !== 'scheduled') {
|
||||
const total_sent = ~~(newsletter.queue.count_processed);
|
||||
|
||||
let percentage_clicked = 0;
|
||||
let percentage_opened = 0;
|
||||
let percentage_unsubscribed = 0;
|
||||
|
||||
if (total_sent > 0) {
|
||||
percentage_clicked = Math.round(
|
||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
||||
);
|
||||
percentage_opened = Math.round(
|
||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
||||
);
|
||||
percentage_unsubscribed = Math.round(
|
||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
const rowClasses = classNames(
|
||||
'manage-column',
|
||||
'column-primary',
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
const segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
}).join(', ');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<td className={ rowClasses }>
|
||||
<strong>
|
||||
<a
|
||||
className="row-title"
|
||||
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
|
||||
>{ newsletter.subject }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||
{ this.renderStatus(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
|
||||
{ segments }
|
||||
</td>
|
||||
{ (mailpoet_tracking_enabled === true) ? (
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title">
|
||||
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
|
||||
</h1>
|
||||
|
||||
<ListingTabs tab="standard" />
|
||||
|
||||
<Listing
|
||||
limit={ mailpoet_listing_per_page }
|
||||
location={ this.props.location }
|
||||
params={ this.props.params }
|
||||
endpoint="newsletters"
|
||||
tab="standard"
|
||||
onRenderItem={this.renderItem}
|
||||
columns={columns}
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ newsletter_actions }
|
||||
messages={ messages }
|
||||
auto_refresh={ true }
|
||||
sort_by="updated_at"
|
||||
sort_order="desc"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewsletterListStandard;
|
53
assets/js/src/newsletters/listings/tabs.jsx
Normal file
53
assets/js/src/newsletters/listings/tabs.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import classNames from 'classnames'
|
||||
import MailPoet from 'mailpoet'
|
||||
|
||||
const ListingTabs = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
tab: null,
|
||||
tabs: [
|
||||
{
|
||||
name: 'standard',
|
||||
label: MailPoet.I18n.t('tabStandardTitle'),
|
||||
link: '/standard'
|
||||
},
|
||||
{
|
||||
name: 'welcome',
|
||||
label: MailPoet.I18n.t('tabWelcomeTitle'),
|
||||
link: '/welcome'
|
||||
},
|
||||
{
|
||||
name: 'notification',
|
||||
label: MailPoet.I18n.t('tabNotificationTitle'),
|
||||
link: '/notification'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const tabs = this.state.tabs.map((tab, index) => {
|
||||
const tabClasses = classNames(
|
||||
'nav-tab',
|
||||
{ 'nav-tab-active': (this.props.tab === tab.name) }
|
||||
);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={ 'tab-'+index }
|
||||
className={ tabClasses }
|
||||
to={ tab.link }
|
||||
>{ tab.label }</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<h2 className="nav-tab-wrapper">
|
||||
{ tabs }
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ListingTabs;
|
361
assets/js/src/newsletters/listings/welcome.jsx
Normal file
361
assets/js/src/newsletters/listings/welcome.jsx
Normal file
@ -0,0 +1,361 @@
|
||||
import React from 'react'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
import _ from 'underscore'
|
||||
|
||||
const mailpoet_roles = window.mailpoet_roles || {};
|
||||
const mailpoet_segments = window.mailpoet_segments || {};
|
||||
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
|
||||
|
||||
const messages = {
|
||||
onTrash(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterTrashed')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersTrashed')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterDeleted')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersDeleted')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore(response) {
|
||||
const count = ~~response;
|
||||
let message = null;
|
||||
|
||||
if (count === 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('oneNewsletterRestored')
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleNewslettersRestored')
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'subject',
|
||||
label: MailPoet.I18n.t('subject'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: MailPoet.I18n.t('status'),
|
||||
width: 145
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
label: MailPoet.I18n.t('settings')
|
||||
},
|
||||
{
|
||||
name: 'statistics',
|
||||
label: MailPoet.I18n.t('statistics'),
|
||||
display: mailpoet_tracking_enabled
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
label: MailPoet.I18n.t('lastModifiedOn'),
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
const bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: MailPoet.I18n.t('trash'),
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ newsletter.preview_url } target="_blank">
|
||||
{MailPoet.I18n.t('preview')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
link: function(newsletter) {
|
||||
return (
|
||||
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
|
||||
{MailPoet.I18n.t('edit')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: newsletter.id
|
||||
}).done(function(response) {
|
||||
if (response !== false && response.subject !== undefined) {
|
||||
MailPoet.Notice.success(
|
||||
(MailPoet.I18n.t('newsletterDuplicated')).replace(
|
||||
'%$1s', response.subject
|
||||
)
|
||||
);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
const NewsletterListWelcome = React.createClass({
|
||||
updateStatus: function(e) {
|
||||
// make the event persist so that we can still override the selected value
|
||||
// in the ajax callback
|
||||
e.persist();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'setStatus',
|
||||
data: {
|
||||
id: ~~(e.target.getAttribute('data-id')),
|
||||
status: e.target.value
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response.result === false) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('welcomeEmailActivationFailed'));
|
||||
|
||||
// reset value to actual newsletter's status
|
||||
e.target.value = response.status;
|
||||
} else {
|
||||
if (response.status === 'active') {
|
||||
MailPoet.Notice.success(MailPoet.I18n.t('welcomeEmailActivated'));
|
||||
}
|
||||
// force refresh of listing so that groups are updated
|
||||
this.forceUpdate();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
renderStatus: function(newsletter) {
|
||||
let total_sent;
|
||||
total_sent = (
|
||||
MailPoet.I18n.t('sentToXSubscribers')
|
||||
.replace('%$1d', newsletter.total_sent.toLocaleString())
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<select
|
||||
data-id={ newsletter.id }
|
||||
defaultValue={ newsletter.status }
|
||||
onChange={ this.updateStatus }
|
||||
>
|
||||
<option value="active">{ MailPoet.I18n.t('active') }</option>
|
||||
<option value="draft">{ MailPoet.I18n.t('inactive') }</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>{ total_sent }</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderSettings: function(newsletter) {
|
||||
let sendingEvent;
|
||||
let sendingDelay;
|
||||
|
||||
// set sending event
|
||||
switch (newsletter.options.event) {
|
||||
case 'user':
|
||||
// WP User
|
||||
if (newsletter.options.role === 'mailpoet_all') {
|
||||
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserAnyRole');
|
||||
} else {
|
||||
sendingEvent = MailPoet.I18n.t('welcomeEventWPUserWithRole').replace(
|
||||
'%$1s', mailpoet_roles[newsletter.options.role]
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'segment':
|
||||
// get segment
|
||||
const segment = _.find(mailpoet_segments, function(segment) {
|
||||
return (~~(segment.id) === ~~(newsletter.options.segment));
|
||||
});
|
||||
|
||||
if (segment === undefined) {
|
||||
return (
|
||||
<span className="mailpoet_error">
|
||||
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
|
||||
'%$1s', segment.name
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// set sending delay
|
||||
if (sendingEvent) {
|
||||
if (newsletter.options.afterTimeType !== 'immediate') {
|
||||
switch (newsletter.options.afterTimeType) {
|
||||
case 'hours':
|
||||
sendingDelay = MailPoet.I18n.t('sendingDelayHours').replace(
|
||||
'%$1d', newsletter.options.afterTimeNumber
|
||||
);
|
||||
break;
|
||||
|
||||
case 'days':
|
||||
sendingDelay = MailPoet.I18n.t('sendingDelayDays').replace(
|
||||
'%$1d', newsletter.options.afterTimeNumber
|
||||
);
|
||||
break;
|
||||
|
||||
case 'weeks':
|
||||
sendingDelay = MailPoet.I18n.t('sendingDelayWeeks').replace(
|
||||
'%$1d', newsletter.options.afterTimeNumber
|
||||
);
|
||||
break;
|
||||
}
|
||||
sendingEvent += ' [' + sendingDelay + ']';
|
||||
}
|
||||
// add a "period" at the end if we do have a sendingEvent
|
||||
sendingEvent += '.';
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ sendingEvent }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
renderStatistics: function(newsletter) {
|
||||
if (mailpoet_tracking_enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newsletter.total_sent > 0 && newsletter.statistics) {
|
||||
const total_sent = ~~(newsletter.total_sent);
|
||||
|
||||
const percentage_clicked = Math.round(
|
||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
||||
);
|
||||
const percentage_opened = Math.round(
|
||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
||||
);
|
||||
const percentage_unsubscribed = Math.round(
|
||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
||||
);
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
const rowClasses = classNames(
|
||||
'manage-column',
|
||||
'column-primary',
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<td className={ rowClasses }>
|
||||
<strong>
|
||||
<a
|
||||
className="row-title"
|
||||
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
|
||||
>{ newsletter.subject }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
|
||||
{ this.renderStatus(newsletter) }
|
||||
</td>
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('settings') }>
|
||||
{ this.renderSettings(newsletter) }
|
||||
</td>
|
||||
{ (mailpoet_tracking_enabled === true) ? (
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="title">
|
||||
{ MailPoet.I18n.t('pageTitle') } <Link className="page-title-action" to="/new">{ MailPoet.I18n.t('new') }</Link>
|
||||
</h1>
|
||||
|
||||
<ListingTabs tab="welcome" />
|
||||
|
||||
<Listing
|
||||
limit={ mailpoet_listing_per_page }
|
||||
location={ this.props.location }
|
||||
params={ this.props.params }
|
||||
endpoint="newsletters"
|
||||
tab="welcome"
|
||||
onRenderItem={ this.renderItem }
|
||||
columns={ columns }
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ newsletter_actions }
|
||||
messages={ messages }
|
||||
auto_refresh={ true }
|
||||
sort_by="updated_at"
|
||||
sort_order="desc"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewsletterListWelcome;
|
@ -1,14 +1,19 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
import NewsletterList from 'newsletters/list.jsx'
|
||||
|
||||
import NewsletterTypes from 'newsletters/types.jsx'
|
||||
import NewsletterTemplates from 'newsletters/templates.jsx'
|
||||
import NewsletterSend from 'newsletters/send.jsx'
|
||||
import NewsletterStandard from 'newsletters/types/standard.jsx'
|
||||
import NewsletterWelcome from 'newsletters/types/welcome/welcome.jsx'
|
||||
import NewsletterNotification from 'newsletters/types/notification/notification.jsx'
|
||||
|
||||
import NewsletterTypeStandard from 'newsletters/types/standard.jsx'
|
||||
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx'
|
||||
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx'
|
||||
|
||||
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
|
||||
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx'
|
||||
import NewsletterListNotification from 'newsletters/listings/notification.jsx'
|
||||
|
||||
const history = useRouterHistory(createHashHistory)({ queryKey: false });
|
||||
|
||||
@ -24,15 +29,24 @@ if(container) {
|
||||
ReactDOM.render((
|
||||
<Router history={ history }>
|
||||
<Route path="/" component={ App }>
|
||||
<IndexRoute component={ NewsletterList } />
|
||||
<IndexRedirect to="standard" />
|
||||
{/* Listings */}
|
||||
<Route name="listing/standard" path="standard" component={ NewsletterListStandard } />
|
||||
<Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } />
|
||||
<Route name="listing/notification" path="notification" component={ NewsletterListNotification } />
|
||||
<Route path="standard/*" component={ NewsletterListStandard } />
|
||||
<Route path="welcome/*" component={ NewsletterListWelcome } />
|
||||
<Route path="notification/*" component={ NewsletterListNotification } />
|
||||
{/* Newsletter: type selection */}
|
||||
<Route path="new" component={ NewsletterTypes } />
|
||||
<Route name="standard" path="new/standard" component={ NewsletterStandard } />
|
||||
<Route name="welcome" path="new/welcome" component={ NewsletterWelcome } />
|
||||
<Route name="notification" path="new/notification" component={ NewsletterNotification } />
|
||||
{/* New newsletter: types */}
|
||||
<Route name="new/standard" path="new/standard" component={ NewsletterTypeStandard } />
|
||||
<Route name="new/welcome" path="new/welcome" component={ NewsletterTypeWelcome } />
|
||||
<Route name="new/notification" path="new/notification" component={ NewsletterTypeNotification } />
|
||||
{/* Template selection */}
|
||||
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
||||
{/* Sending options */}
|
||||
<Route path="send/:id" component={ NewsletterSend } />
|
||||
<Route path="filter[:filter]" component={ NewsletterList } />
|
||||
<Route path="*" component={ NewsletterList } />
|
||||
</Route>
|
||||
</Router>
|
||||
), container);
|
||||
|
82
assets/js/src/newsletters/scheduling/common.jsx
Normal file
82
assets/js/src/newsletters/scheduling/common.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import _ from 'underscore'
|
||||
import MailPoet from 'mailpoet'
|
||||
|
||||
const timeFormat = window.mailpoet_time_format || 'H:i';
|
||||
|
||||
// welcome emails
|
||||
const _timeDelayValues = {
|
||||
'immediate': MailPoet.I18n.t('delayImmediately'),
|
||||
'hours': MailPoet.I18n.t('delayHoursAfter'),
|
||||
'days': MailPoet.I18n.t('delayDaysAfter'),
|
||||
'weeks': MailPoet.I18n.t('delayWeeksAfter')
|
||||
};
|
||||
|
||||
const _intervalValues = {
|
||||
'daily': MailPoet.I18n.t('daily'),
|
||||
'weekly': MailPoet.I18n.t('weekly'),
|
||||
'monthly': MailPoet.I18n.t('monthly'),
|
||||
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
|
||||
'immediately': MailPoet.I18n.t('immediately')
|
||||
};
|
||||
|
||||
// notification emails
|
||||
const SECONDS_IN_DAY = 86400;
|
||||
const TIME_STEP_SECONDS = 3600;
|
||||
const numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
|
||||
|
||||
const _timeOfDayValues = _.object(_.map(
|
||||
_.times(numberOfTimeSteps,function(step) {
|
||||
return step * TIME_STEP_SECONDS;
|
||||
}), function(seconds) {
|
||||
let date = new Date(null);
|
||||
date.setSeconds(seconds);
|
||||
const timeLabel = MailPoet.Date.format(date, { format: timeFormat, offset: 0 });
|
||||
return [seconds, timeLabel];
|
||||
})
|
||||
);
|
||||
|
||||
const _weekDayValues = {
|
||||
0: MailPoet.I18n.t('sunday'),
|
||||
1: MailPoet.I18n.t('monday'),
|
||||
2: MailPoet.I18n.t('tuesday'),
|
||||
3: MailPoet.I18n.t('wednesday'),
|
||||
4: MailPoet.I18n.t('thursday'),
|
||||
5: MailPoet.I18n.t('friday'),
|
||||
6: MailPoet.I18n.t('saturday')
|
||||
};
|
||||
|
||||
const NUMBER_OF_DAYS_IN_MONTH = 28;
|
||||
const _monthDayValues = _.object(
|
||||
_.map(
|
||||
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) {
|
||||
return day;
|
||||
}), function(day) {
|
||||
const labels = {
|
||||
0: MailPoet.I18n.t('first'),
|
||||
1: MailPoet.I18n.t('second'),
|
||||
2: MailPoet.I18n.t('third')
|
||||
};
|
||||
let label;
|
||||
if (labels[day] !== undefined) {
|
||||
label = labels[day];
|
||||
} else {
|
||||
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
|
||||
}
|
||||
return [day, label];
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const _nthWeekDayValues = {
|
||||
'1': MailPoet.I18n.t('first'),
|
||||
'2': MailPoet.I18n.t('second'),
|
||||
'3': MailPoet.I18n.t('third'),
|
||||
'L': MailPoet.I18n.t('last')
|
||||
};
|
||||
|
||||
export { _timeDelayValues as timeDelayValues };
|
||||
export { _intervalValues as intervalValues };
|
||||
export { _timeOfDayValues as timeOfDayValues };
|
||||
export { _weekDayValues as weekDayValues };
|
||||
export { _monthDayValues as monthDayValues };
|
||||
export { _nthWeekDayValues as nthWeekDayValues };
|
@ -34,15 +34,20 @@ define(
|
||||
};
|
||||
},
|
||||
getFieldsByNewsletter: function(newsletter) {
|
||||
var type = this.getSubtype(newsletter);
|
||||
return type.getFields(newsletter);
|
||||
},
|
||||
getSendButtonOptions: function() {
|
||||
var type = this.getSubtype(this.state.item);
|
||||
return type.getSendButtonOptions(this.state.item);
|
||||
},
|
||||
getSubtype: function(newsletter) {
|
||||
switch(newsletter.type) {
|
||||
case 'notification': return NotificationNewsletterFields;
|
||||
case 'welcome': return WelcomeNewsletterFields;
|
||||
default: return StandardNewsletterFields;
|
||||
}
|
||||
},
|
||||
isAutomatedNewsletter: function() {
|
||||
return this.state.item.type !== 'standard';
|
||||
},
|
||||
isValid: function() {
|
||||
return jQuery('#mailpoet_newsletter').parsley().isValid();
|
||||
},
|
||||
@ -105,8 +110,9 @@ define(
|
||||
}
|
||||
}).done((response) => {
|
||||
this.setState({ loading: false });
|
||||
|
||||
if(response.result === true) {
|
||||
this.context.router.push('/');
|
||||
this.context.router.push(`/${ this.state.item.type || '' }`);
|
||||
MailPoet.Notice.success(response.data.message);
|
||||
} else {
|
||||
if(response.errors) {
|
||||
@ -123,9 +129,22 @@ define(
|
||||
},
|
||||
handleSave: function(e) {
|
||||
e.preventDefault();
|
||||
this._save(e).done(() => {
|
||||
this.context.router.push(`/${ this.state.item.type || '' }`);
|
||||
});
|
||||
},
|
||||
handleRedirectToDesign: function(e) {
|
||||
e.preventDefault();
|
||||
var redirectTo = e.target.href;
|
||||
|
||||
this._save(e).done(() => {
|
||||
window.location = redirectTo;
|
||||
});
|
||||
},
|
||||
_save: function(e) {
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: this.state.item,
|
||||
@ -133,7 +152,6 @@ define(
|
||||
this.setState({ loading: false });
|
||||
|
||||
if(response.result === true) {
|
||||
this.context.router.push('/');
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('newsletterUpdated')
|
||||
);
|
||||
@ -175,10 +193,9 @@ define(
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={ this.handleSend }
|
||||
value={
|
||||
this.isAutomatedNewsletter()
|
||||
? MailPoet.I18n.t('activate')
|
||||
: MailPoet.I18n.t('send')} />
|
||||
value={MailPoet.I18n.t('send')}
|
||||
{...this.getSendButtonOptions()}
|
||||
/>
|
||||
|
||||
<input
|
||||
className="button button-secondary"
|
||||
@ -188,7 +205,8 @@ define(
|
||||
<a
|
||||
href={
|
||||
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
|
||||
}>
|
||||
}
|
||||
onClick={this.handleRedirectToDesign}>
|
||||
{MailPoet.I18n.t('goBackToDesign')}
|
||||
</a>.
|
||||
</p>
|
||||
|
@ -23,7 +23,7 @@ define(
|
||||
},
|
||||
{
|
||||
name: 'options',
|
||||
label: MailPoet.I18n.t('selectPeriodicity'),
|
||||
label: MailPoet.I18n.t('selectFrequency'),
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
},
|
||||
@ -95,6 +95,15 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
},
|
||||
getSendButtonOptions: function(newsletter) {
|
||||
return {
|
||||
value: MailPoet.I18n.t('activate')
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -253,7 +253,13 @@ define(
|
||||
|
||||
var StandardScheduling = React.createClass({
|
||||
_getCurrentValue: function() {
|
||||
return this.props.item[this.props.field.name] || {};
|
||||
return _.defaults(
|
||||
this.props.item[this.props.field.name] || {},
|
||||
{
|
||||
isScheduled: '0',
|
||||
scheduledAt: defaultDateTime
|
||||
}
|
||||
);
|
||||
},
|
||||
handleValueChange: function(event) {
|
||||
var oldValue = this._getCurrentValue(),
|
||||
@ -401,6 +407,30 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
},
|
||||
getSendButtonOptions: function(newsletter) {
|
||||
newsletter = newsletter || {};
|
||||
|
||||
let isScheduled = (
|
||||
typeof newsletter.options === 'object'
|
||||
&& newsletter.options.isScheduled === '1'
|
||||
);
|
||||
let options = {
|
||||
value: (isScheduled
|
||||
? MailPoet.I18n.t('schedule')
|
||||
: MailPoet.I18n.t('send'))
|
||||
};
|
||||
|
||||
if (newsletter.status === 'sent'
|
||||
|| newsletter.status === 'sending') {
|
||||
options['disabled'] = 'disabled';
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -71,7 +71,16 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
},
|
||||
getSendButtonOptions: function(newsletter) {
|
||||
return {
|
||||
value: MailPoet.I18n.t('activate')
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -18,7 +18,6 @@ define(
|
||||
|
||||
var field = {
|
||||
name: 'options',
|
||||
label: 'Periodicity',
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
};
|
||||
@ -72,7 +71,7 @@ define(
|
||||
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<h3>{MailPoet.I18n.t('selectPeriodicity')}</h3>
|
||||
<h3>{MailPoet.I18n.t('selectFrequency')}</h3>
|
||||
|
||||
<Scheduling
|
||||
item={this.state}
|
||||
|
@ -1,100 +1,48 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'form/fields/select.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Select
|
||||
) {
|
||||
import _ from 'underscore'
|
||||
import React from 'react'
|
||||
import MailPoet from 'mailpoet'
|
||||
import Select from 'form/fields/select.jsx'
|
||||
import {
|
||||
intervalValues,
|
||||
timeOfDayValues,
|
||||
weekDayValues,
|
||||
monthDayValues,
|
||||
nthWeekDayValues
|
||||
} from 'newsletters/scheduling/common.jsx'
|
||||
|
||||
var intervalField = {
|
||||
const intervalField = {
|
||||
name: 'intervalType',
|
||||
values: {
|
||||
'daily': MailPoet.I18n.t('daily'),
|
||||
'weekly': MailPoet.I18n.t('weekly'),
|
||||
'monthly': MailPoet.I18n.t('monthly'),
|
||||
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
|
||||
'immediately': MailPoet.I18n.t('immediately'),
|
||||
},
|
||||
values: intervalValues
|
||||
};
|
||||
|
||||
var SECONDS_IN_DAY = 86400;
|
||||
var TIME_STEP_SECONDS = 3600; // Default: 3600
|
||||
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
|
||||
var timeOfDayValues = _.object(_.map(
|
||||
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
|
||||
function(seconds) {
|
||||
var date = new Date(null);
|
||||
date.setSeconds(seconds);
|
||||
var timeLabel = date.toISOString().substr(11, 5);
|
||||
return [seconds, timeLabel];
|
||||
}
|
||||
));
|
||||
var timeOfDayField = {
|
||||
const timeOfDayField = {
|
||||
name: 'timeOfDay',
|
||||
values: timeOfDayValues,
|
||||
values: timeOfDayValues
|
||||
};
|
||||
|
||||
var weekDayField = {
|
||||
const weekDayField = {
|
||||
name: 'weekDay',
|
||||
values: {
|
||||
0: MailPoet.I18n.t('sunday'),
|
||||
1: MailPoet.I18n.t('monday'),
|
||||
2: MailPoet.I18n.t('tuesday'),
|
||||
3: MailPoet.I18n.t('wednesday'),
|
||||
4: MailPoet.I18n.t('thursday'),
|
||||
5: MailPoet.I18n.t('friday'),
|
||||
6: MailPoet.I18n.t('saturday')
|
||||
},
|
||||
values: weekDayValues
|
||||
};
|
||||
|
||||
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
|
||||
var monthDayField = {
|
||||
const monthDayField = {
|
||||
name: 'monthDay',
|
||||
values: _.object(_.map(
|
||||
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
|
||||
function(day) {
|
||||
var labels = {
|
||||
0: MailPoet.I18n.t('first'),
|
||||
1: MailPoet.I18n.t('second'),
|
||||
2: MailPoet.I18n.t('third')
|
||||
},
|
||||
label;
|
||||
if (labels[day] !== undefined) {
|
||||
label = labels[day];
|
||||
} else {
|
||||
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
|
||||
}
|
||||
|
||||
return [day, label];
|
||||
},
|
||||
)),
|
||||
values: monthDayValues
|
||||
};
|
||||
|
||||
var nthWeekDayField = {
|
||||
const nthWeekDayField = {
|
||||
name: 'nthWeekDay',
|
||||
values: {
|
||||
'1': MailPoet.I18n.t('first'),
|
||||
'2': MailPoet.I18n.t('second'),
|
||||
'3': MailPoet.I18n.t('third'),
|
||||
'L': MailPoet.I18n.t('last'),
|
||||
},
|
||||
values: nthWeekDayValues
|
||||
};
|
||||
|
||||
var NotificationScheduling = React.createClass({
|
||||
const NotificationScheduling = React.createClass({
|
||||
_getCurrentValue: function() {
|
||||
return this.props.item[this.props.field.name] || {};
|
||||
return (this.props.item[this.props.field.name] || {});
|
||||
},
|
||||
handleValueChange: function(name, value) {
|
||||
var oldValue = this._getCurrentValue(),
|
||||
newValue = {};
|
||||
const oldValue = this._getCurrentValue();
|
||||
let newValue = {};
|
||||
|
||||
newValue[name] = value;
|
||||
|
||||
return this.props.onValueChange({
|
||||
@ -135,12 +83,11 @@ define(
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
var value = this._getCurrentValue(),
|
||||
timeOfDaySelection,
|
||||
weekDaySelection,
|
||||
monthDaySelection,
|
||||
nthWeekDaySelection;
|
||||
|
||||
const value = this._getCurrentValue();
|
||||
let timeOfDaySelection;
|
||||
let weekDaySelection;
|
||||
let monthDaySelection;
|
||||
let nthWeekDaySelection;
|
||||
|
||||
if (value.intervalType !== 'immediately') {
|
||||
timeOfDaySelection = (
|
||||
@ -151,8 +98,7 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
if (value.intervalType === 'weekly'
|
||||
|| value.intervalType === 'nthWeekDay') {
|
||||
if (value.intervalType === 'weekly' || value.intervalType === 'nthWeekDay') {
|
||||
weekDaySelection = (
|
||||
<Select
|
||||
field={weekDayField}
|
||||
@ -192,9 +138,7 @@ define(
|
||||
{timeOfDaySelection}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
return NotificationScheduling;
|
||||
}
|
||||
);
|
||||
module.exports = NotificationScheduling;
|
||||
|
@ -1,79 +1,71 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'form/fields/select.jsx',
|
||||
'form/fields/text.jsx',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Select,
|
||||
Text,
|
||||
Breadcrumb
|
||||
) {
|
||||
import _ from 'underscore'
|
||||
import React from 'react'
|
||||
import MailPoet from 'mailpoet'
|
||||
import Select from 'form/fields/select.jsx'
|
||||
import Text from 'form/fields/text.jsx'
|
||||
import {
|
||||
timeDelayValues,
|
||||
intervalValues
|
||||
} from 'newsletters/scheduling/common.jsx'
|
||||
|
||||
var availableRoles = window.mailpoet_roles || {};
|
||||
var availableSegments = window.mailpoet_segments || {};
|
||||
const availableRoles = window.mailpoet_roles || {};
|
||||
const availableSegments = _.filter(
|
||||
window.mailpoet_segments || [],
|
||||
function (segment) {
|
||||
return segment.type === 'default';
|
||||
}
|
||||
);
|
||||
|
||||
var events = {
|
||||
const events = {
|
||||
name: 'event',
|
||||
values: {
|
||||
'segment': MailPoet.I18n.t('onSubscriptionToList'),
|
||||
'user': MailPoet.I18n.t('onWordpressUserRegistration'),
|
||||
'user': MailPoet.I18n.t('onWPUserRegistration'),
|
||||
}
|
||||
};
|
||||
|
||||
var availableSegmentValues = _.object(_.map(
|
||||
const availableSegmentValues = _.object(_.map(
|
||||
availableSegments,
|
||||
function(segment) {
|
||||
var name = segment.name;
|
||||
let name = segment.name;
|
||||
if (segment.subscribers > 0) {
|
||||
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
|
||||
}
|
||||
return [segment.id, name];
|
||||
}
|
||||
));
|
||||
var segmentField = {
|
||||
const segmentField = {
|
||||
name: 'segment',
|
||||
values: availableSegmentValues,
|
||||
sortBy: (key, value) => value.toLowerCase()
|
||||
};
|
||||
|
||||
var roleField = {
|
||||
const roleField = {
|
||||
name: 'role',
|
||||
values: availableRoles,
|
||||
values: availableRoles
|
||||
};
|
||||
|
||||
var afterTimeNumberField = {
|
||||
const afterTimeNumberField = {
|
||||
name: 'afterTimeNumber',
|
||||
size: 3,
|
||||
size: 3
|
||||
};
|
||||
|
||||
var afterTimeTypeField = {
|
||||
const afterTimeTypeField = {
|
||||
name: 'afterTimeType',
|
||||
values: {
|
||||
'immediate': MailPoet.I18n.t('delayImmediately'),
|
||||
'hours': MailPoet.I18n.t('delayHoursAfter'),
|
||||
'days': MailPoet.I18n.t('delayDaysAfter'),
|
||||
'weeks': MailPoet.I18n.t('delayWeeksAfter'),
|
||||
}
|
||||
values: timeDelayValues
|
||||
};
|
||||
|
||||
var WelcomeScheduling = React.createClass({
|
||||
const WelcomeScheduling = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
_getCurrentValue: function() {
|
||||
return this.props.item[this.props.field.name] || {};
|
||||
return (this.props.item[this.props.field.name] || {});
|
||||
},
|
||||
handleValueChange: function(name, value) {
|
||||
var oldValue = this._getCurrentValue(),
|
||||
newValue = {};
|
||||
const oldValue = this._getCurrentValue();
|
||||
let newValue = {};
|
||||
|
||||
newValue[name] = value;
|
||||
|
||||
return this.props.onValueChange({
|
||||
@ -119,8 +111,8 @@ define(
|
||||
action: 'create',
|
||||
data: {
|
||||
type: 'welcome',
|
||||
options: this.state,
|
||||
},
|
||||
options: this.state
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response.result && response.newsletter.id) {
|
||||
this.showTemplateSelection(response.newsletter.id);
|
||||
@ -137,8 +129,9 @@ define(
|
||||
this.context.router.push(`/template/${ newsletterId }`);
|
||||
},
|
||||
render: function() {
|
||||
var value = this._getCurrentValue(),
|
||||
roleSegmentSelection, timeNumber;
|
||||
const value = this._getCurrentValue();
|
||||
let roleSegmentSelection;
|
||||
let timeNumber;
|
||||
|
||||
if (value.event === 'user') {
|
||||
roleSegmentSelection = (
|
||||
@ -184,7 +177,4 @@ define(
|
||||
},
|
||||
});
|
||||
|
||||
return WelcomeScheduling;
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = WelcomeScheduling;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Router, Link } from 'react-router'
|
||||
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
import classNames from 'classnames'
|
||||
@ -15,23 +14,19 @@ var columns = [
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: MailPoet.I18n.t('description'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('description')
|
||||
},
|
||||
{
|
||||
name: 'subscribed',
|
||||
label: MailPoet.I18n.t('subscribed'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('subscribed')
|
||||
},
|
||||
{
|
||||
name: 'unconfirmed',
|
||||
label: MailPoet.I18n.t('unconfirmed'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('unconfirmed')
|
||||
},
|
||||
{
|
||||
name: 'unsubscribed',
|
||||
label: MailPoet.I18n.t('unsubscribed'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('unsubscribed')
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
@ -191,13 +186,20 @@ const SegmentList = React.createClass({
|
||||
const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0);
|
||||
const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0);
|
||||
|
||||
let segment_name = (
|
||||
<Link to={ `/edit/${segment.id}` }>{ segment.name }</Link>
|
||||
);
|
||||
let segment_name;
|
||||
|
||||
// the WP users segment is not editable so just display its name
|
||||
if (segment.type === 'wp_users') {
|
||||
segment_name = segment.name;
|
||||
// the WP users segment is not editable so just display its name
|
||||
segment_name = (
|
||||
<span className="row-title">{ segment.name }</span>
|
||||
);
|
||||
} else {
|
||||
segment_name = (
|
||||
<Link
|
||||
className="row-title"
|
||||
to={ `/edit/${segment.id}` }
|
||||
>{ segment.name }</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -244,6 +246,8 @@ const SegmentList = React.createClass({
|
||||
columns={ columns }
|
||||
bulk_actions={ bulk_actions }
|
||||
item_actions={ item_actions }
|
||||
sort_by="name"
|
||||
sort_order="asc"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
|
||||
import SegmentList from 'segments/list.jsx'
|
||||
import SegmentForm from 'segments/form.jsx'
|
||||
|
||||
|
@ -34,16 +34,13 @@ define(
|
||||
// show sending methods
|
||||
jQuery('.mailpoet_sending_methods').fadeIn();
|
||||
} else {
|
||||
// toggle SPF/DKIM (hidden if the sending method is MailPoet)
|
||||
// toggle SPF (hidden if the sending method is MailPoet)
|
||||
jQuery('#mailpoet_mta_spf')[
|
||||
(group === 'mailpoet')
|
||||
? 'hide'
|
||||
: 'show'
|
||||
]();
|
||||
|
||||
// (HIDDEN FOR BETA)
|
||||
jQuery('#mailpoet_mta_dkim').hide();
|
||||
|
||||
// hide sending methods
|
||||
jQuery('.mailpoet_sending_methods').hide();
|
||||
|
||||
|
@ -73,7 +73,7 @@ define(
|
||||
});
|
||||
},
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
return !!(!segment.deleted_at && segment.type === 'default');
|
||||
},
|
||||
getSearchLabel: function(segment, subscriber) {
|
||||
let label = '';
|
||||
|
@ -154,8 +154,7 @@ define(
|
||||
.replace('%1$s', '<strong>' + parseInt(response.data.totalExported).toLocaleString() + '</strong>')
|
||||
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
|
||||
.replace('[/link]', '</a>');
|
||||
jQuery('#export_result_notice > ul > li').html(resultMessage);
|
||||
jQuery('#export_result_notice').show();
|
||||
jQuery('#export_result_notice').html('<p>' + resultMessage + '</p>').show();
|
||||
window.location.href = response.data.exportFileURL;
|
||||
}
|
||||
})
|
||||
|
@ -6,9 +6,10 @@ define(
|
||||
'mailpoet',
|
||||
'handlebars',
|
||||
'papaparse',
|
||||
'select2',
|
||||
'asyncqueue',
|
||||
'xss'
|
||||
'xss',
|
||||
'moment',
|
||||
'select2'
|
||||
],
|
||||
function (
|
||||
Backbone,
|
||||
@ -18,7 +19,8 @@ define(
|
||||
Handlebars,
|
||||
Papa,
|
||||
AsyncQueue,
|
||||
xss
|
||||
xss,
|
||||
Moment
|
||||
) {
|
||||
if (!jQuery('#mailpoet_subscribers_import').length) {
|
||||
return;
|
||||
@ -69,7 +71,7 @@ define(
|
||||
jQuery('#method_paste > div.mailpoet_method_process')
|
||||
.find('a.mailpoet_process'),
|
||||
mailChimpKeyInputElement = jQuery('#mailchimp_key'),
|
||||
mailChimpKeyVerifyButtonEelement = jQuery('#mailchimp_key_verify'),
|
||||
mailChimpKeyVerifyButtonElement = jQuery('#mailchimp_key_verify'),
|
||||
mailChimpListsContainerElement = jQuery('#mailchimp_lists'),
|
||||
mailChimpProcessButtonElement =
|
||||
jQuery('#method_mailchimp > div.mailpoet_method_process')
|
||||
@ -176,15 +178,11 @@ define(
|
||||
jQuery('.mailpoet_mailchimp-key-status')
|
||||
.html('')
|
||||
.removeClass('mailpoet_mailchimp-ok mailpoet_mailchimp-error');
|
||||
mailChimpKeyVerifyButtonEelement.prop('disabled', true);
|
||||
toggleNextStepButton(mailChimpProcessButtonElement, 'off');
|
||||
}
|
||||
else {
|
||||
mailChimpKeyVerifyButtonEelement.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
mailChimpKeyVerifyButtonEelement.click(function () {
|
||||
mailChimpKeyVerifyButtonElement.click(function () {
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'ImportExport',
|
||||
@ -307,25 +305,25 @@ define(
|
||||
// trim spaces, commas, periods,
|
||||
// single/double quotes and convert to lowercase
|
||||
detectAndCleanupEmail = function (email) {
|
||||
email = email.toLowerCase();
|
||||
var test,
|
||||
cleanEmail =
|
||||
email
|
||||
var test;
|
||||
// decode HTML entities
|
||||
email = jQuery('<div />').html(email).text();
|
||||
email = email
|
||||
.toLowerCase()
|
||||
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
|
||||
// right trim non-printable characters (e.g., "email@email.com<6F>")
|
||||
.replace(/^["';.,\s]+|[^\x20-\x7E]+$|["';.,_\s]+$/g, '')
|
||||
// remove spaces (e.g., "email @ email . com")
|
||||
// remove urlencoded characters
|
||||
.replace(/\s+|%\d+|,+/g, '')
|
||||
.toLowerCase();
|
||||
// detect e-mails that will otherwise be rejected by ^email_regex$
|
||||
.replace(/\s+|%\d+|,+/g, '');
|
||||
// detect e-mails that will be otherwise rejected by email regex
|
||||
if (test = /<(.*?)>/.exec(email)) {
|
||||
// is email inside angle brackets (e.g., 'some@email.com <some@email.com>')?
|
||||
return test[1].trim();
|
||||
// is the email inside angle brackets (e.g., 'some@email.com <some@email.com>')?
|
||||
email = test[1].trim();
|
||||
}
|
||||
else if (test = /mailto:(?:\s+)?(.*)/.exec(email)) {
|
||||
// is email in 'mailto:email' format?
|
||||
return test[1].trim();
|
||||
if (test = /mailto:(?:\s+)?(.*)/.exec(email)) {
|
||||
// is the email in 'mailto:email' format?
|
||||
email = test[1].trim();
|
||||
}
|
||||
return email;
|
||||
};
|
||||
@ -527,6 +525,7 @@ define(
|
||||
segmentSelectElement
|
||||
.html('')
|
||||
.select2('destroy');
|
||||
toggleNextStepButton('off');
|
||||
}
|
||||
segmentSelectElement
|
||||
.select2({
|
||||
@ -924,31 +923,9 @@ define(
|
||||
// DATE filter: if column type is date, check if we can recognize it
|
||||
if (column.type === 'date' && matchedColumn !== -1) {
|
||||
jQuery.map(subscribersClone.subscribers, function (data, position) {
|
||||
var rowData = data[matchedColumn],
|
||||
date = new Date(rowData.replace(/-/g, '/')), // IE doesn't like
|
||||
// dashes as date separators
|
||||
month_name = [
|
||||
MailPoet.I18n.t('january'),
|
||||
MailPoet.I18n.t('february'),
|
||||
MailPoet.I18n.t('march'),
|
||||
MailPoet.I18n.t('april'),
|
||||
MailPoet.I18n.t('may'),
|
||||
MailPoet.I18n.t('june'),
|
||||
MailPoet.I18n.t('july'),
|
||||
MailPoet.I18n.t('august'),
|
||||
MailPoet.I18n.t('september'),
|
||||
MailPoet.I18n.t('october'),
|
||||
MailPoet.I18n.t('november'),
|
||||
MailPoet.I18n.t('december')
|
||||
];
|
||||
|
||||
var rowData = data[matchedColumn];
|
||||
if (position !== fillterPosition) {
|
||||
// check for valid date:
|
||||
// * invalid date object returns NaN for getTime() and NaN
|
||||
// is the only object not strictly equal to itself
|
||||
// * date must have period/dash/slash OR be at least 4
|
||||
// characters long (e.g., year)
|
||||
// * must be before now
|
||||
// check if date exists
|
||||
if (rowData.trim() === '') {
|
||||
data[matchedColumn] =
|
||||
'<span class="mailpoet_data_match mailpoet_import_error" title="'
|
||||
@ -958,25 +935,12 @@ define(
|
||||
preventNextStep = true;
|
||||
return;
|
||||
}
|
||||
else if (date.getTime() === date.getTime() &&
|
||||
(/[.-\/]/.test(rowData) || rowData.length >= 4) &&
|
||||
date.getTime() < (new Date()).getTime()
|
||||
) {
|
||||
date = '/ '
|
||||
+ month_name[date.getMonth()]
|
||||
+ ' ' + date.getDate() + ', '
|
||||
+ date.getFullYear() + ' '
|
||||
+ date.getHours() + ':'
|
||||
+ ((date.getMinutes() < 10 ? '0' : '')
|
||||
+ date.getMinutes()) + ' '
|
||||
+ ((date.getHours() >= 12)
|
||||
? MailPoet.I18n.t('pm')
|
||||
: MailPoet.I18n.t('am')
|
||||
);
|
||||
// check if date is valid and is before today
|
||||
if (Moment(rowData).isValid() && Moment(rowData).isBefore(Moment())) {
|
||||
data[matchedColumn] +=
|
||||
'<span class="mailpoet_data_match" title="'
|
||||
+ MailPoet.I18n.t('verifyDateMatch') + '">'
|
||||
+ MailPoet.Date.format(date) + '</span>';
|
||||
+ MailPoet.Date.format(rowData) + '</span>';
|
||||
}
|
||||
else {
|
||||
data[matchedColumn] +=
|
||||
@ -1083,6 +1047,7 @@ define(
|
||||
importResults.created = response.data.created;
|
||||
importResults.updated = response.data.updated;
|
||||
importResults.segments = response.data.segments;
|
||||
importResults.added_to_segment_with_welcome_notification = response.data.added_to_segment_with_welcome_notification;
|
||||
}
|
||||
queue.run();
|
||||
})
|
||||
@ -1112,7 +1077,6 @@ define(
|
||||
});
|
||||
importData.step2 = importResults;
|
||||
enableSegmentSelection(mailpoetSegments);
|
||||
toggleNextStepButton('off');
|
||||
router.navigate('step3', {trigger: true});
|
||||
}
|
||||
});
|
||||
@ -1151,9 +1115,8 @@ define(
|
||||
.replace('%1$s', '<strong>' + importData.step2.updated.toLocaleString() + '</strong>')
|
||||
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
|
||||
: false,
|
||||
noaction: (!importData.step2.updated && !importData.step2.created)
|
||||
? true
|
||||
: false
|
||||
no_action: (!importData.step2.created && !importData.step2.updated),
|
||||
added_to_segment_with_welcome_notification: importData.step2.added_to_segment_with_welcome_notification
|
||||
};
|
||||
|
||||
jQuery('#subscribers_data_import_results')
|
||||
|
@ -21,8 +21,7 @@ const columns = [
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: MailPoet.I18n.t('lists'),
|
||||
sortable: false
|
||||
label: MailPoet.I18n.t('lists')
|
||||
},
|
||||
|
||||
{
|
||||
@ -48,7 +47,7 @@ const messages = {
|
||||
} else if (~~response > 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleSubscribersTrashed')
|
||||
).replace('%$1d', ~~response);
|
||||
).replace('%$1d', (~~response).toLocaleString());
|
||||
}
|
||||
|
||||
if (message !== null) {
|
||||
@ -84,7 +83,7 @@ const messages = {
|
||||
} else if (~~response > 1) {
|
||||
message = (
|
||||
MailPoet.I18n.t('multipleSubscribersRestored')
|
||||
).replace('%$1d', ~~response);
|
||||
).replace('%$1d', (~~response).toLocaleString());
|
||||
}
|
||||
|
||||
if (message !== null) {
|
||||
@ -106,11 +105,6 @@ const bulk_actions = [
|
||||
return !!(
|
||||
!segment.deleted_at && segment.type === 'default'
|
||||
);
|
||||
},
|
||||
getLabel: function (segment) {
|
||||
return (segment.subscribers > 0) ?
|
||||
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
|
||||
segment.name;
|
||||
}
|
||||
};
|
||||
|
||||
@ -126,7 +120,7 @@ const bulk_actions = [
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('multipleSubscribersMovedToList')
|
||||
.replace('%$1d', ~~response.subscribers)
|
||||
.replace('%$1d', (~~(response.subscribers)).toLocaleString())
|
||||
.replace('%$2s', response.segment)
|
||||
);
|
||||
}
|
||||
@ -142,11 +136,6 @@ const bulk_actions = [
|
||||
return !!(
|
||||
!segment.deleted_at && segment.type === 'default'
|
||||
);
|
||||
},
|
||||
getLabel: function (segment) {
|
||||
return (segment.subscribers > 0) ?
|
||||
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
|
||||
segment.name;
|
||||
}
|
||||
};
|
||||
|
||||
@ -162,7 +151,7 @@ const bulk_actions = [
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('multipleSubscribersAddedToList')
|
||||
.replace('%$1d', ~~response.subscribers)
|
||||
.replace('%$1d', (~~response.subscribers).toLocaleString())
|
||||
.replace('%$2s', response.segment)
|
||||
);
|
||||
}
|
||||
@ -178,11 +167,6 @@ const bulk_actions = [
|
||||
return !!(
|
||||
segment.type === 'default'
|
||||
);
|
||||
},
|
||||
getLabel: function (segment) {
|
||||
return (segment.subscribers > 0) ?
|
||||
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
|
||||
segment.name;
|
||||
}
|
||||
};
|
||||
|
||||
@ -198,7 +182,7 @@ const bulk_actions = [
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('multipleSubscribersRemovedFromList')
|
||||
.replace('%$1d', ~~response.subscribers)
|
||||
.replace('%$1d', (~~response.subscribers).toLocaleString())
|
||||
.replace('%$2s', response.segment)
|
||||
);
|
||||
}
|
||||
@ -209,7 +193,7 @@ const bulk_actions = [
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('multipleSubscribersRemovedFromAllLists')
|
||||
.replace('%$1d', ~~response)
|
||||
.replace('%$1d', (~~response).toLocaleString())
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -219,7 +203,7 @@ const bulk_actions = [
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('multipleConfirmationEmailsSent')
|
||||
.replace('%$1d', ~~response)
|
||||
.replace('%$1d', (~~response).toLocaleString())
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -234,14 +218,17 @@ const item_actions = [
|
||||
{
|
||||
name: 'edit',
|
||||
label: MailPoet.I18n.t('edit'),
|
||||
link: function(item) {
|
||||
link: function(subscriber) {
|
||||
return (
|
||||
<Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
|
||||
<Link to={ `/edit/${subscriber.id}` }>{MailPoet.I18n.t('edit')}</Link>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
name: 'trash',
|
||||
display: function(subscriber) {
|
||||
return !!(~~subscriber.wp_user_id === 0);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -280,15 +267,11 @@ const SubscriberList = React.createClass({
|
||||
}
|
||||
|
||||
let segments = false;
|
||||
let subscribed_segments = [];
|
||||
|
||||
// WordPress Users
|
||||
if (~~(subscriber.wp_user_id) > 0) {
|
||||
subscribed_segments.push(MailPoet.I18n.t('WPUsersSegment'));
|
||||
}
|
||||
|
||||
// Subscriptions
|
||||
if (subscriber.subscriptions.length > 0) {
|
||||
let subscribed_segments = [];
|
||||
|
||||
subscriber.subscriptions.map((subscription) => {
|
||||
const segment = this.getSegmentFromId(subscription.segment_id);
|
||||
if (segment === false) return;
|
||||
@ -296,13 +279,14 @@ const SubscriberList = React.createClass({
|
||||
subscribed_segments.push(segment.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
segments = (
|
||||
<span>
|
||||
{ subscribed_segments.join(', ') }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
let avatar = false;
|
||||
if (subscriber.avatar_url) {
|
||||
@ -320,9 +304,12 @@ const SubscriberList = React.createClass({
|
||||
return (
|
||||
<div>
|
||||
<td className={ row_classes }>
|
||||
<strong><Link to={ `/edit/${ subscriber.id }` }>
|
||||
{ subscriber.email }
|
||||
</Link></strong>
|
||||
<strong>
|
||||
<Link
|
||||
className="row-title"
|
||||
to={ `/edit/${ subscriber.id }` }
|
||||
>{ subscriber.email }</Link>
|
||||
</strong>
|
||||
<p style={{margin: 0}}>
|
||||
{ subscriber.first_name } { subscriber.last_name }
|
||||
</p>
|
||||
@ -366,6 +353,8 @@ const SubscriberList = React.createClass({
|
||||
item_actions={ item_actions }
|
||||
messages={ messages }
|
||||
onGetItems={ this.onGetItems }
|
||||
sort_by={ 'created_at' }
|
||||
sort_order={ 'desc' }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -26,6 +26,7 @@ coverage:
|
||||
include:
|
||||
- lib/*
|
||||
exclude:
|
||||
blacklist:
|
||||
include:
|
||||
exclude:
|
||||
- lib/Util/Sudzy/*
|
||||
- lib/Util/CSS.php
|
||||
- lib/Util/Helpers.php
|
||||
- lib/Util/XLSXWriter.php
|
||||
|
@ -1,4 +1,10 @@
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/mailpoet/html2text"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"twig/twig": "1.*",
|
||||
@ -11,7 +17,7 @@
|
||||
"phpseclib/phpseclib": "*",
|
||||
"mtdowling/cron-expression": "^1.1",
|
||||
"nesbot/carbon": "^1.21",
|
||||
"soundasleep/html2text": "^0.3.0"
|
||||
"soundasleep/html2text": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "*",
|
||||
@ -19,7 +25,8 @@
|
||||
"codegyre/robo": "*",
|
||||
"vlucas/phpdotenv": "*",
|
||||
"umpirsky/twig-gettext-extractor": "1.1.*",
|
||||
"raveren/kint": "^1.0"
|
||||
"raveren/kint": "^1.0",
|
||||
"squizlabs/php_codesniffer": "2.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
1020
composer.lock
generated
1020
composer.lock
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
83
lib/API/API.php
Normal file
83
lib/API/API.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
public $api_request;
|
||||
public $endpoint;
|
||||
public $action;
|
||||
public $data;
|
||||
const NAME = 'mailpoet_api';
|
||||
const ENDPOINT_NAMESPACE = '\MailPoet\API\Endpoints\\';
|
||||
const RESPONSE_ERROR = 404;
|
||||
|
||||
function __construct($api_data = false) {
|
||||
$api_data = ($api_data) ? $api_data : $_GET;
|
||||
$this->api_request = isset($api_data[self::NAME]);
|
||||
$this->endpoint = isset($api_data['endpoint']) ?
|
||||
Helpers::underscoreToCamelCase($api_data['endpoint']) :
|
||||
false;
|
||||
$this->action = isset($api_data['action']) ?
|
||||
Helpers::underscoreToCamelCase($api_data['action']) :
|
||||
false;
|
||||
$this->data = isset($api_data['data']) ?
|
||||
self::decodeRequestData($api_data['data']) :
|
||||
false;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$endpoint = self::ENDPOINT_NAMESPACE . ucfirst($this->endpoint);
|
||||
if(!$this->api_request) return;
|
||||
if(!$this->endpoint || !class_exists($endpoint)) {
|
||||
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API endpoint.'));
|
||||
}
|
||||
$this->callEndpoint(
|
||||
$endpoint,
|
||||
$this->action,
|
||||
$this->data
|
||||
);
|
||||
}
|
||||
|
||||
function callEndpoint($endpoint, $action, $data) {
|
||||
if(!method_exists($endpoint, $action)) {
|
||||
$this->terminateRequest(self::RESPONSE_ERROR, __('Invalid API action.'));
|
||||
}
|
||||
call_user_func(
|
||||
array(
|
||||
$endpoint,
|
||||
$action
|
||||
),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
static function decodeRequestData($data) {
|
||||
$data = base64_decode($data);
|
||||
return (is_serialized($data)) ?
|
||||
unserialize($data) :
|
||||
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid API data format.'));
|
||||
}
|
||||
|
||||
static function encodeRequestData($data) {
|
||||
return rtrim(base64_encode(serialize($data)), '=');
|
||||
}
|
||||
|
||||
static function buildRequest($endpoint, $action, $data) {
|
||||
$data = self::encodeRequestData($data);
|
||||
$params = array(
|
||||
self::NAME => '',
|
||||
'endpoint' => $endpoint,
|
||||
'action' => $action,
|
||||
'data' => $data
|
||||
);
|
||||
return add_query_arg($params, home_url());
|
||||
}
|
||||
|
||||
function terminateRequest($code, $message) {
|
||||
status_header($code, $message);
|
||||
exit;
|
||||
}
|
||||
}
|
16
lib/API/Endpoints/Queue.php
Normal file
16
lib/API/Endpoints/Queue.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use MailPoet\Cron\Daemon;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Queue {
|
||||
const ENDPOINT = 'queue';
|
||||
const ACTION_RUN = 'run';
|
||||
|
||||
static function run($data) {
|
||||
$queue = new Daemon($data);
|
||||
$queue->run();
|
||||
}
|
||||
}
|
22
lib/API/Endpoints/Subscription.php
Normal file
22
lib/API/Endpoints/Subscription.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use MailPoet\Subscription as UserSubscription;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Subscription {
|
||||
const ENDPOINT = 'subscription';
|
||||
|
||||
static function confirm($data) {
|
||||
$subscription = new UserSubscription\Pages('confirm', $data);
|
||||
}
|
||||
|
||||
static function manage($data) {
|
||||
$subscription = new UserSubscription\Pages('manage', $data);
|
||||
}
|
||||
|
||||
static function unsubscribe($data) {
|
||||
$subscription = new UserSubscription\Pages('unsubscribe', $data);
|
||||
}
|
||||
}
|
23
lib/API/Endpoints/Track.php
Normal file
23
lib/API/Endpoints/Track.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use MailPoet\Statistics\Track\Clicks;
|
||||
use MailPoet\Statistics\Track\Opens;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Track {
|
||||
const ENDPOINT = 'track';
|
||||
const ACTION_CLICK = 'click';
|
||||
const ACTION_OPEN = 'open';
|
||||
|
||||
static function click($data) {
|
||||
$clicks = new Clicks($data);
|
||||
$clicks->track();
|
||||
}
|
||||
|
||||
static function open($data) {
|
||||
$opens = new Opens($data);
|
||||
$opens->track();
|
||||
}
|
||||
}
|
16
lib/API/Endpoints/ViewInBrowser.php
Normal file
16
lib/API/Endpoints/ViewInBrowser.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class ViewInBrowser {
|
||||
const ENDPOINT = 'view_in_browser';
|
||||
const ACTION_VIEW = 'view';
|
||||
|
||||
static function view($data) {
|
||||
$viewer = new NewsletterViewInBrowser($data);
|
||||
$viewer->view();
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ class Reporter {
|
||||
'Plugin Version' => 'pluginVersion',
|
||||
);
|
||||
|
||||
function __construct() {}
|
||||
|
||||
function getData() {
|
||||
$_this = $this;
|
||||
|
||||
|
@ -10,8 +10,7 @@ class Analytics {
|
||||
}
|
||||
|
||||
function init() {
|
||||
// review: this creates a fatal error when mailpoet tables are dropped.
|
||||
//add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
|
||||
add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
|
||||
}
|
||||
|
||||
function setupAdminDependencies() {
|
||||
|
@ -81,6 +81,16 @@ class Hooks {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Manage subscription
|
||||
add_action(
|
||||
'admin_post_mailpoet_subscription_update',
|
||||
'\MailPoet\Subscription\Manage::onSave'
|
||||
);
|
||||
add_action(
|
||||
'admin_post_nopriv_mailpoet_subscription_update',
|
||||
'\MailPoet\Subscription\Manage::onSave'
|
||||
);
|
||||
}
|
||||
|
||||
function setupWPUsers() {
|
||||
|
@ -33,7 +33,6 @@ class Initializer {
|
||||
$this->setupRenderer();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
$this->setupPermissions();
|
||||
$this->setupAnalytics();
|
||||
$this->setupChangelog();
|
||||
$this->setupShortcodes();
|
||||
@ -49,7 +48,7 @@ class Initializer {
|
||||
|
||||
function onInit() {
|
||||
$this->setupRouter();
|
||||
$this->setupPublicAPI();
|
||||
$this->setupAPI();
|
||||
$this->setupPages();
|
||||
}
|
||||
|
||||
@ -57,11 +56,6 @@ class Initializer {
|
||||
\ORM::configure(Env::$db_source_name);
|
||||
\ORM::configure('username', Env::$db_username);
|
||||
\ORM::configure('password', Env::$db_password);
|
||||
\ORM::configure('logging', WP_DEBUG);
|
||||
\ORM::configure('logger', function($query, $time) {
|
||||
// error_log("\n".$query."\n");
|
||||
});
|
||||
|
||||
\ORM::configure('driver_options', array(
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
||||
@ -151,11 +145,6 @@ class Initializer {
|
||||
$widget->init();
|
||||
}
|
||||
|
||||
function setupPermissions() {
|
||||
$permissions = new Permissions();
|
||||
$permissions->init();
|
||||
}
|
||||
|
||||
function setupChangelog() {
|
||||
$changelog = new Changelog();
|
||||
$changelog->init();
|
||||
@ -176,9 +165,9 @@ class Initializer {
|
||||
$hooks->init();
|
||||
}
|
||||
|
||||
function setupPublicAPI() {
|
||||
$publicAPI = new PublicAPI();
|
||||
$publicAPI->init();
|
||||
function setupAPI() {
|
||||
$API = new \MailPoet\API\API();
|
||||
$API->init();
|
||||
}
|
||||
|
||||
function runQueueSupervisor() {
|
||||
@ -187,6 +176,7 @@ class Initializer {
|
||||
$supervisor = new Supervisor();
|
||||
$supervisor->checkDaemon();
|
||||
} catch(\Exception $e) {
|
||||
// Prevent Daemon exceptions from breaking out and breaking UI
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,10 @@ use MailPoet\Models\CustomField;
|
||||
use MailPoet\Models\Form;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Settings\Charsets;
|
||||
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
|
||||
use MailPoet\Settings\Hosts;
|
||||
use MailPoet\Settings\Pages;
|
||||
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
|
||||
use MailPoet\Util\DKIM;
|
||||
use MailPoet\Util\Permissions;
|
||||
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\WP\DateTime;
|
||||
|
||||
@ -35,28 +33,25 @@ class Menu {
|
||||
}
|
||||
|
||||
function setup() {
|
||||
$main_page_slug = 'mailpoet-newsletters';
|
||||
|
||||
add_menu_page(
|
||||
'MailPoet',
|
||||
'MailPoet',
|
||||
'manage_options',
|
||||
'mailpoet',
|
||||
array(
|
||||
$this,
|
||||
'home'
|
||||
),
|
||||
$main_page_slug,
|
||||
null,
|
||||
$this->assets_url . '/img/menu_icon.png',
|
||||
30
|
||||
);
|
||||
|
||||
$newsletters_page = add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Newsletters')),
|
||||
__('Newsletters'),
|
||||
'manage_options',
|
||||
'mailpoet-newsletters',
|
||||
array(
|
||||
$this,
|
||||
'newsletters'
|
||||
)
|
||||
$main_page_slug,
|
||||
array($this, 'newsletters')
|
||||
);
|
||||
|
||||
// add limit per page to screen options
|
||||
@ -71,15 +66,12 @@ class Menu {
|
||||
});
|
||||
|
||||
$forms_page = add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Forms')),
|
||||
__('Forms'),
|
||||
'manage_options',
|
||||
'mailpoet-forms',
|
||||
array(
|
||||
$this,
|
||||
'forms'
|
||||
)
|
||||
array($this, 'forms')
|
||||
);
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$forms_page, function() {
|
||||
@ -93,15 +85,12 @@ class Menu {
|
||||
});
|
||||
|
||||
$subscribers_page = add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Subscribers')),
|
||||
__('Subscribers'),
|
||||
'manage_options',
|
||||
'mailpoet-subscribers',
|
||||
array(
|
||||
$this,
|
||||
'subscribers'
|
||||
)
|
||||
array($this, 'subscribers')
|
||||
);
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$subscribers_page, function() {
|
||||
@ -115,15 +104,12 @@ class Menu {
|
||||
});
|
||||
|
||||
$segments_page = add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Segments')),
|
||||
__('Segments'),
|
||||
'manage_options',
|
||||
'mailpoet-segments',
|
||||
array(
|
||||
$this,
|
||||
'segments'
|
||||
)
|
||||
array($this, 'segments')
|
||||
);
|
||||
|
||||
// add limit per page to screen options
|
||||
@ -138,15 +124,12 @@ class Menu {
|
||||
});
|
||||
|
||||
add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle( __('Settings')),
|
||||
__('Settings'),
|
||||
'manage_options',
|
||||
'mailpoet-settings',
|
||||
array(
|
||||
$this,
|
||||
'settings'
|
||||
)
|
||||
array($this, 'settings')
|
||||
);
|
||||
add_submenu_page(
|
||||
'admin.php?page=mailpoet-subscribers',
|
||||
@ -154,10 +137,7 @@ class Menu {
|
||||
__('Import'),
|
||||
'manage_options',
|
||||
'mailpoet-import',
|
||||
array(
|
||||
$this,
|
||||
'import'
|
||||
)
|
||||
array($this, 'import')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
@ -166,10 +146,7 @@ class Menu {
|
||||
__('Export'),
|
||||
'manage_options',
|
||||
'mailpoet-export',
|
||||
array(
|
||||
$this,
|
||||
'export'
|
||||
)
|
||||
array($this, 'export')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
@ -178,10 +155,7 @@ class Menu {
|
||||
__('Welcome'),
|
||||
'manage_options',
|
||||
'mailpoet-welcome',
|
||||
array(
|
||||
$this,
|
||||
'welcome'
|
||||
)
|
||||
array($this, 'welcome')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
@ -190,10 +164,7 @@ class Menu {
|
||||
__('Update'),
|
||||
'manage_options',
|
||||
'mailpoet-update',
|
||||
array(
|
||||
$this,
|
||||
'update'
|
||||
)
|
||||
array($this, 'update')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
@ -202,42 +173,28 @@ class Menu {
|
||||
__('Form editor'),
|
||||
'manage_options',
|
||||
'mailpoet-form-editor',
|
||||
array(
|
||||
$this,
|
||||
'formEditor'
|
||||
)
|
||||
array($this, 'formEditor')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Newsletter')),
|
||||
__('Newsletter editor'),
|
||||
__('Newsletter Editor'),
|
||||
'manage_options',
|
||||
'mailpoet-newsletter-editor',
|
||||
array(
|
||||
$this,
|
||||
'newletterEditor'
|
||||
)
|
||||
array($this, 'newletterEditor')
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'mailpoet',
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Cron')),
|
||||
__('Cron'),
|
||||
'manage_options',
|
||||
'mailpoet-cron',
|
||||
array(
|
||||
$this,
|
||||
'cron'
|
||||
)
|
||||
array($this, 'cron')
|
||||
);
|
||||
}
|
||||
|
||||
function home() {
|
||||
$data = array();
|
||||
echo $this->renderer->render('index.html', $data);
|
||||
}
|
||||
|
||||
function welcome() {
|
||||
if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
|
||||
|
||||
@ -253,13 +210,14 @@ class Menu {
|
||||
or
|
||||
strpos($redirect_url, 'mailpoet') === false
|
||||
) {
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet');
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-newsletters');
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'settings' => Setting::getAll(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'redirect_url' => $redirect_url
|
||||
'redirect_url' => $redirect_url,
|
||||
'sub_menu' => 'mailpoet-newsletters'
|
||||
);
|
||||
echo $this->renderer->render('welcome.html', $data);
|
||||
}
|
||||
@ -277,13 +235,14 @@ class Menu {
|
||||
or
|
||||
strpos($redirect_url, 'mailpoet') === false
|
||||
) {
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet');
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-newsletters');
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'settings' => Setting::getAll(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'redirect_url' => $redirect_url
|
||||
'redirect_url' => $redirect_url,
|
||||
'sub_menu' => 'mailpoet-newsletters'
|
||||
);
|
||||
|
||||
echo $this->renderer->render('update.html', $data);
|
||||
@ -293,29 +252,12 @@ class Menu {
|
||||
$settings = Setting::getAll();
|
||||
$flags = $this->_getFlags();
|
||||
|
||||
// dkim: check if public/private keys have been generated
|
||||
if(
|
||||
empty($settings['dkim'])
|
||||
or empty($settings['dkim']['public_key'])
|
||||
or empty($settings['dkim']['private_key'])
|
||||
) {
|
||||
// generate public/private keys
|
||||
$keys = DKIM::generateKeys();
|
||||
$settings['dkim'] = array(
|
||||
'public_key' => $keys['public'],
|
||||
'private_key' => $keys['private'],
|
||||
'domain' => preg_replace('/^www\./', '', $_SERVER['SERVER_NAME'])
|
||||
);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'settings' => $settings,
|
||||
'segments' => Segment::getPublic()->findArray(),
|
||||
'pages' => Pages::getAll(),
|
||||
'flags' => $flags,
|
||||
'charsets' => Charsets::getAll(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'permissions' => Permissions::getAll(),
|
||||
'hosts' => array(
|
||||
'web' => Hosts::getWebHosts(),
|
||||
'smtp' => Hosts::getSMTPHosts()
|
||||
@ -355,7 +297,7 @@ class Menu {
|
||||
$data = array();
|
||||
|
||||
$data['items_per_page'] = $this->getLimitPerPage('subscribers');
|
||||
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
|
||||
$data['segments'] = Segment::findArray();
|
||||
|
||||
$data['custom_fields'] = array_map(function($field) {
|
||||
$field['params'] = unserialize($field['params']);
|
||||
@ -398,7 +340,7 @@ class Menu {
|
||||
$data = array();
|
||||
|
||||
$data['items_per_page'] = $this->getLimitPerPage('newsletters');
|
||||
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
|
||||
$data['segments'] = Segment::getSegmentsWithSubscriberCount($type = false);
|
||||
$data['settings'] = Setting::getAll();
|
||||
$data['roles'] = $wp_roles->get_names();
|
||||
$data['roles']['mailpoet_all'] = __('In any WordPress role');
|
||||
@ -412,6 +354,8 @@ class Menu {
|
||||
24
|
||||
);
|
||||
|
||||
$data['tracking_enabled'] = Setting::getValue('tracking.enabled');
|
||||
|
||||
wp_enqueue_script('jquery-ui');
|
||||
wp_enqueue_script('jquery-ui-datepicker');
|
||||
|
||||
@ -419,15 +363,8 @@ class Menu {
|
||||
}
|
||||
|
||||
function newletterEditor() {
|
||||
$custom_fields = array_map(function($field) {
|
||||
return array(
|
||||
'text' => $field['name'],
|
||||
'shortcode' => 'field:' . $field['id'],
|
||||
);
|
||||
}, CustomField::findArray());
|
||||
|
||||
$data = array(
|
||||
'customFields' => $custom_fields,
|
||||
'shortcodes' => ShortcodesHelper::getShortcodes(),
|
||||
'settings' => Setting::getAll(),
|
||||
'sub_menu' => 'mailpoet-newsletters'
|
||||
);
|
||||
@ -438,14 +375,14 @@ class Menu {
|
||||
}
|
||||
|
||||
function import() {
|
||||
$import = new BootStrapMenu('import');
|
||||
$import = new ImportExportFactory('import');
|
||||
$data = $import->bootstrap();
|
||||
$data['sub_menu'] = 'mailpoet-subscribers';
|
||||
echo $this->renderer->render('subscribers/importExport/import.html', $data);
|
||||
}
|
||||
|
||||
function export() {
|
||||
$export = new BootStrapMenu('export');
|
||||
$export = new ImportExportFactory('export');
|
||||
$data = $export->bootstrap();
|
||||
$data['sub_menu'] = 'mailpoet-subscribers';
|
||||
echo $this->renderer->render('subscribers/importExport/export.html', $data);
|
||||
@ -484,7 +421,7 @@ class Menu {
|
||||
);
|
||||
}
|
||||
|
||||
function getLimitPerPage($model = null) {
|
||||
private function getLimitPerPage($model = null) {
|
||||
if($model === null) {
|
||||
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
@ -38,7 +42,8 @@ class Migrator {
|
||||
|
||||
$_this = $this;
|
||||
$migrate = function($model) use($_this) {
|
||||
dbDelta($_this->$model());
|
||||
$modelMethod = Helpers::underscoreToCamelCase($model);
|
||||
dbDelta($_this->$modelMethod());
|
||||
};
|
||||
|
||||
array_map($migrate, $this->models);
|
||||
@ -62,8 +67,8 @@ class Migrator {
|
||||
'type varchar(90) NOT NULL DEFAULT "default",',
|
||||
'description varchar(250) NOT NULL DEFAULT "",',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY name (name)'
|
||||
);
|
||||
@ -75,7 +80,7 @@ class Migrator {
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'name varchar(20) NOT NULL,',
|
||||
'value longtext,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY name (name)'
|
||||
@ -83,13 +88,13 @@ class Migrator {
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function custom_fields() {
|
||||
function customFields() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'name varchar(90) NOT NULL,',
|
||||
'type varchar(90) NOT NULL,',
|
||||
'params longtext NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY name (name)'
|
||||
@ -97,7 +102,7 @@ class Migrator {
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function sending_queues() {
|
||||
function sendingQueues() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
@ -113,7 +118,7 @@ class Migrator {
|
||||
'scheduled_at TIMESTAMP NULL,',
|
||||
'processed_at TIMESTAMP NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
@ -127,9 +132,9 @@ class Migrator {
|
||||
'first_name tinytext NOT NULL DEFAULT "",',
|
||||
'last_name tinytext NOT NULL DEFAULT "",',
|
||||
'email varchar(150) NOT NULL,',
|
||||
'status varchar(12) NOT NULL DEFAULT "unconfirmed",',
|
||||
'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_UNCONFIRMED . '",',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY email (email)'
|
||||
@ -137,28 +142,28 @@ class Migrator {
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function subscriber_segment() {
|
||||
function subscriberSegment() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'segment_id mediumint(9) NOT NULL,',
|
||||
'status varchar(12) NOT NULL DEFAULT "subscribed",',
|
||||
'status varchar(12) NOT NULL DEFAULT "' . Subscriber::STATUS_SUBSCRIBED . '",',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function subscriber_custom_field() {
|
||||
function subscriberCustomField() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'custom_field_id mediumint(9) NOT NULL,',
|
||||
'value varchar(255) NOT NULL DEFAULT "",',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
|
||||
);
|
||||
@ -172,19 +177,20 @@ class Migrator {
|
||||
'type varchar(20) NOT NULL DEFAULT "standard",',
|
||||
'sender_address varchar(150) NOT NULL DEFAULT "",',
|
||||
'sender_name varchar(150) NOT NULL DEFAULT "",',
|
||||
'status varchar(20) NOT NULL DEFAULT "'.Newsletter::STATUS_DRAFT.'",',
|
||||
'reply_to_address varchar(150) NOT NULL DEFAULT "",',
|
||||
'reply_to_name varchar(150) NOT NULL DEFAULT "",',
|
||||
'preheader varchar(250) NOT NULL DEFAULT "",',
|
||||
'body longtext,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'PRIMARY KEY (id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_templates() {
|
||||
function newsletterTemplates() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'name varchar(250) NOT NULL,',
|
||||
@ -193,53 +199,53 @@ class Migrator {
|
||||
'thumbnail LONGTEXT,',
|
||||
'readonly TINYINT(1) DEFAULT 0,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_option_fields() {
|
||||
function newsletterOptionFields() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'name varchar(90) NOT NULL,',
|
||||
'newsletter_type varchar(90) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY name_newsletter_type (newsletter_type,name)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_option() {
|
||||
function newsletterOption() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'option_field_id mediumint(9) NOT NULL,',
|
||||
'value varchar(255) NOT NULL DEFAULT "",',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_segment() {
|
||||
function newsletterSegment() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'segment_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_links() {
|
||||
function newsletterLinks() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
@ -247,18 +253,18 @@ class Migrator {
|
||||
'url varchar(255) NOT NULL,',
|
||||
'hash varchar(20) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function newsletter_posts() {
|
||||
function newsletterPosts() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'post_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
@ -273,14 +279,14 @@ class Migrator {
|
||||
'settings longtext,',
|
||||
'styles longtext,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'PRIMARY KEY (id)'
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function statistics_newsletters() {
|
||||
function statisticsNewsletters() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
@ -292,7 +298,7 @@ class Migrator {
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function statistics_clicks() {
|
||||
function statisticsClicks() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
@ -301,42 +307,42 @@ class Migrator {
|
||||
'link_id mediumint(9) NOT NULL,',
|
||||
'count mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function statistics_opens() {
|
||||
function statisticsOpens() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function statistics_unsubscribes() {
|
||||
function statisticsUnsubscribes() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
||||
function statistics_forms() {
|
||||
function statisticsForms() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'form_id mediumint(9) NOT NULL,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY form_subscriber (form_id,subscriber_id)'
|
||||
);
|
||||
@ -344,7 +350,7 @@ class Migrator {
|
||||
}
|
||||
|
||||
private function sqlify($model, $attributes) {
|
||||
$table = $this->prefix . $model;
|
||||
$table = $this->prefix . Helpers::camelCaseToUnderscore($model);
|
||||
|
||||
$sql = array();
|
||||
$sql[] = "CREATE TABLE " . $table . " (";
|
||||
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
class Permissions {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
add_action(
|
||||
'admin_init',
|
||||
array($this, 'setup')
|
||||
);
|
||||
}
|
||||
|
||||
function setup() {
|
||||
// administrative roles
|
||||
$roles = array('administrator', 'super_admin');
|
||||
|
||||
// mailpoet capabilities
|
||||
$capabilities = array(
|
||||
'mailpoet_newsletters',
|
||||
'mailpoet_newsletter_styles',
|
||||
'mailpoet_subscribers',
|
||||
'mailpoet_settings',
|
||||
'mailpoet_statistics'
|
||||
);
|
||||
|
||||
foreach($roles as $role_key){
|
||||
// get role based on role key
|
||||
$role = get_role($role_key);
|
||||
|
||||
// if the role doesn't exist, skip it
|
||||
if($role !== null) {
|
||||
// add capability
|
||||
foreach($capabilities as $capability) {
|
||||
if(!$role->has_cap($capability)) {
|
||||
$role->add_cap($capability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ use \MailPoet\Models\Segment;
|
||||
use \MailPoet\Segments\WP;
|
||||
use \MailPoet\Models\Setting;
|
||||
use \MailPoet\Settings\Pages;
|
||||
use \MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -26,25 +27,6 @@ class Populator {
|
||||
function up() {
|
||||
global $wpdb;
|
||||
|
||||
$_this = $this;
|
||||
|
||||
$populate = function($model) use($_this, $wpdb) {
|
||||
$fields = $_this->$model();
|
||||
$table = $_this->prefix . $model;
|
||||
|
||||
array_map(function($field) use ($wpdb, $table) {
|
||||
$column_conditions = array_map(function($key) use ($field) {
|
||||
return $key . '=' . $field[$key];
|
||||
}, $field);
|
||||
if($wpdb->get_var("SELECT COUNT(*) FROM " . $table . " WHERE " . implode(' AND ', $column_conditions)) === 0) {
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
$field
|
||||
);
|
||||
}
|
||||
}, $fields);
|
||||
};
|
||||
|
||||
array_map(array($this, 'populate'), $this->models);
|
||||
|
||||
$this->createDefaultSegments();
|
||||
@ -93,9 +75,12 @@ class Populator {
|
||||
'address' => $current_user->user_email
|
||||
);
|
||||
|
||||
if(!Setting::getValue('sender')) {
|
||||
// default from name & address
|
||||
Setting::setValue('sender', $sender);
|
||||
}
|
||||
|
||||
if(!Setting::getValue('signup_confirmation')) {
|
||||
// enable signup confirmation by default
|
||||
Setting::setValue('signup_confirmation', array(
|
||||
'enabled' => true,
|
||||
@ -106,22 +91,11 @@ class Populator {
|
||||
'reply_to' => $sender
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function createDefaultSegments() {
|
||||
// WP Users segment
|
||||
$wp_users_segment = Segment::getWPUsers();
|
||||
|
||||
if($wp_users_segment === false) {
|
||||
// create the wp users list
|
||||
$wp_users_segment = Segment::create();
|
||||
$wp_users_segment->hydrate(array(
|
||||
'name' => __('WordPress Users'),
|
||||
'description' =>
|
||||
__('The list containing all of your WordPress users.'),
|
||||
'type' => 'wp_users'
|
||||
));
|
||||
$wp_users_segment->save();
|
||||
}
|
||||
$wp_segment = Segment::getWPSegment();
|
||||
|
||||
// Synchronize WP Users
|
||||
WP::synchronizeUsers();
|
||||
@ -132,13 +106,13 @@ class Populator {
|
||||
$default_segment->hydrate(array(
|
||||
'name' => __('My First List'),
|
||||
'description' =>
|
||||
__('The list created automatically on install of MailPoet')
|
||||
__('The list is automatically created when you install MailPoet')
|
||||
));
|
||||
$default_segment->save();
|
||||
}
|
||||
}
|
||||
|
||||
function newsletter_option_fields() {
|
||||
private function newsletterOptionFields() {
|
||||
return array(
|
||||
array(
|
||||
'name' => 'isScheduled',
|
||||
@ -196,7 +170,7 @@ class Populator {
|
||||
);
|
||||
}
|
||||
|
||||
private function newsletter_templates() {
|
||||
private function newsletterTemplates() {
|
||||
return array(
|
||||
(new FranksRoastHouseTemplate(Env::$assets_url))->get(),
|
||||
(new BlankTemplate(Env::$assets_url))->get(),
|
||||
@ -206,7 +180,8 @@ class Populator {
|
||||
}
|
||||
|
||||
private function populate($model) {
|
||||
$rows = $this->$model();
|
||||
$modelMethod = Helpers::underscoreToCamelCase($model);
|
||||
$rows = $this->$modelMethod();
|
||||
$table = $this->prefix . $model;
|
||||
$_this = $this;
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,77 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Cron\Daemon;
|
||||
use MailPoet\Newsletter\Viewer\ViewInBrowser;
|
||||
use MailPoet\Statistics\Track\Clicks;
|
||||
use MailPoet\Statistics\Track\Opens;
|
||||
use MailPoet\Subscription;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class PublicAPI {
|
||||
public $api;
|
||||
public $endpoint;
|
||||
public $action;
|
||||
public $data;
|
||||
|
||||
function __construct() {
|
||||
# http://example.com/?mailpoet&endpoint=&action=&data=
|
||||
$this->api = isset($_GET['mailpoet']) ? true : false;
|
||||
$this->endpoint = isset($_GET['endpoint']) ?
|
||||
Helpers::underscoreToCamelCase($_GET['endpoint']) :
|
||||
false;
|
||||
$this->action = isset($_GET['action']) ?
|
||||
Helpers::underscoreToCamelCase($_GET['action']) :
|
||||
false;
|
||||
$this->data = isset($_GET['data']) ?
|
||||
unserialize(base64_decode($_GET['data'])) :
|
||||
false;
|
||||
}
|
||||
|
||||
function init() {
|
||||
if(!$this->api && !$this->endpoint) return;
|
||||
$this->_checkAndCallMethod($this, $this->endpoint, $terminate_request = true);
|
||||
}
|
||||
|
||||
function queue() {
|
||||
$queue = new Daemon($this->data);
|
||||
$this->_checkAndCallMethod($queue, $this->action);
|
||||
}
|
||||
|
||||
function subscription() {
|
||||
$subscription = new Subscription\Pages($this->action, $this->data);
|
||||
$this->_checkAndCallMethod($subscription, $this->action);
|
||||
}
|
||||
|
||||
function track() {
|
||||
if($this->action === 'click') {
|
||||
$track_class = new Clicks($this->data);
|
||||
}
|
||||
if($this->action === 'open') {
|
||||
$track_class = new Opens($this->data);
|
||||
}
|
||||
if(!isset($track_class)) return;
|
||||
$track_class->track();
|
||||
}
|
||||
|
||||
function viewInBrowser() {
|
||||
$viewer = new ViewInBrowser($this->data);
|
||||
$viewer->view();
|
||||
}
|
||||
|
||||
private function _checkAndCallMethod($class, $method, $terminate_request = false) {
|
||||
if(!method_exists($class, $method)) {
|
||||
if(!$terminate_request) return;
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
call_user_func(
|
||||
array(
|
||||
$class,
|
||||
$method
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ class Renderer {
|
||||
$file_system,
|
||||
array(
|
||||
'cache' => $this->detectCache(),
|
||||
'debug' => WP_DEBUG
|
||||
'debug' => WP_DEBUG,
|
||||
'auto_reload' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -26,11 +27,12 @@ class Renderer {
|
||||
$this->setupHandlebars();
|
||||
$this->setupGlobalVariables();
|
||||
$this->setupSyntax();
|
||||
|
||||
return $this->renderer;
|
||||
}
|
||||
|
||||
function setupTranslations() {
|
||||
$this->renderer->addExtension(new Twig\i18n(Env::$plugin_name));
|
||||
$this->renderer->addExtension(new Twig\I18n(Env::$plugin_name));
|
||||
}
|
||||
|
||||
function setupFunctions() {
|
||||
|
@ -114,7 +114,7 @@ class Shortcodes {
|
||||
|
||||
function renderArchiveSubject($newsletter) {
|
||||
return '<a href="TODO" target="_blank" title="'
|
||||
.esc_attr(__('Preview in new tab')).'">'
|
||||
.esc_attr(__('Preview in a new tab')).'">'
|
||||
.esc_attr($newsletter->subject).
|
||||
'</a>';
|
||||
}
|
||||
|
@ -14,20 +14,16 @@ class BootStrapMenu {
|
||||
return ($this->daemon) ?
|
||||
array_merge(
|
||||
array(
|
||||
'timeSinceStart' =>
|
||||
Carbon::createFromFormat(
|
||||
'timeSinceStart' => Carbon::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$this->daemon->created_at,
|
||||
'UTC'
|
||||
)
|
||||
->diffForHumans(),
|
||||
'timeSinceUpdate' =>
|
||||
Carbon::createFromFormat(
|
||||
)->diffForHumans(),
|
||||
'timeSinceUpdate' => Carbon::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$this->daemon->updated_at,
|
||||
'UTC'
|
||||
)
|
||||
->diffForHumans()
|
||||
)->diffForHumans()
|
||||
),
|
||||
json_decode($this->daemon->value, true)
|
||||
) :
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\API\API;
|
||||
use MailPoet\API\Endpoints\Queue as QueueAPI;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
@ -14,7 +16,6 @@ class CronHelper {
|
||||
static function createDaemon($token) {
|
||||
$daemon = array(
|
||||
'status' => Daemon::STATUS_STARTING,
|
||||
'counter' => 0,
|
||||
'token' => $token
|
||||
);
|
||||
self::saveDaemon($daemon);
|
||||
@ -38,17 +39,17 @@ class CronHelper {
|
||||
}
|
||||
|
||||
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
|
||||
$data = serialize(array('token' => $token));
|
||||
$url = '/?mailpoet&endpoint=queue&action=run&data=' .
|
||||
base64_encode($data);
|
||||
$data = array('token' => $token);
|
||||
$url = API::buildRequest(
|
||||
QueueAPI::ENDPOINT,
|
||||
QueueAPI::ACTION_RUN,
|
||||
$data
|
||||
);
|
||||
$args = array(
|
||||
'timeout' => $timeout,
|
||||
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
|
||||
);
|
||||
$result = wp_remote_get(
|
||||
self::getSiteUrl() . $url,
|
||||
$args
|
||||
);
|
||||
$result = wp_remote_get($url, $args);
|
||||
return wp_remote_retrieve_body($result);
|
||||
}
|
||||
|
||||
@ -72,7 +73,7 @@ class CronHelper {
|
||||
static function checkExecutionTimer($timer) {
|
||||
$elapsed_time = microtime(true) - $timer;
|
||||
if($elapsed_time >= self::DAEMON_EXECUTION_LIMIT) {
|
||||
throw new \Exception(__('Maximum execution time reached.'));
|
||||
throw new \Exception(__('Maximum execution time has been reached.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Cron\Workers\Scheduler;
|
||||
use MailPoet\Cron\Workers\SendingQueue;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
|
||||
@ -20,7 +20,7 @@ class Daemon {
|
||||
private $timer;
|
||||
|
||||
function __construct($data) {
|
||||
if(empty($data)) $this->abortWithError(__('Invalid or missing cron data.'));
|
||||
if(empty($data)) $this->abortWithError(__('Invalid or missing Cron data.'));
|
||||
ignore_user_abort();
|
||||
$this->daemon = CronHelper::getDaemon();
|
||||
$this->token = CronHelper::createToken();
|
||||
@ -30,7 +30,6 @@ class Daemon {
|
||||
|
||||
function run() {
|
||||
$daemon = $this->daemon;
|
||||
set_time_limit(0);
|
||||
if(!$daemon) {
|
||||
$this->abortWithError(__('Daemon does not exist.'));
|
||||
}
|
||||
@ -39,41 +38,43 @@ class Daemon {
|
||||
) {
|
||||
$this->abortWithError(__('Invalid or missing token.'));
|
||||
}
|
||||
$daemon['token'] = $this->token;
|
||||
CronHelper::saveDaemon($daemon);
|
||||
$this->abortIfStopped($daemon);
|
||||
try {
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->process($this->timer);
|
||||
$queue = new SendingQueue();
|
||||
$queue->process($this->timer);
|
||||
$scheduler = new SchedulerWorker($this->timer);
|
||||
$scheduler->process();
|
||||
$queue = new SendingQueueWorker($this->timer);
|
||||
$queue->process();
|
||||
} catch(\Exception $e) {
|
||||
// continue processing, no need to catch errors
|
||||
// continue processing, no need to handle errors
|
||||
}
|
||||
$elapsed_time = microtime(true) - $this->timer;
|
||||
if($elapsed_time < CronHelper::DAEMON_EXECUTION_LIMIT) {
|
||||
sleep(CronHelper::DAEMON_EXECUTION_LIMIT - $elapsed_time);
|
||||
}
|
||||
// after each execution, re-read daemon data in case it was deleted or
|
||||
// after each execution, re-read daemon data in case its status was changed
|
||||
// its status has changed
|
||||
$daemon = CronHelper::getDaemon();
|
||||
if(!$daemon || $daemon['token'] !== $this->data['token']) {
|
||||
exit;
|
||||
if(!$daemon || $daemon['token'] !== $this->token) {
|
||||
$this->terminateRequest();
|
||||
}
|
||||
$daemon['counter']++;
|
||||
$this->abortIfStopped($daemon);
|
||||
if($daemon['status'] === self::STATUS_STARTING) {
|
||||
$daemon['status'] = self::STATUS_STARTED;
|
||||
}
|
||||
$daemon['token'] = $this->token;
|
||||
CronHelper::saveDaemon($daemon);
|
||||
$this->callSelf();
|
||||
}
|
||||
|
||||
function abortIfStopped($daemon) {
|
||||
if($daemon['status'] === self::STATUS_STOPPED) exit;
|
||||
if($daemon['status'] === self::STATUS_STOPPED) {
|
||||
$this->terminateRequest();
|
||||
}
|
||||
if($daemon['status'] === self::STATUS_STOPPING) {
|
||||
$daemon['status'] = self::STATUS_STOPPED;
|
||||
CronHelper::saveDaemon($daemon);
|
||||
exit;
|
||||
$this->terminateRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,6 +84,10 @@ class Daemon {
|
||||
|
||||
function callSelf() {
|
||||
CronHelper::accessDaemon($this->token, self::REQUEST_TIMEOUT);
|
||||
$this->terminateRequest();
|
||||
}
|
||||
|
||||
function terminateRequest() {
|
||||
exit;
|
||||
}
|
||||
}
|
@ -26,24 +26,22 @@ class Supervisor {
|
||||
$daemon['status'] === Daemon::STATUS_STOPPED
|
||||
) {
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
|
||||
}
|
||||
$elapsed_time = time() - (int)$daemon['updated_at'];
|
||||
// if it's been less than 40 seconds since last execution and we're not
|
||||
// force-running the daemon, return its status and do nothing
|
||||
if($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT && !$this->force_run) {
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
}
|
||||
// if it's been less than 40 seconds since last execution, we are
|
||||
// force-running the daemon and it's either being started or stopped,
|
||||
// return its status and do nothing
|
||||
elseif($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT &&
|
||||
} elseif($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT &&
|
||||
$this->force_run &&
|
||||
in_array($daemon['status'], array(
|
||||
Daemon::STATUS_STOPPING,
|
||||
Daemon::STATUS_STARTING
|
||||
))
|
||||
) {
|
||||
// if it's been less than 40 seconds since last execution, we are
|
||||
// force-running the daemon and it's either being started or stopped,
|
||||
// return its status and do nothing
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
}
|
||||
// re-create (restart) daemon
|
||||
|
@ -150,7 +150,7 @@ class Scheduler {
|
||||
function verifyWPSubscriber($subscriber_id, $newsletter, $queue) {
|
||||
// check if user has the proper role
|
||||
$subscriber = Subscriber::findOne($subscriber_id);
|
||||
if(!$subscriber || $subscriber->wp_user_id === null) {
|
||||
if(!$subscriber || $subscriber->isWPUser() === false) {
|
||||
$queue->delete();
|
||||
return false;
|
||||
}
|
||||
@ -172,6 +172,5 @@ class Scheduler {
|
||||
$queue->scheduled_at = $next_run_date;
|
||||
$queue->save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
@ -1,398 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterPost;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\StatisticsNewsletters;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class SendingQueue {
|
||||
public $mta_config;
|
||||
public $mta_log;
|
||||
public $processing_method;
|
||||
private $timer;
|
||||
const BATCH_SIZE = 50;
|
||||
const DIVIDER = '***MailPoet***';
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->mta_config = $this->getMailerConfig();
|
||||
$this->mta_log = $this->getMailerLog();
|
||||
$this->processing_method = ($this->mta_config['method'] === 'MailPoet') ?
|
||||
'processBulkSubscribers' :
|
||||
'processIndividualSubscriber';
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
}
|
||||
|
||||
function process() {
|
||||
foreach($this->getQueues() as $queue) {
|
||||
$newsletter = Newsletter::findOne($queue->newsletter_id);
|
||||
if(!$newsletter) {
|
||||
$queue->delete();
|
||||
continue;
|
||||
}
|
||||
$newsletter = $newsletter->asArray();
|
||||
$newsletter['body'] = $this->getOrRenderNewsletterBody($queue, $newsletter);
|
||||
if($newsletter['type'] === 'notification' &&
|
||||
strpos($newsletter['body']['html'], 'data-post-id') === false
|
||||
){
|
||||
$queue->delete();
|
||||
continue;
|
||||
}
|
||||
$queue->subscribers = (object) unserialize($queue->subscribers);
|
||||
if(!isset($queue->subscribers->processed)) {
|
||||
$queue->subscribers->processed = array();
|
||||
}
|
||||
if(!isset($queue->subscribers->failed)) {
|
||||
$queue->subscribers->failed = array();
|
||||
}
|
||||
$mailer = $this->configureMailer($newsletter);
|
||||
foreach(array_chunk($queue->subscribers->to_process, self::BATCH_SIZE) as
|
||||
$subscribers_ids) {
|
||||
$subscribers = Subscriber::whereIn('id', $subscribers_ids)
|
||||
->findArray();
|
||||
if(count($subscribers_ids) !== count($subscribers)) {
|
||||
$queue->subscribers->to_process = $this->recalculateSubscriberCount(
|
||||
Helpers::arrayColumn($subscribers, 'id'),
|
||||
$subscribers_ids,
|
||||
$queue->subscribers->to_process
|
||||
);
|
||||
}
|
||||
if(!count($queue->subscribers->to_process)) {
|
||||
$this->updateQueue($queue);
|
||||
continue;
|
||||
}
|
||||
$queue->subscribers = call_user_func_array(
|
||||
array(
|
||||
$this,
|
||||
$this->processing_method
|
||||
),
|
||||
array(
|
||||
$mailer,
|
||||
$newsletter,
|
||||
$subscribers,
|
||||
$queue
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOrRenderNewsletterBody($queue, $newsletter) {
|
||||
// check if newsletter has been rendered, in which case return its contents
|
||||
// or render and save for future reuse
|
||||
if($queue->newsletter_rendered_body === null) {
|
||||
if((boolean) Setting::getValue('tracking.enabled')) {
|
||||
// insert tracking code
|
||||
add_filter('mailpoet_rendering_post_process', function($template) {
|
||||
return OpenTracking::process($template);
|
||||
});
|
||||
// render newsletter
|
||||
$rendered_newsletter = $this->renderNewsletter($newsletter);
|
||||
// process link shortcodes, extract and save links in the database
|
||||
$processed_newsletter = $this->processLinksAndShortcodes(
|
||||
$this->joinObject($rendered_newsletter),
|
||||
$newsletter['id'],
|
||||
$queue->id
|
||||
);
|
||||
list($newsletter['body']['html'], $newsletter['body']['text']) =
|
||||
$this->splitObject($processed_newsletter);
|
||||
}
|
||||
else {
|
||||
// render newsletter
|
||||
$newsletter['body'] = $this->renderNewsletter($newsletter);
|
||||
}
|
||||
$this->extractAndSaveNewsletterPosts(
|
||||
$newsletter['id'],
|
||||
$newsletter['body']['html']
|
||||
);
|
||||
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
|
||||
$queue->save();
|
||||
} else {
|
||||
$newsletter['body'] = json_decode($queue->newsletter_rendered_body);
|
||||
}
|
||||
return (array) $newsletter['body'];
|
||||
}
|
||||
|
||||
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
|
||||
foreach($subscribers as $subscriber) {
|
||||
$processed_newsletters[] =
|
||||
$this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
|
||||
if(!$queue->newsletter_rendered_subject) {
|
||||
$queue->newsletter_rendered_subject = $processed_newsletters[0]['subject'];
|
||||
}
|
||||
$transformed_subscribers[] =
|
||||
$mailer->transformSubscriber($subscriber);
|
||||
}
|
||||
$result = $this->sendNewsletter(
|
||||
$mailer,
|
||||
$processed_newsletters,
|
||||
$transformed_subscribers
|
||||
);
|
||||
$subscribers_ids = Helpers::arrayColumn($subscribers, 'id');
|
||||
if(!$result) {
|
||||
$queue->subscribers->failed = array_merge(
|
||||
$queue->subscribers->failed,
|
||||
$subscribers_ids
|
||||
);
|
||||
} else {
|
||||
$newsletter_statistics =
|
||||
array_map(function($data) use ($newsletter, $subscribers_ids, $queue) {
|
||||
return array(
|
||||
$newsletter['id'],
|
||||
$subscribers_ids[$data],
|
||||
$queue->id
|
||||
);
|
||||
}, range(0, count($transformed_subscribers) - 1));
|
||||
$newsletter_statistics = Helpers::flattenArray($newsletter_statistics);
|
||||
$this->updateMailerLog();
|
||||
$this->updateNewsletterStatistics($newsletter_statistics);
|
||||
$queue->subscribers->processed = array_merge(
|
||||
$queue->subscribers->processed,
|
||||
$subscribers_ids
|
||||
);
|
||||
}
|
||||
$this->updateQueue($queue);
|
||||
$this->checkSendingLimit();
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
return $queue->subscribers;
|
||||
}
|
||||
|
||||
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
|
||||
foreach($subscribers as $subscriber) {
|
||||
$this->checkSendingLimit();
|
||||
$processed_newsletter = $this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
|
||||
if(!$queue->newsletter_rendered_subject) {
|
||||
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
|
||||
}
|
||||
$transformed_subscriber = $mailer->transformSubscriber($subscriber);
|
||||
$result = $this->sendNewsletter(
|
||||
$mailer,
|
||||
$processed_newsletter,
|
||||
$transformed_subscriber
|
||||
);
|
||||
if(!$result) {
|
||||
$queue->subscribers->failed[] = $subscriber['id'];
|
||||
} else {
|
||||
$queue->subscribers->processed[] = $subscriber['id'];
|
||||
$newsletter_statistics = array(
|
||||
$newsletter['id'],
|
||||
$subscriber['id'],
|
||||
$queue->id
|
||||
);
|
||||
$this->updateMailerLog();
|
||||
$this->updateNewsletterStatistics($newsletter_statistics);
|
||||
}
|
||||
$this->updateQueue($queue);
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
}
|
||||
return $queue->subscribers;
|
||||
}
|
||||
|
||||
function updateNewsletterStatistics($data) {
|
||||
return StatisticsNewsletters::createMultiple($data);
|
||||
}
|
||||
|
||||
function renderNewsletter($newsletter) {
|
||||
$renderer = new Renderer($newsletter);
|
||||
return $renderer->render();
|
||||
}
|
||||
|
||||
function processLinksAndShortcodes($content, $newsletter_id, $queue_id) {
|
||||
// process only link shortcodes
|
||||
$shortcodes = new Shortcodes($newsletter = false, $subscriber = false, $queue_id);
|
||||
$content = $shortcodes->replace(
|
||||
$content,
|
||||
$categories = array('link')
|
||||
);
|
||||
// extract and save links and link shortcodes
|
||||
list($content, $processed_links) =
|
||||
Links::process(
|
||||
$content,
|
||||
$links = false,
|
||||
$process_link_shortcodes = true,
|
||||
$queue_id
|
||||
);
|
||||
Links::save($processed_links, $newsletter_id, $queue_id);
|
||||
return $content;
|
||||
}
|
||||
|
||||
function processNewsletterBeforeSending($newsletter, $subscriber = false, $queue) {
|
||||
$data_for_shortcodes = array(
|
||||
$newsletter['subject'],
|
||||
$newsletter['body']['html'],
|
||||
$newsletter['body']['text']
|
||||
);
|
||||
$processed_newsletter = $this->replaceShortcodes(
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue,
|
||||
$this->joinObject($data_for_shortcodes)
|
||||
);
|
||||
if((boolean) Setting::getValue('tracking.enabled')) {
|
||||
$processed_newsletter = Links::replaceSubscriberData(
|
||||
$newsletter['id'],
|
||||
$subscriber['id'],
|
||||
$queue->id,
|
||||
$processed_newsletter
|
||||
);
|
||||
}
|
||||
list($newsletter['subject'],
|
||||
$newsletter['body']['html'],
|
||||
$newsletter['body']['text']
|
||||
) = $this->splitObject($processed_newsletter);
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
function replaceShortcodes($newsletter, $subscriber, $queue, $body) {
|
||||
$shortcodes = new Shortcodes(
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
);
|
||||
return $shortcodes->replace($body);
|
||||
}
|
||||
|
||||
function sendNewsletter($mailer, $newsletter, $subscriber) {
|
||||
return $mailer->mailer_instance->send(
|
||||
$newsletter,
|
||||
$subscriber
|
||||
);
|
||||
}
|
||||
|
||||
function configureMailer($newsletter) {
|
||||
$sender['address'] = (!empty($newsletter['sender_address'])) ?
|
||||
$newsletter['sender_address'] :
|
||||
false;
|
||||
$sender['name'] = (!empty($newsletter['sender_name'])) ?
|
||||
$newsletter['sender_name'] :
|
||||
false;
|
||||
$reply_to['address'] = (!empty($newsletter['reply_to_address'])) ?
|
||||
$newsletter['reply_to_address'] :
|
||||
false;
|
||||
$reply_to['name'] = (!empty($newsletter['reply_to_name'])) ?
|
||||
$newsletter['reply_to_name'] :
|
||||
false;
|
||||
if(!$sender['address']) {
|
||||
$sender = false;
|
||||
}
|
||||
if(!$reply_to['address']) {
|
||||
$reply_to = false;
|
||||
}
|
||||
$mailer = new Mailer($method = false, $sender, $reply_to);
|
||||
return $mailer;
|
||||
}
|
||||
|
||||
function getQueues() {
|
||||
return \MailPoet\Models\SendingQueue::orderByDesc('priority')
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->findResultSet();
|
||||
}
|
||||
|
||||
function updateQueue($queue) {
|
||||
$queue = clone($queue);
|
||||
$queue->subscribers->to_process = array_diff(
|
||||
$queue->subscribers->to_process,
|
||||
array_merge(
|
||||
$queue->subscribers->processed,
|
||||
$queue->subscribers->failed
|
||||
)
|
||||
);
|
||||
$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);
|
||||
$queue->count_failed = count($queue->subscribers->failed);
|
||||
$queue->count_total =
|
||||
$queue->count_processed + $queue->count_to_process;
|
||||
if(!$queue->count_to_process) {
|
||||
$queue->processed_at = current_time('mysql');
|
||||
$queue->status = self::STATUS_COMPLETED;
|
||||
}
|
||||
$queue->subscribers = serialize((array) $queue->subscribers);
|
||||
$queue->save();
|
||||
}
|
||||
|
||||
function updateMailerLog() {
|
||||
$this->mta_log['sent']++;
|
||||
return Setting::setValue('mta_log', $this->mta_log);
|
||||
}
|
||||
|
||||
function getMailerConfig() {
|
||||
$mta_config = Setting::getValue('mta');
|
||||
if(!$mta_config) {
|
||||
throw new \Exception(__('Mailer is not configured.'));
|
||||
}
|
||||
return $mta_config;
|
||||
}
|
||||
|
||||
function getMailerLog() {
|
||||
$mta_log = Setting::getValue('mta_log');
|
||||
if(!$mta_log) {
|
||||
$mta_log = array(
|
||||
'sent' => 0,
|
||||
'started' => time()
|
||||
);
|
||||
Setting::setValue('mta_log', $mta_log);
|
||||
}
|
||||
return $mta_log;
|
||||
}
|
||||
|
||||
function checkSendingLimit() {
|
||||
$frequency_interval = (int)$this->mta_config['frequency']['interval'] * 60;
|
||||
$frequency_limit = (int)$this->mta_config['frequency']['emails'];
|
||||
$elapsed_time = time() - (int)$this->mta_log['started'];
|
||||
if($this->mta_log['sent'] === $frequency_limit &&
|
||||
$elapsed_time <= $frequency_interval
|
||||
) {
|
||||
throw new \Exception(__('Sending frequency limit reached.'));
|
||||
}
|
||||
if($elapsed_time > $frequency_interval) {
|
||||
$this->mta_log = array(
|
||||
'sent' => 0,
|
||||
'started' => time()
|
||||
);
|
||||
Setting::setValue('mta_log', $this->mta_log);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function recalculateSubscriberCount(
|
||||
$found_subscriber, $existing_subscribers, $subscribers_to_process) {
|
||||
$subscibers_to_exclude = array_diff($existing_subscribers, $found_subscriber);
|
||||
return array_diff($subscribers_to_process, $subscibers_to_exclude);
|
||||
}
|
||||
|
||||
function extractAndSaveNewsletterPosts($newletter_id, $content) {
|
||||
preg_match_all('/data-post-id="(\d+)"/ism', $content, $posts);
|
||||
$posts = $posts[1];
|
||||
foreach($posts as $post) {
|
||||
$newletter_post = NewsletterPost::create();
|
||||
$newletter_post->newsletter_id = $newletter_id;
|
||||
$newletter_post->post_id = $post;
|
||||
$newletter_post->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function joinObject($object = array()) {
|
||||
return implode(self::DIVIDER, $object);
|
||||
}
|
||||
|
||||
private function splitObject($object = array()) {
|
||||
return explode(self::DIVIDER, $object);
|
||||
}
|
||||
}
|
190
lib/Cron/Workers/SendingQueue/SendingQueue.php
Normal file
190
lib/Cron/Workers/SendingQueue/SendingQueue.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\SendingQueue;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Subscribers as SubscribersTask;
|
||||
use MailPoet\Models\Newsletter as NewsletterModel;
|
||||
use MailPoet\Models\SendingQueue as SendingQueueModel;
|
||||
use MailPoet\Models\StatisticsNewsletters as StatisticsNewslettersModel;
|
||||
use MailPoet\Models\Subscriber as SubscriberModel;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class SendingQueue {
|
||||
public $mailer_task;
|
||||
public $newsletter_task;
|
||||
private $timer;
|
||||
const BATCH_SIZE = 50;
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->mailer_task = new MailerTask();
|
||||
$this->newsletter_task = new NewsletterTask();
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
}
|
||||
|
||||
function process() {
|
||||
$this->mailer_task->checkSendingLimit();
|
||||
foreach($this->getQueues() as $queue) {
|
||||
// get and pre-process newsletter (render, replace shortcodes/links, etc.)
|
||||
$newsletter = $this->newsletter_task->getAndPreProcess($queue->asArray());
|
||||
if(!$newsletter) {
|
||||
$queue->delete();
|
||||
continue;
|
||||
}
|
||||
// configure mailer
|
||||
$this->mailer_task->configureMailer($newsletter);
|
||||
if(is_null($queue->newsletter_rendered_body)) {
|
||||
$queue->newsletter_rendered_body = json_encode($newsletter['rendered_body']);
|
||||
$queue->save();
|
||||
}
|
||||
// get subscribers
|
||||
$queue->subscribers = $queue->getSubscribers();
|
||||
$subscriber_batches = array_chunk(
|
||||
$queue->subscribers['to_process'],
|
||||
self::BATCH_SIZE
|
||||
);
|
||||
foreach($subscriber_batches as $subscribers_to_process_ids) {
|
||||
$found_subscribers = SubscriberModel::whereIn('id', $subscribers_to_process_ids)
|
||||
->findArray();
|
||||
$found_subscribers_ids = Helpers::arrayColumn($found_subscribers, 'id');
|
||||
// if some subscribers weren't found, remove them from the processing list
|
||||
if(count($found_subscribers_ids) !== count($subscribers_to_process_ids)) {
|
||||
$queue->subscribers = SubscribersTask::updateToProcessList(
|
||||
$found_subscribers_ids,
|
||||
$subscribers_to_process_ids,
|
||||
$queue->subscribers
|
||||
);
|
||||
}
|
||||
if(!count($queue->subscribers['to_process'])) {
|
||||
$this->updateQueue($queue);
|
||||
continue;
|
||||
}
|
||||
$queue = $this->processQueue(
|
||||
$queue,
|
||||
$newsletter,
|
||||
$found_subscribers
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processQueue($queue, $newsletter, $subscribers) {
|
||||
// determine if processing is done in bulk or individually
|
||||
$processing_method = $this->mailer_task->getProcessingMethod();
|
||||
$prepared_newsletters = array();
|
||||
$prepared_subscribers = array();
|
||||
$prepared_subscribers_ids = array();
|
||||
$statistics = array();
|
||||
foreach($subscribers as $subscriber) {
|
||||
// render shortcodes and replace subscriber data in tracked links
|
||||
$prepared_newsletters[] =
|
||||
$this->newsletter_task->prepareNewsletterForSending(
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue->asArray()
|
||||
);
|
||||
if(!$queue->newsletter_rendered_subject) {
|
||||
$queue->newsletter_rendered_subject = $prepared_newsletters[0]['subject'];
|
||||
}
|
||||
// format subscriber name/address according to mailer settings
|
||||
$prepared_subscribers[] = $this->mailer_task->prepareSubscriberForSending(
|
||||
$subscriber
|
||||
);
|
||||
$prepared_subscribers_ids[] = $subscriber['id'];
|
||||
// keep track of values for statistics purposes
|
||||
$statistics[] = array(
|
||||
'newsletter_id' => $newsletter['id'],
|
||||
'subscriber_id' => $subscriber['id'],
|
||||
'queue_id' => $queue->id
|
||||
);
|
||||
if($processing_method === 'individual') {
|
||||
$queue = $this->sendNewsletters(
|
||||
$queue,
|
||||
$prepared_subscribers_ids,
|
||||
$prepared_newsletters[0],
|
||||
$prepared_subscribers[0],
|
||||
$statistics
|
||||
);
|
||||
$prepared_newsletters = array();
|
||||
$prepared_subscribers = array();
|
||||
$prepared_subscribers_ids = array();
|
||||
$statistics = array();
|
||||
}
|
||||
}
|
||||
if($processing_method === 'bulk') {
|
||||
$queue = $this->sendNewsletters(
|
||||
$queue,
|
||||
$prepared_subscribers_ids,
|
||||
$prepared_newsletters,
|
||||
$prepared_subscribers,
|
||||
$statistics
|
||||
);
|
||||
}
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function sendNewsletters(
|
||||
$queue, $prepared_subscribers_ids, $prepared_newsletters,
|
||||
$prepared_subscribers, $statistics
|
||||
) {
|
||||
// send newsletter
|
||||
$send_result = $this->mailer_task->send(
|
||||
$prepared_newsletters,
|
||||
$prepared_subscribers
|
||||
);
|
||||
if(!$send_result) {
|
||||
// update failed/to process list
|
||||
$queue->subscribers = SubscribersTask::updateFailedList(
|
||||
$prepared_subscribers_ids,
|
||||
$queue->subscribers
|
||||
);
|
||||
} else {
|
||||
// update processed/to process list
|
||||
$queue->subscribers = SubscribersTask::updateProcessedList(
|
||||
$prepared_subscribers_ids,
|
||||
$queue->subscribers
|
||||
);
|
||||
// log statistics
|
||||
StatisticsNewslettersModel::createMultiple($statistics);
|
||||
// keep track of sent items
|
||||
$this->mailer_task->updateMailerLog();
|
||||
$subscribers_to_process_count = count($queue->subscribers['to_process']);
|
||||
}
|
||||
$queue = $this->updateQueue($queue);
|
||||
if($subscribers_to_process_count) {
|
||||
$this->mailer_task->checkSendingLimit();
|
||||
}
|
||||
CronHelper::checkExecutionTimer($this->timer);
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function getQueues() {
|
||||
return SendingQueueModel::orderByDesc('priority')
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->findMany();
|
||||
}
|
||||
|
||||
function updateQueue($queue) {
|
||||
$queue->count_processed =
|
||||
count($queue->subscribers['processed']) + count($queue->subscribers['failed']);
|
||||
$queue->count_to_process = count($queue->subscribers['to_process']);
|
||||
$queue->count_failed = count($queue->subscribers['failed']);
|
||||
$queue->count_total =
|
||||
$queue->count_processed + $queue->count_to_process;
|
||||
if(!$queue->count_to_process) {
|
||||
$queue->processed_at = current_time('mysql');
|
||||
$queue->status = SendingQueueModel::STATUS_COMPLETED;
|
||||
// set newsletter status to sent
|
||||
$newsletter = NewsletterModel::findOne($queue->newsletter_id);
|
||||
// if it's a standard newsletter, update its status
|
||||
if($newsletter->type === NewsletterModel::TYPE_STANDARD) {
|
||||
$newsletter->setStatus(NewsletterModel::STATUS_SENT);
|
||||
}
|
||||
}
|
||||
return $queue->save();
|
||||
}
|
||||
}
|
33
lib/Cron/Workers/SendingQueue/Tasks/Links.php
Normal file
33
lib/Cron/Workers/SendingQueue/Tasks/Links.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
||||
|
||||
use MailPoet\Newsletter\Links\Links as NewsletterLinks;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Links {
|
||||
static function process(array $newsletter, array $queue) {
|
||||
list($newsletter, $links) = self::hashAndReplaceLinks($newsletter, $queue);
|
||||
self::saveLinks($links, $newsletter, $queue);
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
static function hashAndReplaceLinks(array $newsletter, array $queue) {
|
||||
// join HTML and TEXT rendered body into a text string
|
||||
$content = Helpers::joinObject($newsletter['rendered_body']);
|
||||
list($content, $links) = NewsletterLinks::process($content);
|
||||
// split the processed body with hashed links back to HTML and TEXT
|
||||
list($newsletter['rendered_body']['html'], $newsletter['rendered_body']['text'])
|
||||
= Helpers::splitObject($content);
|
||||
return array(
|
||||
$newsletter,
|
||||
$links
|
||||
);
|
||||
}
|
||||
|
||||
static function saveLinks($links, $newsletter, $queue) {
|
||||
return NewsletterLinks::save($links, $newsletter['id'], $queue['id']);
|
||||
}
|
||||
}
|
||||
|
103
lib/Cron/Workers/SendingQueue/Tasks/Mailer.php
Normal file
103
lib/Cron/Workers/SendingQueue/Tasks/Mailer.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
||||
|
||||
use MailPoet\Mailer\Mailer as MailerFactory;
|
||||
use MailPoet\Models\Setting;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Mailer {
|
||||
public $mta_config;
|
||||
public $mta_log;
|
||||
public $mailer;
|
||||
|
||||
function __construct() {
|
||||
$this->mta_config = $this->getMailerConfig();
|
||||
$this->mta_log = $this->getMailerLog();
|
||||
$this->mailer = $this->configureMailer();
|
||||
}
|
||||
|
||||
function configureMailer(array $newsletter = null) {
|
||||
$sender['address'] = (!empty($newsletter['sender_address'])) ?
|
||||
$newsletter['sender_address'] :
|
||||
false;
|
||||
$sender['name'] = (!empty($newsletter['sender_name'])) ?
|
||||
$newsletter['sender_name'] :
|
||||
false;
|
||||
$reply_to['address'] = (!empty($newsletter['reply_to_address'])) ?
|
||||
$newsletter['reply_to_address'] :
|
||||
false;
|
||||
$reply_to['name'] = (!empty($newsletter['reply_to_name'])) ?
|
||||
$newsletter['reply_to_name'] :
|
||||
false;
|
||||
if(!$sender['address']) {
|
||||
$sender = false;
|
||||
}
|
||||
if(!$reply_to['address']) {
|
||||
$reply_to = false;
|
||||
}
|
||||
$this->mailer = new MailerFactory($method = false, $sender, $reply_to);
|
||||
return $this->mailer;
|
||||
}
|
||||
|
||||
function getMailerConfig() {
|
||||
$mta_config = Setting::getValue('mta');
|
||||
if(!$mta_config) {
|
||||
throw new \Exception(__('Mailer is not configured.'));
|
||||
}
|
||||
return $mta_config;
|
||||
}
|
||||
|
||||
function getMailerLog() {
|
||||
$mta_log = Setting::getValue('mta_log');
|
||||
if(!$mta_log) {
|
||||
$mta_log = array(
|
||||
'sent' => 0,
|
||||
'started' => time()
|
||||
);
|
||||
Setting::setValue('mta_log', $mta_log);
|
||||
}
|
||||
return $mta_log;
|
||||
}
|
||||
|
||||
function updateMailerLog() {
|
||||
$this->mta_log['sent']++;
|
||||
Setting::setValue('mta_log', $this->mta_log);
|
||||
}
|
||||
|
||||
function getProcessingMethod() {
|
||||
return ($this->mta_config['method'] === 'MailPoet') ?
|
||||
'bulk' :
|
||||
'individual';
|
||||
}
|
||||
|
||||
function prepareSubscriberForSending(array $subscriber) {
|
||||
return $this->mailer->transformSubscriber($subscriber);
|
||||
}
|
||||
|
||||
function send($prepared_newsletters, $prepared_subscribers) {
|
||||
return $this->mailer->mailer_instance->send(
|
||||
$prepared_newsletters,
|
||||
$prepared_subscribers
|
||||
);
|
||||
}
|
||||
|
||||
function checkSendingLimit() {
|
||||
if($this->mta_config['method'] === 'MailPoet') return;
|
||||
$frequency_interval = (int)$this->mta_config['frequency']['interval'] * 60;
|
||||
$frequency_limit = (int)$this->mta_config['frequency']['emails'];
|
||||
$elapsed_time = time() - (int)$this->mta_log['started'];
|
||||
if($this->mta_log['sent'] === $frequency_limit &&
|
||||
$elapsed_time <= $frequency_interval
|
||||
) {
|
||||
throw new \Exception(__('Sending frequency limit has been reached.'));
|
||||
}
|
||||
if($elapsed_time > $frequency_interval) {
|
||||
$this->mta_log = array(
|
||||
'sent' => 0,
|
||||
'started' => time()
|
||||
);
|
||||
Setting::setValue('mta_log', $this->mta_log);
|
||||
}
|
||||
}
|
||||
}
|
106
lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php
Normal file
106
lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
||||
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Links as LinksTask;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Posts as PostsTask;
|
||||
use MailPoet\Cron\Workers\SendingQueue\Tasks\Shortcodes as ShortcodesTask;
|
||||
use MailPoet\Models\Newsletter as NewsletterModel;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Newsletter\Links\Links as NewsletterLinks;
|
||||
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Newsletter {
|
||||
public $tracking_enabled;
|
||||
public $tracking_image_inserted;
|
||||
|
||||
function __construct() {
|
||||
$this->tracking_enabled = (boolean)Setting::getValue('tracking.enabled');
|
||||
$this->tracking_image_inserted = false;
|
||||
}
|
||||
|
||||
function get($newsletter_id) {
|
||||
$newsletter = NewsletterModel::findOne($newsletter_id);
|
||||
return ($newsletter) ? $newsletter->asArray() : false;
|
||||
}
|
||||
|
||||
function getAndPreProcess(array $queue) {
|
||||
$newsletter = $this->get($queue['newsletter_id']);
|
||||
if(!$newsletter) {
|
||||
return false;
|
||||
}
|
||||
// if the newsletter was previously rendered, return it
|
||||
// otherwise, process/render it
|
||||
if(!is_null($queue['newsletter_rendered_body'])) {
|
||||
$newsletter['rendered_body'] = json_decode($queue['newsletter_rendered_body'], true);
|
||||
return $newsletter;
|
||||
}
|
||||
// if tracking is enabled, do additional processing
|
||||
if($this->tracking_enabled) {
|
||||
// hook once to the newsletter post-processing filter and add tracking image
|
||||
if(!$this->tracking_image_inserted) {
|
||||
$this->tracking_image_inserted = OpenTracking::addTrackingImage();
|
||||
}
|
||||
// render newsletter
|
||||
$newsletter = $this->render($newsletter);
|
||||
// hash and save all links
|
||||
$newsletter = LinksTask::process($newsletter, $queue);
|
||||
} else {
|
||||
// render newsletter
|
||||
$newsletter = $this->render($newsletter);
|
||||
}
|
||||
// check if this is a post notification and if it contains posts
|
||||
$newsletter_contains_posts = strpos($newsletter['rendered_body']['html'], 'data-post-id');
|
||||
if($newsletter['type'] === 'notification' && !$newsletter_contains_posts) {
|
||||
return false;
|
||||
}
|
||||
// save all posts
|
||||
$newsletter = PostsTask::extractAndSave($newsletter);
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
function render($newsletter) {
|
||||
$renderer = new Renderer($newsletter);
|
||||
$newsletter['rendered_body'] = $renderer->render();
|
||||
return $newsletter;
|
||||
}
|
||||
|
||||
function prepareNewsletterForSending(
|
||||
array $newsletter, array $subscriber, array $queue
|
||||
) {
|
||||
// shortcodes and links will be replaced in the subject, html and text body
|
||||
// to speed the processing, join content into a continuous string
|
||||
$prepared_newsletter = Helpers::joinObject(
|
||||
array(
|
||||
$newsletter['subject'],
|
||||
$newsletter['rendered_body']['html'],
|
||||
$newsletter['rendered_body']['text']
|
||||
)
|
||||
);
|
||||
$prepared_newsletter = ShortcodesTask::process(
|
||||
$prepared_newsletter,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
);
|
||||
if($this->tracking_enabled) {
|
||||
$prepared_newsletter = NewsletterLinks::replaceSubscriberData(
|
||||
$newsletter['id'],
|
||||
$subscriber['id'],
|
||||
$queue['id'],
|
||||
$prepared_newsletter
|
||||
);
|
||||
}
|
||||
$prepared_newsletter = Helpers::splitObject($prepared_newsletter);
|
||||
return array(
|
||||
'subject' => $prepared_newsletter[0],
|
||||
'body' => array(
|
||||
'html' => $prepared_newsletter[1],
|
||||
'text' => $prepared_newsletter[2]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
28
lib/Cron/Workers/SendingQueue/Tasks/Posts.php
Normal file
28
lib/Cron/Workers/SendingQueue/Tasks/Posts.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\SendingQueue\Tasks;
|
||||
|
||||
use MailPoet\Models\NewsletterPost;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Posts {
|
||||
static function extractAndSave(array $newsletter) {
|
||||
if(empty($newsletter['rendered_body']['html']) || empty($newsletter['id'])) {
|
||||
return;
|
||||
}
|
||||
preg_match_all(
|
||||
'/data-post-id="(\d+)"/ism',
|
||||
$newsletter['rendered_body']['html'],
|
||||
$matched_posts_ids);
|
||||
$matched_posts_ids = $matched_posts_ids[1];
|
||||
if(!count($matched_posts_ids)) {
|
||||
return $newsletter;
|
||||
}
|
||||
foreach($matched_posts_ids as $post_id) {
|
||||
$newletter_post = NewsletterPost::create();
|
||||
$newletter_post->newsletter_id = $newsletter['id'];
|
||||
$newletter_post->post_id = $post_id;
|
||||
$newletter_post->save();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user