Compare commits

...

127 Commits

Author SHA1 Message Date
71d8fb0d93 Bump up version to 0.0.20 2016-03-18 19:40:11 +02:00
2f293da7a3 Merge pull request #391 from mailpoet/scheduled_sending
Scheduled sending
2016-03-18 18:49:47 +02:00
17b56f0160 - Fixes scheduling issues
- Fixes unit test
- Updates as per code review comments
2016-03-18 12:11:38 -04:00
bb9fce7f82 - Implements post notification scheduling 2016-03-18 11:15:31 -04:00
ad4f1f8326 Merge pull request #390 from mailpoet/newsletter_footer_links
Newsletter footer links
2016-03-18 16:10:17 +02:00
8ece62c9a6 fix tests 2016-03-18 14:58:33 +01:00
a9b9e9c631 Updated tests in order to fix WP related issues 2016-03-18 14:30:59 +01:00
6e289b6a8f - Implements welcome e-mail scheduling 2016-03-17 11:22:29 -04:00
20ced8b099 replace mailpoet_title 'hack' by checking the post type 2016-03-17 15:48:41 +01:00
f38b632707 properly handle custom subscriptions pages 2016-03-17 15:48:41 +01:00
8ce0595342 turned static values into constants 2016-03-17 15:48:06 +01:00
f11de2f1ad Updated shortcodes for unsubscribe/manage/browser links
- fixed all issues in #387 except the custom mailpoet pages
2016-03-17 15:48:06 +01:00
e28451d410 removed edit page in settings + manage subscription update 2016-03-17 15:45:05 +01:00
72882aaf2b fixed shortcodes replacement for "global:" in newsletters
- extracted "get subscription pages urls" from models\Subscriber
- added unit tests for subscription\url class (not working because of WP/Codeception issue)
2016-03-17 15:45:05 +01:00
fdf9dd0fa3 Merge pull request #386 from mailpoet/editor_refactor
Editor refactor
2016-03-17 15:19:04 +01:00
97dd0abea2 fixed makepot task
- added proper grunt module (grunt-cli)
- updated translations extract code to remove warnings when no translations are found
2016-03-17 15:08:38 +01:00
a099174226 Revert "Extract text labels from React code for translation in twig views"
This reverts commit 9aef6850c2.

Conflicts:
	views/newsletters.html
2016-03-17 15:24:07 +02:00
a054acc6e6 Merge pull request #389 from mailpoet/import_xss_update
Updates import to santize user input
2016-03-17 12:02:58 +02:00
74254d7e2a - Updates import to santize user input 2016-03-15 13:06:21 -04:00
1795964c69 - Updates composer dependencies 2016-03-15 12:10:09 -04:00
0118b2472a Fix loading of makepot task for pot translation file generation 2016-03-10 19:24:35 +02:00
5fe03f0dee Add event selection explanation to welcome email type 2016-03-10 17:49:06 +02:00
9aef6850c2 Extract text labels from React code for translation in twig views 2016-03-10 17:08:40 +02:00
f688a69f8b Fix translations to be injected at config time 2016-03-07 17:52:14 +02:00
b2682fa0b7 Remove console.log statements from newsletter editor 2016-03-07 16:57:20 +02:00
18b15c5440 Restructure JS loading to not duplicate JS asset loading 2016-03-07 16:15:26 +02:00
c3416977bb Replace editor translations with Twig localize function 2016-03-07 15:42:12 +02:00
88a00bc38c Rename editor form.html to editor.html 2016-03-07 14:42:12 +02:00
a1441dfde6 Bump up version to 0.0.19 2016-03-04 18:43:22 +02:00
8e7336d352 Merge pull request #373 from mailpoet/editor_rendering
Editor rendering (Part 2)
2016-03-04 11:11:42 -05:00
e0e2933cdf Merge pull request #379 from mailpoet/scheduled_sending
Fix mistyped method name
2016-03-04 11:06:40 -05:00
c93ec629ea Fix mistyped method name 2016-03-04 18:03:02 +02:00
d613df6558 Merge pull request #378 from mailpoet/scheduled_sending
Prepares codebase for future scheduled sending
2016-03-04 17:58:59 +02:00
01c9096543 - Rewrites hooks to not use closures 2016-03-04 10:47:55 -05:00
f120e839dd Merge pull request #375 from mailpoet/export_large_dataset_fix
Export update
2016-03-04 14:12:20 +02:00
f0ab592c04 Merge pull request #376 from mailpoet/rendering_serverside_update
Removes padding from the last column element
2016-03-04 13:30:31 +02:00
1a269d28b3 Merge pull request #374 from mailpoet/edit_subscription
edit subscription form + save
2016-03-04 12:54:56 +02:00
8d4a666bf0 added logged out handling of subscriber save request 2016-03-04 11:52:06 +01:00
4a96e483a6 edit profile rendering (missing segments list) + fallback for Url::redirectBack() 2016-03-04 11:20:17 +01:00
263a66407f - Removes mailpoet_padded class from the last element in a column 2016-03-03 19:13:50 -05:00
c4b728f4e1 - Updates Daemon to execute workers via WP's action hook
- Bootstraps Scheduler class
2016-03-03 15:42:10 -05:00
9970ad7fb6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-03 15:33:56 -05:00
eb380499d9 - Rewrites export to support large datasets
- Updates unit tests
- Fixes an issue with export UI not displaying subscribers without segment
2016-03-03 14:33:10 -05:00
4b528549f5 edit subscription form + save 2016-03-03 15:57:42 +01:00
a903017bc2 Update "Please include unsubscribe link" message 2016-03-03 15:48:59 +02:00
afd25e9174 Remove old and unused editor template 2016-03-03 15:39:46 +02:00
b1bbf1b3bc Add default email for newsletter preview, disallow empty email 2016-03-03 12:59:49 +02:00
9e84e8df93 Show loading icon when loading newsletter preview 2016-03-02 16:57:04 +02:00
df775b5a07 Set font-style for paragraph and headings 2016-03-02 16:57:04 +02:00
c9c22b9b52 Switch 'Comic Sans' to 'Comic Sans MS' 2016-03-02 16:57:04 +02:00
93d20688ea Prevent heading line height and font weight from being overridden 2016-03-02 16:57:04 +02:00
9ebcddfa2a Update button defaults 2016-03-02 16:57:04 +02:00
85995bc8a6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-02 16:42:12 +02:00
a8f2959bc6 added Subscriber::generateToken() 2016-03-02 15:07:37 +01:00
36242bd580 Fixed sending confirmation email to new subscribers (first time)
- fixed newsletters listing issue with queue
2016-03-02 13:11:06 +01:00
1e7dbc8449 refactor pages 2016-03-01 18:27:31 +01:00
12c159c627 bugfix + refactoring 2016-03-01 16:23:15 +01:00
82ed7e51c5 Replaced "contains" by "indexOf" (chrome issue)
- added public ajax routing (not checking permissions)
- exception handling in form subscription
2016-03-01 13:18:36 +01:00
0b3aa0d12a Merge pull request #370 from mailpoet/import_batch_update
Import batch size increase
2016-03-01 12:10:46 +02:00
4d788f69aa - Updates batch size to 2000 2016-02-29 11:41:17 -05:00
c721843c12 extracted subscription pages code from router 2016-02-29 13:34:17 +01:00
d2be407ccb rendering of edit subscription form 2016-02-29 13:34:17 +01:00
e6337216cf improved demo view of subscription pages 2016-02-29 13:34:17 +01:00
f1c396f0b0 basic implementation of confirm/edit/unsubscribe pages 2016-02-29 13:34:17 +01:00
3622bc9fcb fixed sending issue of confirmation email 2016-02-29 13:34:17 +01:00
14fe333678 Send confirmation email + page 2016-02-29 13:34:17 +01:00
cf6466197a Fixed Subscribers' bulk actions when filtering by a segment
- filter by segment is now affected by the selected group (all, trash,...)
- updated relationship methods between subscribers & segments (to account for subsegment status)
2016-02-29 13:34:17 +01:00
f56bee76f2 MailPoet.Date to handle localized dates and times 2016-02-29 13:34:17 +01:00
2ec6bc8c99 Update release version to 0.0.18 2016-02-26 16:57:42 +02:00
4aeccb1961 Merge pull request #368 from mailpoet/rendering_serverside_update
Rendering serverside update
2016-02-26 16:18:11 +02:00
bd593b1ad4 - Updates button/spacer rendering and unit tests 2016-02-26 09:05:43 -05:00
bb7812bd5d Merge pull request #366 from mailpoet/newsletter_rendering
Newsletter rendering
2016-02-25 21:14:16 -05:00
73ed070a34 - Formats code 2016-02-25 11:32:56 -05:00
9e81c48bf8 - Adds new 'fontWeight' property
- Limits max button width to column width
- Adds bold option to buttons
2016-02-25 11:32:09 -05:00
f9028d28c0 - Adds background color to spacer 2016-02-25 11:31:38 -05:00
da32b243ea Change header, footer and text padding based on Becs' feedback 2016-02-25 16:29:55 +02:00
5092c3d328 Reduce ALC post refresh timeout to 0.5s instead of 2s 2016-02-25 16:29:55 +02:00
06ad4488bf Homogenize ALC and Posts output, wrap Read More text in a paragraph tag 2016-02-25 16:29:55 +02:00
a856800e6d Set line height multiplier to golden ratio for editor text blocks 2016-02-25 16:28:32 +02:00
dfc680f3a1 Do not rerender header and footer blocks on content change 2016-02-25 16:28:32 +02:00
a9baecc504 Specify encoding for newsletter previews in browser to fix entity issue 2016-02-25 16:28:32 +02:00
11e15659ac Merge pull request #365 from mailpoet/export_fix
Export update
2016-02-25 15:51:52 +02:00
ebe440a272 Remove debugging statement 2016-02-25 15:51:25 +02:00
853794d459 Merge pull request #364 from mailpoet/phpmail_fix
Fixes localhost sending
2016-02-25 15:20:30 +02:00
cfea13bf81 Merge pull request #363 from mailpoet/alc_posts_polishing
Alc posts polishing
2016-02-25 14:27:44 +02:00
7f291d80b9 - Updates UI based on @rafaehlers review comments
- Closes #323
2016-02-24 21:54:33 -05:00
9840b55de6 - Adds unit test for image link 2016-02-24 19:16:48 -05:00
ca7322933f - Fixes issue with incorrect transport being used for localhost sending 2016-02-24 11:56:29 -05:00
81569e5b81 Fix PHP code style 2016-02-23 15:37:00 +02:00
1942972282 Change title position to featured image position 2016-02-23 15:26:12 +02:00
a23aac370c Add optional links to image rendering 2016-02-23 13:07:52 +02:00
99d6f74d1b Change inline form fields to not be inline 2016-02-23 13:07:52 +02:00
a883e1176c Merge pull request #359 from mailpoet/import_batch_processing
Import update
2016-02-23 13:04:48 +02:00
24b98a1154 Move jquery.asyncqueue.js to assets/js/src/vendor 2016-02-23 12:49:11 +02:00
8dbb6ab79f - Updates based on code review comments 2016-02-22 11:54:31 -05:00
3e7d1690bd Merge pull request #360 from mailpoet/manage_subscription
Manage subscriptions (part 1)
2016-02-22 17:05:24 +02:00
07d533a810 Manage subscriptions
- make use of the SubscriberSegment::status column to keep track of unsubscriptions
- unsubscribed segments now appear grayed out in the Subscribers listing
- added unsubscribed_at after segment names when editing a subscriber
- added date() method for Twig (uses WP's date format / date_offset)
- fixed typo in Form iframe export
- fixed unit test for Newsletters
- updated selection component (JSX) to allow more customization
2016-02-22 11:35:34 +01:00
499936e3ab - Removes file size limit in import
- Implements chunked import processing
- Updates tests/migrator/Subscriber model
2016-02-20 18:55:34 -05:00
580ac989aa Bump up version to 0.0.17 2016-02-19 15:35:27 +02:00
acf300160d Merge pull request #356 from mailpoet/page_reviews
Page reviews
2016-02-18 15:34:10 +02:00
983df4ee13 Merge pull request #355 from mailpoet/editor_polishing_3
Editor polishing 3
2016-02-18 07:45:20 -05:00
03c782d4ab Fixed issue #305
- added validation on add/edit Custom Field for multiple values (select/radio)
- disabled Remove link when only one value remains
- Added confirmation message on Reinstall
- Added missing period in Import
2016-02-18 13:14:57 +01:00
0daf7e12c1 remove logging function (polluting unit tests) & bugfix on model subscriber addToSegments 2016-02-18 13:13:19 +01:00
6a2e18a0e1 fix segments being reset on Subscriber::createOrUpdate() 2016-02-18 13:13:19 +01:00
316d5ab183 fixed unit tests 2016-02-18 13:13:19 +01:00
eb6bba5961 Merge pull request #351 from mailpoet/import_language_update
Updates error messages displayed during import
2016-02-18 09:55:59 +01:00
b5864adf06 - Fixes writable path check 2016-02-17 11:47:20 -05:00
9bce50a633 Fix "Full width" image option 2016-02-17 15:10:51 +02:00
365a53cf27 Merge pull request #353 from mailpoet/subscribers_page_review
Subscribers page review
2016-02-17 14:28:36 +02:00
31a4575d43 replaced closure by callbacks 2016-02-17 13:02:35 +01:00
1ae584c4e7 Restyle ALC Post number/type selector, limit to 2 character input 2016-02-17 13:29:16 +02:00
aac2cd6eb8 Add button "Bold" text option, fix unit tests 2016-02-17 12:25:03 +02:00
9874e1c371 Remove 1px left black border from newsletter thumbnails 2016-02-17 12:25:03 +02:00
16b1c0dc41 Convert mailpoet buttons to WP buttons in newsletter editor 2016-02-17 12:25:03 +02:00
9223fbf478 Display loading animation when listing newsletter templates 2016-02-17 12:25:03 +02:00
eb27de36f4 Select active block format and allow custom colors in TinyMCe 2016-02-17 12:25:03 +02:00
636fa38ab6 - Enables check for writable export file 2016-02-16 17:35:20 -05:00
9b1503dc7a Fixed reported issues + refactoring
- refactored Config/Hooks to make it more readable
- added hook to save limit per page
- added default limit per page as a constant in Listing/Handler
2016-02-16 16:33:20 +01:00
2ac3b00af6 Merge pull request #354 from mailpoet/parsley_firefox_fix
fixed parsley issue with firefox
2016-02-16 12:32:29 +02:00
5fcdbfe826 fixed parsley issue with firefox 2016-02-15 21:19:21 +01:00
67036ddb61 cleanup and bugfix on bulk actions 2016-02-15 15:50:47 +01:00
6c0f6a07cd removed useless constant 2016-02-15 15:40:21 +01:00
8139a7dd0a Subscribers page review
- added screen option to set number of items per page
- improved bulk actions in order to handle large sets
2016-02-15 15:40:21 +01:00
97adfc14c0 Bump up version to 0.0.16 2016-02-15 14:50:13 +02:00
4ed703a351 Merge pull request #352 from mailpoet/exception_fix
Fixes remaining exception namescape issues
2016-02-15 13:39:18 +02:00
2aee853406 - Fixes remaining exception namescape issues 2016-02-13 21:39:55 -05:00
854736fac7 - Updates error messages 2016-02-12 14:12:14 -05:00
192 changed files with 7466 additions and 9721 deletions

View File

@ -89,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
} }
function makepot() { function makepot() {
$this->_exec('grunt makepot'. $this->_exec('./node_modules/.bin/grunt makepot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'. ' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__ ' --base_path '.__DIR__
); );
} }
function pushpot() { function pushpot() {
$this->_exec('grunt pushpot'. $this->_exec('./node_modules/.bin/grunt pushpot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'. ' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__ ' --base_path '.__DIR__
); );
@ -123,7 +123,7 @@ class RoboFile extends \Robo\Tasks {
$this->compileJs(); $this->compileJs();
$this->_exec(join(' ', array( $this->_exec(join(' ', array(
'./node_modules/mocha/bin/mocha', './node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js', '-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js' 'tests/javascript/testBundles/**/*.js'
))); )));

View File

@ -17,3 +17,5 @@
@require 'settings' @require 'settings'
@require 'progress_bar' @require 'progress_bar'
@require 'subscribers'

View File

@ -37,3 +37,6 @@
input[type=text] input[type=text]
vertical-align: middle vertical-align: middle
.mailpoet_form_field_block
display: block

View File

@ -38,9 +38,7 @@
content: '\f142' content: '\f142'
.mailpoet_save_show_options_icon .mailpoet_save_show_options_icon
width: auto vertical-align: middle
height: auto
line-height: auto
&::before &::before
content: '\f140' content: '\f140'

View File

@ -21,3 +21,9 @@
.mailpoet_automated_latest_content_display_options .mailpoet_automated_latest_content_display_options
animation-slide-open-downwards() animation-slide-open-downwards()
.mailpoet_automated_latest_content_show_amount
width: 25px
.mailpoet_automated_latest_content_content_type
width: 180px

View File

@ -30,3 +30,8 @@ $block-hover-highlight-color = $primary-active-color
.mailpoet_content .mailpoet_content
position: relative position: relative
line-height: 1.61803398875
p, h1, h2, h3, h4, h5, h6
line-height: 1.61803398875
font-style: normal

View File

@ -1,6 +1,10 @@
.mailpoet_footer_block .mailpoet_footer_block
padding-left: 0 padding-left: 0
padding-right: 0 padding-right: 0
margin-bottom: 0
.mailpoet_content .mailpoet_content
padding: 5px 20px padding: 10px 20px
p
margin: 0

View File

@ -1,6 +1,10 @@
.mailpoet_header_block .mailpoet_header_block
padding-left: 0 padding-left: 0
padding-right: 0 padding-right: 0
margin-bottom: 0
.mailpoet_content .mailpoet_content
padding: 5px 20px padding: 10px 20px
p
margin: 0

View File

@ -11,9 +11,6 @@
padding-right: 0 padding-right: 0
margin-bottom: 0 margin-bottom: 0
img
width: 100%
.mailpoet_content a:hover .mailpoet_content a:hover
cursor: all-scroll cursor: all-scroll

View File

@ -1,16 +1,23 @@
$text-vertical-padding = 3px
.mailpoet_text_block .mailpoet_text_block
padding-left: 0 padding-left: 0
padding-right: 0 padding-right: 0
& > .mailpoet_content & > .mailpoet_content
overflow: hidden overflow: hidden
padding-top: 13px padding-top: 0
padding-bottom: 13px padding-bottom: 0px
padding-left: 20px padding-left: 20px
padding-right: 20px padding-right: 20px
h1, h2, h3, h4, h5, h6
padding: 0
margin: 0
font-weight: normal
p
margin-top: 0
font-weight: normal
blockquote blockquote
margin: 1em margin: 1em
padding-left: 1em padding-left: 1em

View File

@ -133,3 +133,17 @@ body
.wrap > .mailpoet_notice, .wrap > .mailpoet_notice,
.update-nag .update-nag
margin-left: 2px + 15px !important margin-left: 2px + 15px !important
/* Make a button group */
.mailpoet_button_group
.button:first-child
border-right: 0
border-top-right-radius: 0
border-bottom-right-radius: 0
.button:last-child
border-left: 0
border-top-left-radius: 0
border-bottom-left-radius: 0

View File

@ -2,3 +2,13 @@
@require 'parsley' @require 'parsley'
@require 'form_validation' @require 'form_validation'
/* labels */
.mailpoet_text_label
.mailpoet_textarea_label
.mailpoet_select_label
.mailpoet_radio_label
.mailpoet_checkbox_label
.mailpoet_list_label
.mailpoet_date_label
display:block

View File

@ -0,0 +1,3 @@
#subscribers_container
.mailpoet_segments_unsubscribed
color: lighten(#555, 33)

136
assets/js/src/date.js Normal file
View File

@ -0,0 +1,136 @@
define('date',
[
'mailpoet',
'jquery',
'moment'
], function(
MailPoet,
jQuery,
Moment
) {
'use strict';
MailPoet.Date = {
version: 0.1,
options: {},
defaults: {
offset: 0,
format: 'F, d Y H:i:s'
},
init: function(options) {
options = options || {};
// set UTC offset
if (
options.offset === undefined
&& window.mailpoet_date_offset !== undefined
) {
options.offset = window.mailpoet_date_offset;
}
// set date format
if (
options.format === undefined
&& window.mailpoet_date_format !== undefined
) {
options.format = window.mailpoet_date_format;
}
// merge options
this.options = jQuery.extend({}, this.defaults, options);
return this;
},
format: function(date, options) {
this.init(options);
return Moment.utc(date)
.local()
.format(this.convertFormat(this.options.format));
},
short: function(date) {
return this.format(date, {
format: 'F, j Y'
});
},
full: function(date) {
return this.format(date, {
format: 'F, j Y H:i:s'
});
},
time: function(date) {
return this.format(date, {
format: 'H:i:s'
});
},
convertFormat: function(format) {
const format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
d: 'DD',
j: 'D',
z: 'DDDD',
N: 'E',
S: '',
M: 'MMM',
F: 'MMMM',
m: 'MM',
n: '',
t: '',
y: 'YY',
Y: 'YYYY',
H: 'HH',
h: 'hh',
g: 'h',
A: 'A',
i: 'mm',
s: 'ss',
T: 'z',
O: 'ZZ',
w: 'd',
W: 'WW'
},
strftime: {
a: 'ddd',
A: 'dddd',
b: 'MMM',
B: 'MMMM',
d: 'DD',
e: 'D',
F: 'YYYY-MM-DD',
H: 'HH',
I: 'hh',
j: 'DDDD',
k: 'H',
l: 'h',
m: 'MM',
M: 'mm',
p: 'A',
S: 'ss',
u: 'E',
w: 'd',
W: 'WW',
y: 'YY',
Y: 'YYYY',
z: 'ZZ',
Z: 'z'
}
};
const replacements = format_mappings['date'];
let outputFormat = '';
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {
format = format.replace(key, '%'+key);
}
});
outputFormat = format;
Object.keys(replacements).forEach(function(key) {
if (outputFormat.indexOf('%'+key) !== -1) {
outputFormat = outputFormat.replace('%'+key, replacements[key]);
}
});
return outputFormat;
}
};
});

View File

@ -10,10 +10,13 @@ function(
return this.props.onValueChange(e); return this.props.onValueChange(e);
}, },
render: function() { render: function() {
const isChecked = !!(this.props.item[this.props.field.name]); if (this.props.field.values === undefined) {
return false;
}
const isChecked = !!(this.props.item[this.props.field.name]);
const options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
function(value, index) { (value, index) => {
return ( return (
<p key={ 'checkbox-' + index }> <p key={ 'checkbox-' + index }>
<label> <label>
@ -29,7 +32,7 @@ function(
</label> </label>
</p> </p>
); );
}.bind(this) }
); );
return ( return (

View File

@ -4,12 +4,15 @@ define([
function( function(
React React
) { ) {
var FormFieldRadio = React.createClass({ const FormFieldRadio = React.createClass({
render: function() { render: function() {
var selected_value = this.props.item[this.props.field.name]; if (this.props.field.values === undefined) {
return false;
}
var options = Object.keys(this.props.field.values).map( const selected_value = this.props.item[this.props.field.name];
function(value, index) { const options = Object.keys(this.props.field.values).map(
(value, index) => {
return ( return (
<p key={ 'radio-' + index }> <p key={ 'radio-' + index }>
<label> <label>
@ -23,7 +26,7 @@ function(
</label> </label>
</p> </p>
); );
}.bind(this) }
); );
return ( return (

View File

@ -4,10 +4,14 @@ define([
function( function(
React React
) { ) {
var FormFieldSelect = React.createClass({ const FormFieldSelect = React.createClass({
render: function() { render: function() {
var options = if (this.props.field.values === undefined) {
Object.keys(this.props.field.values).map(function(value, index) { return false;
}
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return ( return (
<option <option
key={ 'option-' + index } key={ 'option-' + index }
@ -15,7 +19,7 @@ function(
{ this.props.field.values[value] } { this.props.field.values[value] }
</option> </option>
); );
}.bind(this) }
); );
return ( return (

View File

@ -26,7 +26,7 @@ function(
&& (this.props.item.id !== prevProps.item.id) && (this.props.item.id !== prevProps.item.id)
) { ) {
jQuery('#'+this.refs.select.id) jQuery('#'+this.refs.select.id)
.val(this.props.item[this.props.field.name]) .val(this.getSelectedValues())
.trigger('change'); .trigger('change');
} }
}, },
@ -44,10 +44,14 @@ function(
templateResult: function(item) { templateResult: function(item) {
if(item.element && item.element.selected) { if(item.element && item.element.selected) {
return null; return null;
} else {
if(item.title) {
return item.title;
} else { } else {
return item.text; return item.text;
} }
} }
}
}); });
var hasRemoved = false; var hasRemoved = false;
@ -65,15 +69,25 @@ function(
select2.select2( select2.select2(
'val', 'val',
this.props.item[this.props.field.name] this.getSelectedValues()
); );
this.setState({ initialized: true }); this.setState({ initialized: true });
}, },
getSelectedValues: function() {
if(this.props.field['selected'] !== undefined) {
return this.props.field['selected'](this.props.item);
} else if(this.props.item !== undefined && this.props.field.name !== undefined) {
return this.props.item[this.props.field.name];
} else {
return null;
}
},
loadCachedItems: function() { loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') { if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint]; var items = window['mailpoet_'+this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) { if(this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter); items = items.filter(this.props.field.filter);
} }
@ -98,31 +112,48 @@ function(
}); });
} }
}, },
getLabel: function(item) {
if(this.props.field['getLabel'] !== undefined) {
return this.props.field.getLabel(item, this.props.item);
}
return item.name;
},
getSearchLabel: function(item) {
if(this.props.field['getSearchLabel'] !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item);
}
return null;
},
getValue: function(item) {
if(this.props.field['getValue'] !== undefined) {
return this.props.field.getValue(item, this.props.item);
}
return item.id;
},
render: function() { render: function() {
var options = this.state.items.map(function(item, index) { const options = this.state.items.map((item, index) => {
let label = this.getLabel(item);
let searchLabel = this.getSearchLabel(item);
let value = this.getValue(item);
return ( return (
<option <option
key={ item.id } key={ 'option-'+index }
value={ item.id } value={ value }
title={ searchLabel }
> >
{ item.name } { label }
</option> </option>
); );
}); });
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
return ( return (
<select <select
id={ this.props.field.id || this.props.field.name } id={ this.props.field.id || this.props.field.name }
ref="select" ref="select"
data-placeholder={ this.props.field.placeholder } data-placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple } multiple={ this.props.field.multiple }
defaultValue={ default_value } defaultValue={ this.getSelectedValues() }
{...this.props.field.validation} {...this.props.field.validation}
>{ options }</select> >{ options }</select>
); );

View File

@ -146,7 +146,7 @@ const FormList = React.createClass({
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Created on"> <td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr> <abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -12,9 +12,19 @@ define([
'newsletter_editor/blocks/button', 'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider', 'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
'mailpoet',
'underscore', 'underscore',
'jquery' 'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) { ], function(
App,
BaseBlock,
ButtonBlock,
DividerBlock,
CommunicationComponent,
MailPoet,
_,
jQuery
) {
"use strict"; "use strict";
@ -32,10 +42,10 @@ define([
inclusionType: 'include', // 'include'|'exclude' inclusionType: 'include', // 'include'|'exclude'
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly' displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul' titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
titleAlignment: 'left', // 'left'|'center'|'right' titleAlignment: 'left', // 'left'|'center'|'right'
titleIsLink: false, // false|true titleIsLink: false, // false|true
imageFullWidth: false, // true|false imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none' //imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText' showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:', authorPrecededBy: 'Author:',
@ -63,7 +73,7 @@ define([
initialize: function() { initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.fetchPosts(); this.fetchPosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this); this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
}, },
@ -73,7 +83,7 @@ define([
that.get('_container').get('blocks').reset(content, {parse: true}); that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged'); that.trigger('postsChanged');
}).fail(function(error) { }).fail(function(error) {
console.log('ALC fetchPosts error', arguments); MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
}); });
}, },
/** /**
@ -81,7 +91,7 @@ define([
* ALC posts on each model change * ALC posts on each model change
*/ */
_scheduleFetchPosts: function() { _scheduleFetchPosts: function() {
var timeout = 2000, var timeout = 500,
that = this; that = this;
if (this._fetchPostsTimer !== undefined) { if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer); clearTimeout(this._fetchPostsTimer);
@ -142,9 +152,9 @@ define([
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"), "keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"), "change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"), "change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_automated_latest_content_title_position": _.partial(this.changeField, "titlePosition"),
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"), "change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"), "change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"), "change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"), "keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"), "change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
@ -273,12 +283,13 @@ define([
}, },
changeDisplayType: function(event) { changeDisplayType: function(event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'titleOnly') { if (value == 'titleOnly') {
this.$('.mailpoet_automated_latest_content_title_position_container').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden'); this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
} else { } else {
this.$('.mailpoet_automated_latest_content_title_position_container').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden'); this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly // Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') { if (this.model.get('titleFormat') === 'ul') {
@ -287,6 +298,12 @@ define([
this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden'); this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
} }
} }
if (value === 'excerpt') {
this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
} else {
this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
}
this.changeField('displayType', event); this.changeField('displayType', event);
}, },
changeTitleFormat: function(event) { changeTitleFormat: function(event) {

View File

@ -32,6 +32,7 @@ define([
fontColor: '#000000', fontColor: '#000000',
fontFamily: 'Arial', fontFamily: 'Arial',
fontSize: '16px', fontSize: '16px',
fontWeight: 'normal', // 'normal'|'bold'
textAlign: 'center', textAlign: 'center',
}, },
}, },
@ -72,6 +73,7 @@ define([
"change .mailpoet_field_button_font_size": _.partial(this.changeField, "styles.block.fontSize"), "change .mailpoet_field_button_font_size": _.partial(this.changeField, "styles.block.fontSize"),
"change .mailpoet_field_button_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"), "change .mailpoet_field_button_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"change .mailpoet_field_button_border_color": _.partial(this.changeColorField, "styles.block.borderColor"), "change .mailpoet_field_button_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
"change .mailpoet_field_button_font_weight": "changeFontWeight",
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)), "input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)), "change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
@ -128,6 +130,13 @@ define([
this.$(fieldToUpdate).val(jQuery(event.target).val()); this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event); callable(event);
}, },
changeFontWeight: function(event) {
var checked = !!jQuery(event.target).prop('checked');
this.model.set(
'styles.block.fontWeight',
(checked) ? jQuery(event.target).val() : 'normal'
);
}
}); });
Module.ButtonWidgetView = base.WidgetView.extend({ Module.ButtonWidgetView = base.WidgetView.extend({

View File

@ -16,7 +16,7 @@ define([
defaults: function() { defaults: function() {
return this._getDefaults({ return this._getDefaults({
type: 'footer', type: 'footer',
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>', text: '<a href="[subscription:unsubscribe_url]">Unsubscribe</a> | <a href="[subscription:manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
@ -41,7 +41,7 @@ define([
getTemplate: function() { return templates.footerBlock; }, getTemplate: function() { return templates.footerBlock; },
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render', 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
}, base.BlockView.prototype.modelEvents), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
onDragSubstituteBy: function() { return Module.FooterWidgetView; }, onDragSubstituteBy: function() { return Module.FooterWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model }); this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
@ -60,11 +60,9 @@ define([
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br", 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", invalid_elements: "script",
style_formats: [ block_formats: 'Paragraph=p',
{title: 'Paragraph', block: 'p'},
],
plugins: "link textcolor mailpoet_custom_fields", plugins: "link textcolor colorpicker mailpoet_custom_fields",
setup: function(editor) { setup: function(editor) {
editor.on('change', function(e) { editor.on('change', function(e) {

View File

@ -16,7 +16,7 @@ define([
defaults: function() { defaults: function() {
return this._getDefaults({ return this._getDefaults({
type: 'header', type: 'header',
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>', text: 'Display problems? <a href="[newsletter:view_in_browser_url]">View it in your browser</a>',
styles: { styles: {
block: { block: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
@ -41,7 +41,7 @@ define([
getTemplate: function() { return templates.headerBlock; }, getTemplate: function() { return templates.headerBlock; },
modelEvents: _.extend({ modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render', 'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
}, base.BlockView.prototype.modelEvents), }, _.omit(base.BlockView.prototype.modelEvents, 'change')),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; }, onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model }); this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
@ -60,11 +60,9 @@ define([
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br", 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", invalid_elements: "script",
style_formats: [ block_formats: 'Paragraph=p',
{title: 'Paragraph', block: 'p'},
],
plugins: "link textcolor mailpoet_custom_fields", plugins: "link textcolor colorpicker mailpoet_custom_fields",
setup: function(editor) { setup: function(editor) {
editor.on('change', function(e) { editor.on('change', function(e) {

View File

@ -43,10 +43,10 @@ define([
inclusionType: 'include', // 'include'|'exclude' inclusionType: 'include', // 'include'|'exclude'
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly' displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul' titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
titleAlignment: 'left', // 'left'|'center'|'right' titleAlignment: 'left', // 'left'|'center'|'right'
titleIsLink: false, // false|true titleIsLink: false, // false|true
imageFullWidth: false, // true|false imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none' //imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText' showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:', authorPrecededBy: 'Author:',
@ -88,7 +88,7 @@ define([
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts); this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts); this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts); this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts); this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts); this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
@ -101,7 +101,7 @@ define([
that.get('_selectedPosts').reset(); // Empty out the collection that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts'); that.trigger('change:_availablePosts');
}).fail(function() { }).fail(function() {
console.log('Posts fetchPosts error', arguments); MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchAvailablePosts'));
}); });
}, },
_refreshTransformedPosts: function() { _refreshTransformedPosts: function() {
@ -111,14 +111,14 @@ define([
data.posts = this.get('_selectedPosts').pluck('ID'); data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) { if (data.posts.length === 0) {
this.get('_transformedPosts.blocks').reset(); this.get('_transformedPosts').get('blocks').reset();
return; return;
} }
CommunicationComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function(posts) {
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true}); that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() { }).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments); MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
}); });
}, },
_insertSelectedPosts: function() { _insertSelectedPosts: function() {
@ -134,7 +134,7 @@ define([
CommunicationComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function(posts) {
collection.add(posts, { at: index }); collection.add(posts, { at: index });
}).fail(function() { }).fail(function() {
console.log('Posts fetchPosts error', arguments); MailPoet.Notice.error(App.getConfig().get('translations.failedToFetchRenderedPosts'));
}); });
}, },
}); });
@ -394,9 +394,9 @@ define([
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"), "keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"), "change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"), "change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_posts_title_position": _.partial(this.changeField, "titlePosition"),
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"), "change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"), "change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"), "change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"), "keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"), "change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
@ -448,11 +448,11 @@ define([
changeDisplayType: function(event) { changeDisplayType: function(event) {
var value = jQuery(event.target).val(); var value = jQuery(event.target).val();
if (value == 'titleOnly') { if (value == 'titleOnly') {
this.$('.mailpoet_posts_title_position_container').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden'); this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
} else { } else {
this.$('.mailpoet_posts_title_position_container').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden'); this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly // Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') { if (this.model.get('titleFormat') === 'ul') {
@ -461,6 +461,13 @@ define([
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden'); this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
} }
} }
if (value === 'excerpt') {
this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
} else {
this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
}
this.changeField('displayType', event); this.changeField('displayType', event);
}, },
changeTitleFormat: function(event) { changeTitleFormat: function(event) {

View File

@ -52,21 +52,15 @@ define([
inline: true, inline: true,
menubar: false, menubar: false,
toolbar1: "styleselect bold italic forecolor | link unlink", 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_custom_fields",
//forced_root_block: 'p', //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]", 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", invalid_elements: "script",
style_formats: [ block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
{title: 'Heading 1', block: 'h1'},
{title: 'Heading 2', block: 'h2'},
{title: 'Heading 3', block: 'h3'},
{title: 'Paragraph', block: 'p'}, plugins: "link code textcolor colorpicker mailpoet_custom_fields",
],
plugins: "link code textcolor mailpoet_custom_fields",
setup: function(editor) { setup: function(editor) {
editor.on('change', function(e) { editor.on('change', function(e) {

View File

@ -10,7 +10,6 @@ define([
availableStyles: {}, availableStyles: {},
socialIcons: {}, socialIcons: {},
blockDefaults: {}, blockDefaults: {},
translations: {},
sidepanelWidth: '331px', sidepanelWidth: '331px',
validation: {}, validation: {},
urls: {}, urls: {},

View File

@ -67,7 +67,28 @@ define([
}; };
Module.getThumbnail = function(element, options) { Module.getThumbnail = function(element, options) {
return html2canvas(element, options || {}); var promise = html2canvas(element, options || {});
return promise.then(function(oldCanvas) {
// Temporary workaround for html2canvas-alpha2.
// Removes 1px left transparent border from resulting canvas.
var oldContext = oldCanvas.getContext('2d'),
newCanvas = document.createElement("canvas"),
newContext = newCanvas.getContext("2d"),
leftBorderWidth = 1;
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
newContext.drawImage(
oldCanvas,
leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
0, 0, oldCanvas.width, oldCanvas.height
);
return newCanvas;
});
}; };
Module.saveTemplate = function(options) { Module.saveTemplate = function(options) {
@ -185,12 +206,10 @@ define([
} }
); );
} else { } else {
console.log('Saving template with ', templateName, templateDescription);
Module.saveTemplate({ Module.saveTemplate({
name: templateName, name: templateName,
description: templateDescription, description: templateDescription,
}).done(function() { }).done(function() {
console.log('Template saved', arguments);
MailPoet.Notice.success( MailPoet.Notice.success(
App.getConfig().get('translations.templateSaved'), App.getConfig().get('translations.templateSaved'),
{ {
@ -199,7 +218,6 @@ define([
} }
); );
}).fail(function() { }).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'), App.getConfig().get('translations.templateSaveFailed'),
{ {
@ -241,7 +259,6 @@ define([
} }
); );
} else { } else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({ Module.exportTemplate({
name: templateName, name: templateName,
description: templateDescription, description: templateDescription,
@ -257,7 +274,6 @@ define([
next: function() { next: function() {
this.hideOptionContents(); this.hideOptionContents();
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) { if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
console.log('Next');
window.location.href = App.getConfig().get('urls.send'); window.location.href = App.getConfig().get('urls.send');
} }
}, },
@ -268,7 +284,7 @@ define([
} }
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') && if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) { JSON.stringify(jsonObject).indexOf("[subscription:unsubscribe_url]") < 0) {
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing')); this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
return; return;
} }

View File

@ -1,13 +1,24 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/communication', 'newsletter_editor/components/communication',
'mailpoet',
'backbone', 'backbone',
'backbone.marionette', 'backbone.marionette',
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'jquery', 'jquery',
'sticky-kit' 'sticky-kit'
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) { ], function(
App,
CommunicationComponent,
MailPoet,
Backbone,
Marionette,
SuperModel,
_,
jQuery,
StickyKit
) {
"use strict"; "use strict";
@ -230,27 +241,39 @@ define([
json.body = JSON.stringify(json.body); json.body = JSON.stringify(json.body);
} }
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'render', action: 'render',
data: json, data: json,
}).done(function(response){ }).done(function(response){
console.log('Should open a new window'); MailPoet.Modal.loading(false);
window.open('data:text/html,' + encodeURIComponent(response.rendered_body), '_blank'); window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
}).fail(function(error) { }).fail(function(error) {
console.log('Preview error', json); MailPoet.Modal.loading(false);
alert('Something went wrong, check console'); alert('Something went wrong, check console');
}); });
}, },
sendPreview: function() { sendPreview: function() {
// testing sending method
console.log('trying to send a preview');
// get form data // get form data
var $emailField = this.$('#mailpoet_preview_to_email');
var data = { var data = {
subscriber: this.$('#mailpoet_preview_to_email').val(), subscriber: $emailField.val(),
id: App.getNewsletter().get('id'), id: App.getNewsletter().get('id'),
}; };
if (data.subscriber.length <= 0) {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewEmailMissing'),
{
positionAfter: $emailField,
scroll: true,
}
);
return false;
}
// send test email // send test email
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);

View File

@ -135,7 +135,7 @@ define(
}); });
}, },
renderStatus: function(item) { renderStatus: function(item) {
if(item.queue === null) { if(!item.queue) {
return ( return (
<span>Not sent yet.</span> <span>Not sent yet.</span>
); );
@ -208,10 +208,8 @@ define(
'has-row-actions' 'has-row-actions'
); );
var segments = mailpoet_segments.filter(function(segment) { var segments = newsletter.segments.map(function(segment) {
return (jQuery.inArray(segment.id, newsletter.segments) !== -1); return segment.name
}).map(function(segment) {
return segment.name;
}).join(', '); }).join(', ');
return ( return (
@ -229,10 +227,10 @@ define(
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Subscribed on"> <td className="column-date" data-colname="Subscribed on">
<abbr>{ newsletter.created_at }</abbr> <abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
</td> </td>
<td className="column-date" data-colname="Last modified on"> <td className="column-date" data-colname="Last modified on">
<abbr>{ newsletter.updated_at }</abbr> <abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -26,7 +26,8 @@ define(
"Tempt them to open your email.", "Tempt them to open your email.",
type: 'text', type: 'text',
validation: { validation: {
'data-parsley-required': true 'data-parsley-required': true,
'data-parsley-required-message': 'You need to specify a subject.'
} }
}, },
{ {
@ -42,7 +43,8 @@ define(
return !!(!segment.deleted_at); return !!(!segment.deleted_at);
}, },
validation: { validation: {
'data-parsley-required': true 'data-parsley-required': true,
'data-parsley-required-message': 'You need to select a segment.'
} }
}, },
{ {
@ -107,8 +109,16 @@ define(
mixins: [ mixins: [
Router.History Router.History
], ],
componentDidMount: function() {
jQuery('#mailpoet_newsletter').parsley();
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
handleSend: function() { handleSend: function() {
if(jQuery('#mailpoet_newsletter').parsley().validate() === true) { if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'sendingQueue', endpoint: 'sendingQueue',
action: 'add', action: 'add',
@ -132,9 +142,7 @@ define(
); );
} else { } else {
if(response.errors) { if(response.errors) {
MailPoet.Notice.error( MailPoet.Notice.error(response.errors);
response.errors.join("<br />")
);
} else { } else {
MailPoet.Notice.error( MailPoet.Notice.error(
'An error occurred while trying to send. '+ 'An error occurred while trying to send. '+
@ -144,6 +152,7 @@ define(
} }
}.bind(this)); }.bind(this));
} }
return false;
}, },
render: function() { render: function() {
return ( return (
@ -158,6 +167,7 @@ define(
fields={ fields } fields={ fields }
params={ this.props.params } params={ this.props.params }
messages={ messages } messages={ messages }
isValid={ this.isValid }
> >
<p className="submit"> <p className="submit">
<input <input

View File

@ -24,11 +24,14 @@ define(
template.body = JSON.stringify(template.body); template.body = JSON.stringify(template.body);
} }
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'newsletterTemplates', endpoint: 'newsletterTemplates',
action: 'save', action: 'save',
data: template data: template
}).done(function(response) { }).done(function(response) {
MailPoet.Modal.loading(false);
if(response.result === true) { if(response.result === true) {
this.props.onImport(template); this.props.onImport(template);
} else { } else {
@ -92,10 +95,13 @@ define(
getTemplates: function() { getTemplates: function() {
this.setState({ loading: true }); this.setState({ loading: true });
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'newsletterTemplates', endpoint: 'newsletterTemplates',
action: 'getAll', action: 'getAll',
}).done(function(response) { }).done(function(response) {
MailPoet.Modal.loading(false);
if(this.isMounted()) { if(this.isMounted()) {
if(response.length === 0) { if(response.length === 0) {

View File

@ -53,13 +53,13 @@ define(
var weekDayField = { var weekDayField = {
name: 'weekDay', name: 'weekDay',
values: { values: {
0: 'Monday', 0: 'Sunday',
1: 'Tuesday', 1: 'Monday',
2: 'Wednesday', 2: 'Tuesday',
3: 'Thursday', 3: 'Wednesday',
4: 'Friday', 4: 'Thursday',
5: 'Saturday', 5: 'Friday',
6: 'Sunday', 6: 'Saturday'
}, },
}; };
@ -84,10 +84,10 @@ define(
var nthWeekDayField = { var nthWeekDayField = {
name: 'nthWeekDay', name: 'nthWeekDay',
values: { values: {
'0': '1st', '1': '1st',
'1': '2nd', '2': '2nd',
'2': '3rd', '3': '3rd',
'3': 'last', 'L': 'last',
}, },
}; };
@ -99,9 +99,9 @@ define(
return { return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly' intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0, timeOfDay: 0,
weekDay: 0, weekDay: 1,
monthDay: 0, monthDay: 0,
nthWeekDay: 0, nthWeekDay: 1,
}; };
}, },
handleIntervalChange: function(event) { handleIntervalChange: function(event) {

View File

@ -155,6 +155,8 @@ define(
<h1>Welcome email</h1> <h1>Welcome email</h1>
<Breadcrumb step="type" /> <Breadcrumb step="type" />
<h3>{MailPoetI18n.selectEventToSendWelcomeEmail}</h3>
<Select <Select
field={events} field={events}
item={this.state} item={this.state}

View File

@ -181,16 +181,16 @@ const SegmentList = React.createClass({
<abbr>{ segment.description }</abbr> <abbr>{ segment.description }</abbr>
</td> </td>
<td className="column-date" data-colname="Subscribed"> <td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr> <abbr>{ segment.subscribers_count.subscribed || 0 }</abbr>
</td> </td>
<td className="column-date" data-colname="Unconfirmed"> <td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr> <abbr>{ segment.subscribers_count.unconfirmed || 0 }</abbr>
</td> </td>
<td className="column-date" data-colname="Unsubscribed"> <td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr> <abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
</td> </td>
<td className="column-date" data-colname="Created on"> <td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr> <abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -11,7 +11,6 @@ define(
MailPoet, MailPoet,
Form Form
) { ) {
var fields = [ var fields = [
{ {
name: 'email', name: 'email',
@ -45,8 +44,37 @@ define(
placeholder: "Select a list", placeholder: "Select a list",
endpoint: "segments", endpoint: "segments",
multiple: true, multiple: true,
selected: function(subscriber) {
if (Array.isArray(subscriber.subscriptions) === false) {
return null;
}
return subscriber.subscriptions.map(function(subscription) {
if (subscription.status === 'subscribed') {
return subscription.segment_id;
}
});
},
filter: function(segment) { filter: function(segment) {
return !!(!segment.deleted_at); return !!(!segment.deleted_at);
},
getSearchLabel: function(segment, subscriber) {
let label = '';
if (subscriber.subscriptions !== undefined) {
subscriber.subscriptions.map(function(subscription) {
if (segment.id === subscription.segment_id) {
label = segment.name;
if (subscription.status === 'unsubscribed') {
const unsubscribed_at = MailPoet.Date
.format(subscription.updated_at);
label += ' (Unsubscribed on '+unsubscribed_at+')';
}
}
});
}
return label;
} }
} }
]; ];

View File

@ -115,7 +115,7 @@ define(
exportData.exportConfirmedOption = false; exportData.exportConfirmedOption = false;
renderSegmentsAndFields(segmentsContainerElement, segments); renderSegmentsAndFields(segmentsContainerElement, segments);
} }
segmentsContainerElement.select2('val', selectedSegments); segmentsContainerElement.val(selectedSegments).trigger('change');
}); });
function toggleNextStepButton(condition) { function toggleNextStepButton(condition) {
@ -148,7 +148,7 @@ define(
.done(function (response) { .done(function (response) {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
if (response.result === false) { if (response.result === false) {
MailPoet.Notice.error(response.error); MailPoet.Notice.error(response.errors);
} else { } else {
resultMessage = MailPoetI18n.exportMessage resultMessage = MailPoetI18n.exportMessage
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>') .replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')

View File

@ -6,7 +6,9 @@ define(
'mailpoet', 'mailpoet',
'handlebars', 'handlebars',
'papaparse', 'papaparse',
'select2' 'select2',
'asyncqueue',
'xss'
], ],
function ( function (
Backbone, Backbone,
@ -14,12 +16,15 @@ define(
jQuery, jQuery,
MailPoet, MailPoet,
Handlebars, Handlebars,
Papa Papa,
AsyncQueue,
xss
) { ) {
if (!jQuery('#mailpoet_subscribers_import').length) { if (!jQuery('#mailpoet_subscribers_import').length) {
return; return;
} }
jQuery(document).ready(function () { jQuery(document).ready(function () {
var noticeTimeout = 3000;
jQuery('input[name="select_method"]').attr('checked', false); jQuery('input[name="select_method"]').attr('checked', false);
// configure router // configure router
router = new (Backbone.Router.extend({ router = new (Backbone.Router.extend({
@ -124,7 +129,7 @@ define(
var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1; var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1;
if (pasteSize > maxPostSizeBytes) { if (pasteSize > maxPostSizeBytes) {
MailPoet.Notice.error(MailPoetI18n.maxPostSizeNotice, { MailPoet.Notice.error(MailPoetI18n.maxPostSizeNotice, {
timeout: 3000, timeout: noticeTimeout,
}); });
return; return;
} }
@ -144,7 +149,7 @@ define(
if (ext === null || ext[1].toLowerCase() !== 'csv') { if (ext === null || ext[1].toLowerCase() !== 'csv') {
this.value = ''; this.value = '';
MailPoet.Notice.error(MailPoetI18n.wrongFileFormat, { MailPoet.Notice.error(MailPoetI18n.wrongFileFormat, {
timeout: 3000, timeout: noticeTimeout,
}); });
} }
@ -194,7 +199,7 @@ define(
if (response.result === false) { if (response.result === false) {
MailPoet.Notice.hide(); MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors, { MailPoet.Notice.error(response.errors, {
timeout: 3000, timeout: noticeTimeout,
}); });
jQuery('.mailpoet_mailchimp-key-status') jQuery('.mailpoet_mailchimp-key-status')
.removeClass() .removeClass()
@ -219,7 +224,7 @@ define(
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
timeout: 3000, timeout: noticeTimeout,
} }
); );
}); });
@ -246,7 +251,7 @@ define(
else { else {
MailPoet.Notice.hide(); MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors, { MailPoet.Notice.error(response.errors, {
timeout: 3000, timeout: noticeTimeout,
}); });
} }
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
@ -254,7 +259,7 @@ define(
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + result.statusText.toLowerCase() + '.', { MailPoetI18n.serverError + result.statusText.toLowerCase() + '.', {
timeout: 3000, timeout: noticeTimeout,
} }
); );
}); });
@ -346,13 +351,13 @@ define(
error: function () { error: function () {
MailPoet.Notice.hide(); MailPoet.Notice.hide();
MailPoet.Notice.error(MailPoetI18n.dataProcessingError, { MailPoet.Notice.error(MailPoetI18n.dataProcessingError, {
timeout: 3000, timeout: noticeTimeout,
}); });
}, },
complete: function (CSV) { complete: function (CSV) {
for (var rowCount in CSV.data) { for (var rowCount in CSV.data) {
var rowData = CSV.data[rowCount].map(function (el) { var rowData = CSV.data[rowCount].map(function (el) {
return el.trim(); return filterXSS(el.trim());
}), }),
rowColumnCount = rowData.length; rowColumnCount = rowData.length;
// set the number of row elements based on the first non-empty row // set the number of row elements based on the first non-empty row
@ -430,7 +435,7 @@ define(
errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink); errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink);
errorNotice = errorNotice.replace('[/link]', '</a>'); errorNotice = errorNotice.replace('[/link]', '</a>');
MailPoet.Notice.error(errorNotice, { MailPoet.Notice.error(errorNotice, {
timeout: 3000, timeout: noticeTimeout,
}); });
} }
} }
@ -561,7 +566,7 @@ define(
if (!segmentSelectionNotice.length) { if (!segmentSelectionNotice.length) {
MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, { MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, {
static: true, static: true,
timeout: 3000, timeout: noticeTimeout,
scroll: true, scroll: true,
id: 'segmentSelection', id: 'segmentSelection',
hideClose: true hideClose: true
@ -640,7 +645,7 @@ define(
MailPoet.Modal.close(); MailPoet.Modal.close();
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.segmentCreateError + response.message + '.', { MailPoetI18n.segmentCreateError + response.message + '.', {
timeout: 3000, timeout: noticeTimeout,
} }
); );
} }
@ -649,7 +654,7 @@ define(
MailPoet.Modal.close(); MailPoet.Modal.close();
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
timeout: 3000 timeout: noticeTimeout
} }
); );
}); });
@ -856,7 +861,7 @@ define(
} }
else { else {
MailPoet.Notice.error(MailPoetI18n.customFieldCreateError, { MailPoet.Notice.error(MailPoetI18n.customFieldCreateError, {
timeout: 3000, timeout: noticeTimeout,
}); });
} }
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
@ -865,7 +870,7 @@ define(
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
timeout: 3000, timeout: noticeTimeout,
} }
); );
}); });
@ -930,7 +935,7 @@ define(
if (!jQuery('[data-id="notice_invalidEmail"]').length) { if (!jQuery('[data-id="notice_invalidEmail"]').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, { MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, {
static: true, static: true,
timeout: 3000, timeout: noticeTimeout,
scroll: true, scroll: true,
hideClose: true, hideClose: true,
id: 'invalidEmail' id: 'invalidEmail'
@ -1010,7 +1015,7 @@ define(
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) { if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidDate, { MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidDate, {
static: true, static: true,
timeout: 3000, timeout: noticeTimeout,
scroll: true, scroll: true,
hideClose: true, hideClose: true,
id: 'invalidDate' id: 'invalidDate'
@ -1050,64 +1055,98 @@ define(
} }
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);
var subscribers = {}; var columns = {},
queue = new jQuery.AsyncQueue(),
batchNumber = 0,
batchSize = 2000,
timestamp = Date.now() / 1000,
subscribers = [],
importResults = {
'created': 0,
'updated': 0,
'errors': [],
'segments': []
},
splitSubscribers = function (subscribers, size) {
return subscribers.reduce(function (res, item, index) {
if (index % size === 0) {
res.push([]);
}
res[res.length - 1].push(item);
return res;
}, []);
},
subscribers = splitSubscribers(importData.step1.subscribers, batchSize);
_.each(jQuery('select.mailpoet_subscribers_column_data_match'), _.each(jQuery('select.mailpoet_subscribers_column_data_match'),
function (column, index) { function (column, columnIndex) {
var columnId = jQuery(column).data('column-id'); var columnId = jQuery(column).data('column-id');
if (columnId === 'ignore') { if (columnId === 'ignore') {
return; return;
} }
subscribers[columnId] = []; columns[columnId] = columnIndex;
_.each(importData.step1.subscribers, function (subsciber) {
subscribers[columnId].push(
_.chain(subsciber)
.pick(index)
.toArray()
.flatten()
.value()
);
});
subscribers[columnId] = _.flatten(subscribers[columnId]);
}); });
MailPoet.Ajax.post({ _.each(subscribers, function () {
queue.add(function (queue) {
queue.pause();
MailPoet.Ajax
.post({
endpoint: 'ImportExport', endpoint: 'ImportExport',
action: 'processImport', action: 'processImport',
data: JSON.stringify({ data: JSON.stringify({
subscribers: subscribers, columns: columns,
subscribers: subscribers[batchNumber],
timestamp: timestamp,
segments: segmentSelectElement.val(), segments: segmentSelectElement.val(),
updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false
}) })
}).done(function (response) { })
MailPoet.Modal.loading(false); .done(function (response) {
if (response.result === false) { if (response.result === false) {
MailPoet.Notice.error(response.errors, { importResults.errors.push(response.errors);
timeout: 3000,
});
} else { } else {
mailpoetSegments = response.data.segments; importResults.created = response.data.created;
response.data.segments = _.map(segmentSelectElement.select2('data'), importResults.updated = response.data.updated;
importResults.segments = response.data.segments;
}
queue.run();
})
.error(function (error) {
importResults.errors.push(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
);
queue.run();
});
batchNumber++;
})
});
queue.run();
queue.onComplete(function () {
MailPoet.Modal.loading(false);
if (importResults.errors.length > 0 && !importResults.updated && !importResults.created) {
MailPoet.Notice.error(_.flatten(importResults.errors), {
timeout: noticeTimeout,
}
);
}
else {
mailpoetSegments = importResults.segments;
importResults.segments = _.map(segmentSelectElement.select2('data'),
function (data) { function (data) {
return data.name; return data.name;
}); });
importData.step2 = response.data; importData.step2 = importResults;
enableSegmentSelection(mailpoetSegments); enableSegmentSelection(mailpoetSegments);
router.navigate('step3', {trigger: true}); router.navigate('step3', {trigger: true});
} }
}).error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
timeout: 3000,
}
);
}); });
}); });
filterSubscribers(); filterSubscribers();
enableSegmentSelection(mailpoetSegments); enableSegmentSelection(mailpoetSegments);
}); });
router.on('route:step3', function () { router.on('route:step3', function () {
@ -1118,6 +1157,12 @@ define(
showCurrentStep(); showCurrentStep();
if (importData.step2.errors.length > 0) {
MailPoet.Notice.error(_.flatten(importData.step2.errors), {
timeout: noticeTimeout,
});
}
// display statistics // display statistics
var subscribersDataImportResultsTemplate = var subscribersDataImportResultsTemplate =
Handlebars Handlebars

View File

@ -208,6 +208,16 @@ const bulk_actions = [
); );
} }
}, },
{
name: 'sendConfirmationEmail',
label: 'Resend confirmation email',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d confirmation emails have been sent.'
.replace('%$1d', ~~response)
);
}
},
{ {
name: 'trash', name: 'trash',
label: 'Trash', label: 'Trash',
@ -231,6 +241,15 @@ const item_actions = [
]; ];
const SubscriberList = React.createClass({ const SubscriberList = React.createClass({
getSegmentFromId: function(segment_id) {
let result = false;
mailpoet_segments.map(function(segment) {
if (segment.id === segment_id) {
result = segment;
}
});
return result;
},
renderItem: function(subscriber, actions) { renderItem: function(subscriber, actions) {
let row_classes = classNames( let row_classes = classNames(
'manage-column', 'manage-column',
@ -255,11 +274,41 @@ const SubscriberList = React.createClass({
break; break;
} }
let segments = mailpoet_segments.filter(function(segment) { let segments = false;
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) { if (subscriber.subscriptions.length > 0) {
return segment.name; let subscribed_segments = [];
}).join(', '); let unsubscribed_segments = [];
subscriber.subscriptions.map((subscription) => {
const segment = this.getSegmentFromId(subscription.segment_id);
if(segment === false) return;
if (subscription.status === 'subscribed') {
subscribed_segments.push(segment.name);
} else if (subscription.status === 'unsubscribed') {
unsubscribed_segments.push(segment.name);
}
});
segments = (
<span>
<span className="mailpoet_segments_subscribed">
{ subscribed_segments.join(', ') }
{
(
subscribed_segments.length > 0
&& unsubscribed_segments.length > 0
) ? ' / ' : ''
}
</span>
<span
className="mailpoet_segments_unsubscribed"
title="Lists to which the subscriber was subscribed."
>
{ unsubscribed_segments.join(', ') }
</span>
</span>
);
}
let avatar = false; let avatar = false;
if(subscriber.avatar_url) { if(subscriber.avatar_url) {
@ -292,10 +341,10 @@ const SubscriberList = React.createClass({
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Subscribed on"> <td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr> <abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
</td> </td>
<td className="column-date" data-colname="Last modified on"> <td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr> <abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
</td> </td>
</div> </div>
); );
@ -313,6 +362,7 @@ const SubscriberList = React.createClass({
</h2> </h2>
<Listing <Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location } location={ this.props.location }
params={ this.props.params } params={ this.props.params }
endpoint="subscribers" endpoint="subscribers"

View File

@ -0,0 +1,79 @@
/*
* This file is part of the jquery plugin "asyncQueue".
*
* (c) Sebastien Roch <roch.sebastien@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
(function($){
$.AsyncQueue = function() {
var that = this,
queue = [],
failureFunc,
completeFunc,
paused = false,
lastCallbackData,
_run;
_run = function() {
var f = queue.shift();
if (f) {
f.apply(that, [that]);
if (paused === false) {
_run();
}
} else {
if(completeFunc){
completeFunc.apply(that);
}
}
}
this.onFailure = function(func) {
failureFunc = func;
}
this.onComplete = function(func) {
completeFunc = func;
}
this.add = function(func) {
queue.push(func);
return this;
}
this.storeData = function(dataObject) {
lastCallbackData = dataObject;
return this;
}
this.lastCallbackData = function () {
return lastCallbackData;
}
this.run = function() {
paused = false;
_run();
}
this.pause = function () {
paused = true;
return this;
}
this.failure = function() {
paused = true;
if (failureFunc) {
var args = [that];
for(i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
failureFunc.apply(that, args);
}
}
return this;
}
})(jQuery);

View File

@ -9,6 +9,7 @@ settings:
bootstrap: _bootstrap.php bootstrap: _bootstrap.php
colors: true colors: true
memory_limit: 1024M memory_limit: 1024M
log: true
extensions: extensions:
enabled: enabled:
- Codeception\Extension\RunFailed - Codeception\Extension\RunFailed

View File

@ -9,7 +9,7 @@
"j4mie/paris": "1.5.4", "j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4", "swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*", "phpseclib/phpseclib": "*",
"mtdowling/cron-expression": "^1.0", "mtdowling/cron-expression": "^1.1",
"nesbot/carbon": "^1.21", "nesbot/carbon": "^1.21",
"soundasleep/html2text": "^0.3.0" "soundasleep/html2text": "^0.3.0"
}, },

154
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"hash": "db2edea5fb720fcb8013ac470e924c19", "hash": "2bed8395d84740d7c0ae644a6c6216fd",
"content-hash": "85119d1ccd5193b6b08fda305a2427e7", "content-hash": "7b66e221814f3d5839ed4faabd2f50ad",
"packages": [ "packages": [
{ {
"name": "cerdic/css-tidy", "name": "cerdic/css-tidy",
@ -160,23 +160,23 @@
}, },
{ {
"name": "mtdowling/cron-expression", "name": "mtdowling/cron-expression",
"version": "v1.0.4", "version": "v1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/mtdowling/cron-expression.git", "url": "https://github.com/mtdowling/cron-expression.git",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412" "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/fd92e883195e5dfa77720b1868cf084b08be4412", "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412", "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.2" "php": ">=5.3.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "4.*" "phpunit/phpunit": "~4.0|~5.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -200,7 +200,7 @@
"cron", "cron",
"schedule" "schedule"
], ],
"time": "2015-01-11 23:07:46" "time": "2016-01-26 21:23:30"
}, },
{ {
"name": "nesbot/carbon", "name": "nesbot/carbon",
@ -547,16 +547,16 @@
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.0.1", "version": "v1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25" "reference": "1289d16209491b584839022f29257ad859b8532d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
"reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25", "reference": "1289d16209491b584839022f29257ad859b8532d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -568,7 +568,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0-dev" "dev-master": "1.1-dev"
} }
}, },
"autoload": { "autoload": {
@ -602,7 +602,7 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2015-11-20 09:19:13" "time": "2016-01-20 09:13:37"
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
@ -722,16 +722,16 @@
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v1.23.3", "version": "v1.24.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08" "reference": "3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ae53fc2c312fdee63773b75cb570304f85388b08", "url": "https://api.github.com/repos/twigphp/Twig/zipball/3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8",
"reference": "ae53fc2c312fdee63773b75cb570304f85388b08", "reference": "3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -744,7 +744,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.23-dev" "dev-master": "1.24-dev"
} }
}, },
"autoload": { "autoload": {
@ -779,22 +779,22 @@
"keywords": [ "keywords": [
"templating" "templating"
], ],
"time": "2016-01-11 14:02:19" "time": "2016-01-25 21:22:18"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "codeception/codeception", "name": "codeception/codeception",
"version": "2.1.5", "version": "2.1.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Codeception/Codeception.git", "url": "https://github.com/Codeception/Codeception.git",
"reference": "4ee562f421fd404139dd7b73ae46d075396a4baf" "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/4ee562f421fd404139dd7b73ae46d075396a4baf", "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89",
"reference": "4ee562f421fd404139dd7b73ae46d075396a4baf", "reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -861,7 +861,7 @@
"functional testing", "functional testing",
"unit testing" "unit testing"
], ],
"time": "2015-12-20 12:11:42" "time": "2016-02-09 22:27:48"
}, },
{ {
"name": "codeception/verify", "name": "codeception/verify",
@ -1328,22 +1328,24 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "v1.5.0", "version": "v1.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0", "phpdocumentor/reflection-docblock": "~2.0",
"sebastian/comparator": "~1.1" "sebastian/comparator": "~1.1",
"sebastian/recursion-context": "~1.0"
}, },
"require-dev": { "require-dev": {
"phpspec/phpspec": "~2.0" "phpspec/phpspec": "~2.0"
@ -1351,7 +1353,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.4.x-dev" "dev-master": "1.5.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -1384,7 +1386,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2015-08-13 10:07:40" "time": "2016-02-15 07:46:21"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -1628,16 +1630,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "4.8.21", "version": "4.8.23",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "ea76b17bced0500a28098626b84eda12dbcf119c" "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
"reference": "ea76b17bced0500a28098626b84eda12dbcf119c", "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1696,7 +1698,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2015-12-12 07:45:58" "time": "2016-02-11 14:56:33"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
@ -2227,16 +2229,16 @@
}, },
{ {
"name": "symfony/browser-kit", "name": "symfony/browser-kit",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/browser-kit.git", "url": "https://github.com/symfony/browser-kit.git",
"reference": "334a58c0def6dfcbe4bb57c6d2a8c06c6cc77679" "reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/334a58c0def6dfcbe4bb57c6d2a8c06c6cc77679", "url": "https://api.github.com/repos/symfony/browser-kit/zipball/dde849a0485b70a24b36f826ed3fb95b904d80c3",
"reference": "334a58c0def6dfcbe4bb57c6d2a8c06c6cc77679", "reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2280,7 +2282,7 @@
], ],
"description": "Symfony BrowserKit Component", "description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2015-12-26 13:39:53" "time": "2016-01-27 11:34:55"
}, },
{ {
"name": "symfony/config", "name": "symfony/config",
@ -2292,7 +2294,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/41ee6c70758f40fa1dbf90d019ae0a66c4a09e74", "url": "https://api.github.com/repos/symfony/config/zipball/ee4cdda66aff834c8125e8f2c15932461667c521",
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74", "reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74",
"shasum": "" "shasum": ""
}, },
@ -2394,16 +2396,16 @@
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
"reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3" "reference": "6605602690578496091ac20ec7a5cbd160d4dff4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/4613311fd46e146f506403ce2f8a0c71d402d2a3", "url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4",
"reference": "4613311fd46e146f506403ce2f8a0c71d402d2a3", "reference": "6605602690578496091ac20ec7a5cbd160d4dff4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2443,20 +2445,20 @@
], ],
"description": "Symfony CssSelector Component", "description": "Symfony CssSelector Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2015-12-05 17:45:07" "time": "2016-01-27 05:14:46"
}, },
{ {
"name": "symfony/dom-crawler", "name": "symfony/dom-crawler",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dom-crawler.git", "url": "https://github.com/symfony/dom-crawler.git",
"reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d" "reference": "b693a9650aa004576b593ff2e91ae749dc90123d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d",
"reference": "7c622b0c9fb8bdb146d6dfa86c5f91dcbfdbc11d", "reference": "b693a9650aa004576b593ff2e91ae749dc90123d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2499,7 +2501,7 @@
], ],
"description": "Symfony DomCrawler Component", "description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2015-12-26 13:42:31" "time": "2016-01-25 09:56:57"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
@ -2669,7 +2671,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/7fd5e4034cb8e215887136f5e176430bbf5ef085", "url": "https://api.github.com/repos/symfony/form/zipball/b629051c77a4f37c625651d03002760385df4575",
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085", "reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085",
"shasum": "" "shasum": ""
}, },
@ -2735,16 +2737,16 @@
}, },
{ {
"name": "symfony/intl", "name": "symfony/intl",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/intl.git", "url": "https://github.com/symfony/intl.git",
"reference": "9abd5cd590211c35cda87591f0dee856b7a16125" "reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/9abd5cd590211c35cda87591f0dee856b7a16125", "url": "https://api.github.com/repos/symfony/intl/zipball/7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc",
"reference": "9abd5cd590211c35cda87591f0dee856b7a16125", "reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2806,7 +2808,7 @@
"l10n", "l10n",
"localization" "localization"
], ],
"time": "2015-12-05 11:13:14" "time": "2016-01-27 05:14:46"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
@ -2864,16 +2866,16 @@
}, },
{ {
"name": "symfony/polyfill-intl-icu", "name": "symfony/polyfill-intl-icu",
"version": "v1.0.1", "version": "v1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git", "url": "https://github.com/symfony/polyfill-intl-icu.git",
"reference": "2deb44160e1c886241c06602b12b98779f728177" "reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/2deb44160e1c886241c06602b12b98779f728177", "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/66b0bb4abda229bc073eff6bbc8f2685bdaac165",
"reference": "2deb44160e1c886241c06602b12b98779f728177", "reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2883,7 +2885,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0-dev" "dev-master": "1.1-dev"
} }
}, },
"autoload": { "autoload": {
@ -2915,7 +2917,7 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2015-11-04 20:28:58" "time": "2016-01-20 09:13:37"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
@ -2927,7 +2929,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", "url": "https://api.github.com/repos/symfony/process/zipball/d9d21cfcc3e202ee34777d6da38897695d4d208d",
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", "reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac",
"shasum": "" "shasum": ""
}, },
@ -2968,16 +2970,16 @@
}, },
{ {
"name": "symfony/property-access", "name": "symfony/property-access",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/property-access.git", "url": "https://github.com/symfony/property-access.git",
"reference": "9bb9f79ade13196fadd0b98504117f117d8221ad" "reference": "95363dbabd606e404b6c75095669993bf7ddae4b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/9bb9f79ade13196fadd0b98504117f117d8221ad", "url": "https://api.github.com/repos/symfony/property-access/zipball/95363dbabd606e404b6c75095669993bf7ddae4b",
"reference": "9bb9f79ade13196fadd0b98504117f117d8221ad", "reference": "95363dbabd606e404b6c75095669993bf7ddae4b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3024,7 +3026,7 @@
"property path", "property path",
"reflection" "reflection"
], ],
"time": "2015-12-23 08:00:11" "time": "2016-01-03 15:35:16"
}, },
{ {
"name": "symfony/routing", "name": "symfony/routing",
@ -3183,16 +3185,16 @@
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.0.1", "version": "v3.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "3df409958a646dad2bc5046c3fb671ee24a1a691" "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/3df409958a646dad2bc5046c3fb671ee24a1a691", "url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a",
"reference": "3df409958a646dad2bc5046c3fb671ee24a1a691", "reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3228,7 +3230,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2015-12-26 13:39:53" "time": "2016-02-02 13:44:19"
}, },
{ {
"name": "twig/extensions", "name": "twig/extensions",

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,12 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use \MailPoet\Models\Setting; use \MailPoet\Models\Setting;
use \MailPoet\Util\Url;
class Changelog { class Changelog {
function __construct() {
}
function init() { function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX); $doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
@ -42,20 +46,7 @@ class Changelog {
// save version number // save version number
Setting::setValue('version', Env::$version); Setting::setValue('version', Env::$version);
global $wp; Url::redirectWithReferer($redirect_url);
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
} }
} }
} }

View File

@ -1,5 +1,7 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
use \MailPoet\Models\Setting; use \MailPoet\Models\Setting;
class Hooks { class Hooks {
@ -7,8 +9,21 @@ class Hooks {
} }
function init() { function init() {
$this->setupSubscribe();
$this->setupWPUsers();
$this->setupImageSize();
$this->setupListing();
$this->setupCronWorkers();
}
function setupSubscribe() {
$subscribe = Setting::getValue('subscribe', array());
// Subscribe in comments // Subscribe in comments
if((bool)Setting::getValue('subscribe.on_comment.enabled')) { if(
isset($subscribe['on_comment']['enabled'])
&&
(bool)$subscribe['on_comment']['enabled']
) {
if(is_user_logged_in()) { if(is_user_logged_in()) {
add_action( add_action(
'comment_form_field_comment', 'comment_form_field_comment',
@ -37,7 +52,11 @@ class Hooks {
} }
// Subscribe in registration form // Subscribe in registration form
if((bool)Setting::getValue('subscribe.on_register.enabled')) { if(
isset($subscribe['on_register']['enabled'])
&&
(bool)$subscribe['on_register']['enabled']
) {
if(is_multisite()) { if(is_multisite()) {
add_action( add_action(
'signup_extra_fields', 'signup_extra_fields',
@ -62,7 +81,9 @@ class Hooks {
); );
} }
} }
}
function setupWPUsers() {
// WP Users synchronization // WP Users synchronization
add_action( add_action(
'user_register', 'user_register',
@ -95,19 +116,50 @@ class Hooks {
'\MailPoet\Segments\WP::synchronizeUser', '\MailPoet\Segments\WP::synchronizeUser',
1 1
); );
}
function setupImageSize() {
add_filter( add_filter(
'image_size_names_choose', 'image_size_names_choose',
array( array($this, 'appendImageSize'),
$this, 10, 1
'appendImageSizes'
)
); );
} }
function appendImageSizes($sizes) { function appendImageSize($sizes) {
return array_merge($sizes, array( return array_merge($sizes, array(
'mailpoet_newsletter_max' => __('MailPoet Newsletter'), 'mailpoet_newsletter_max' => __('MailPoet Newsletter')
)); ));
} }
function setupListing() {
add_filter(
'set-screen-option',
array($this, 'setScreenOption'),
10, 3
);
}
function setScreenOption($status, $option, $value) {
if(preg_match('/^mailpoet_(.*)_per_page$/', $option)) {
return $value;
} else {
return $status;
}
}
function setupCronWorkers() {
add_action('mailpoet_cron_worker', array($this, 'runSchedulerWorker'), 10, 1);
add_action('mailpoet_cron_worker', array($this, 'runSendingQueueWorker'), 10, 1);
}
function runSchedulerWorker($timer) {
$scheduler = new Scheduler($timer);
$scheduler->process();
}
function runSendingQueueWorker($timer) {
$sending_queue = new SendingQueue($timer);
$sending_queue->process();
}
} }

View File

@ -4,8 +4,6 @@ namespace MailPoet\Config;
use MailPoet\Models; use MailPoet\Models;
use MailPoet\Cron\Supervisor; use MailPoet\Cron\Supervisor;
use MailPoet\Router; use MailPoet\Router;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -26,6 +24,7 @@ class Initializer {
register_activation_hook(Env::$file, array($this, 'runPopulator')); register_activation_hook(Env::$file, array($this, 'runPopulator'));
add_action('plugins_loaded', array($this, 'setup')); add_action('plugins_loaded', array($this, 'setup'));
add_action('init', array($this, 'onInit'));
add_action('widgets_init', array($this, 'setupWidget')); add_action('widgets_init', array($this, 'setupWidget'));
} }
@ -34,16 +33,14 @@ class Initializer {
$this->setupRenderer(); $this->setupRenderer();
$this->setupLocalizer(); $this->setupLocalizer();
$this->setupMenu(); $this->setupMenu();
$this->setupRouter();
$this->setupPermissions(); $this->setupPermissions();
$this->setupPublicAPI();
$this->setupAnalytics(); $this->setupAnalytics();
$this->setupChangelog(); $this->setupChangelog();
$this->runQueueSupervisor();
$this->setupShortcodes(); $this->setupShortcodes();
$this->setupHooks(); $this->setupHooks();
$this->setupPages();
$this->setupImages(); $this->setupImages();
$this->setupPublicAPI();
$this->runQueueSupervisor();
} catch(\Exception $e) { } catch(\Exception $e) {
// if anything goes wrong during init // if anything goes wrong during init
// automatically deactivate the plugin // automatically deactivate the plugin
@ -51,11 +48,21 @@ class Initializer {
} }
} }
function onInit() {
$this->setupRouter();
$this->setupPages();
$this->runQueueSupervisor();
}
function setupDB() { function setupDB() {
\ORM::configure(Env::$db_source_name); \ORM::configure(Env::$db_source_name);
\ORM::configure('username', Env::$db_username); \ORM::configure('username', Env::$db_username);
\ORM::configure('password', Env::$db_password); \ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG); \ORM::configure('logging', WP_DEBUG);
\ORM::configure('logger', function($query, $time) {
// error_log("\n".$query."\n");
});
\ORM::configure('driver_options', array( \ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"' \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"'
@ -143,8 +150,11 @@ class Initializer {
} }
function setupPages() { function setupPages() {
$pages = new Pages(); $pages = new \MailPoet\Settings\Pages();
$pages->init(); $pages->init();
$subscription_pages = new \MailPoet\Subscription\Pages();
$subscription_pages->init();
} }
function setupShortcodes() { function setupShortcodes() {

View File

@ -13,6 +13,7 @@ use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\BootStrapMenu; use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Util\DKIM; use MailPoet\Util\DKIM;
use MailPoet\Util\Permissions; use MailPoet\Util\Permissions;
use MailPoet\Listing;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -67,7 +68,7 @@ class Menu {
'forms' 'forms'
) )
); );
add_submenu_page( $subscribers_page = add_submenu_page(
'mailpoet', 'mailpoet',
__('Subscribers'), __('Subscribers'),
__('Subscribers'), __('Subscribers'),
@ -78,6 +79,17 @@ class Menu {
'subscribers' 'subscribers'
) )
); );
// add limit per page to screen options
add_action('load-'.$subscribers_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of subscribers per page',
'subscribers per page (screen options)'
),
'option' => 'mailpoet_subscribers_per_page'
));
});
add_submenu_page( add_submenu_page(
'mailpoet', 'mailpoet',
__('Segments'), __('Segments'),
@ -305,6 +317,14 @@ class Menu {
function subscribers() { function subscribers() {
$data = array(); $data = array();
// listing: limit per page
$listing_per_page = get_user_meta(
get_current_user_id(), 'mailpoet_subscribers_per_page', true
);
$data['per_page'] = (!empty($listing_per_page))
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
$data['segments'] = Segment::findArray(); $data['segments'] = Segment::findArray();
$data['custom_fields'] = array_map(function($field) { $data['custom_fields'] = array_map(function($field) {
@ -360,11 +380,12 @@ class Menu {
$data = array( $data = array(
'customFields' => $custom_fields, 'customFields' => $custom_fields,
'settings' => Setting::getAll(),
); );
wp_enqueue_media(); wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js')); wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css')); wp_enqueue_style('editor', includes_url('css/editor.css'));
echo $this->renderer->render('newsletter/form.html', $data); echo $this->renderer->render('newsletter/editor.html', $data);
} }
function import() { function import() {

View File

@ -221,6 +221,7 @@ class Migrator {
'count_processed mediumint(9) NOT NULL DEFAULT 0,', 'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,', 'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,', 'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,', 'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',

View File

@ -70,8 +70,13 @@ class Populator {
if($page === null) { if($page === null) {
$mailpoet_page_id = Pages::createMailPoetPage(); $mailpoet_page_id = Pages::createMailPoetPage();
Setting::setValue('subscription.page', $mailpoet_page_id); } else {
$mailpoet_page_id = (int)$page->ID;
} }
Setting::setValue('subscription.unsubscribe_page', $mailpoet_page_id);
Setting::setValue('subscription.manage_page', $mailpoet_page_id);
Setting::setValue('subscription.confirmation_page', $mailpoet_page_id);
} }
private function createDefaultSettings() { private function createDefaultSettings() {
@ -175,6 +180,14 @@ class Populator {
'name' => 'nthWeekDay', 'name' => 'nthWeekDay',
'newsletter_type' => 'notification', 'newsletter_type' => 'notification',
), ),
array(
'name' => 'schedule',
'newsletter_type' => 'notification',
),
array(
'name' => 'lastSentData',
'newsletter_type' => 'notification',
),
); );
} }

View File

@ -142,7 +142,7 @@ class BlankTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "footer", "type" => "footer",
"text" => "<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>", "text" => "<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "transparent" "backgroundColor" => "transparent"

View File

@ -46,7 +46,7 @@ class FranksRoastHouseTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "header", "type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[viewInBrowserUrl]\">View it in your browser</a>"), "text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "#ccc6c6" "backgroundColor" => "#ccc6c6"
@ -168,6 +168,7 @@ class FranksRoastHouseTemplate {
"fontColor" => "#ffffff", "fontColor" => "#ffffff",
"fontFamily" => "Arial", "fontFamily" => "Arial",
"fontSize" => "14px", "fontSize" => "14px",
"fontWeight" => "normal",
"textAlign" => "center" "textAlign" => "center"
) )
) )
@ -279,7 +280,7 @@ class FranksRoastHouseTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "footer", "type" => "footer",
"text" => __("<p><a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"), "text" => __("<p><a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "#a9a7a7" "backgroundColor" => "#a9a7a7"

View File

@ -242,7 +242,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "footer", "type" => "footer",
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"), "text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "transparent" "backgroundColor" => "transparent"
@ -325,6 +325,7 @@ class PostNotificationsBlankTemplate {
"fontColor" => "#ffffff", "fontColor" => "#ffffff",
"fontFamily" => "Arial", "fontFamily" => "Arial",
"fontSize" => "18px", "fontSize" => "18px",
"fontWeight" => "normal",
"textAlign" => "center" "textAlign" => "center"
) )
) )

View File

@ -46,7 +46,7 @@ class WelcomeTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "header", "type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[viewInBrowserUrl]\">View it in your browser</a>"), "text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "transparent" "backgroundColor" => "transparent"
@ -224,7 +224,7 @@ class WelcomeTemplate {
"blocks" => array( "blocks" => array(
array( array(
"type" => "footer", "type" => "footer",
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"), "text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array( "styles" => array(
"block" => array( "block" => array(
"backgroundColor" => "transparent" "backgroundColor" => "transparent"

View File

@ -68,4 +68,11 @@ class CronHelper {
// throw an error if all connection attempts failed // throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.')); throw new \Exception(__('Site URL is unreachable.'));
} }
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
} }

View File

@ -1,7 +1,9 @@
<?php <?php
namespace MailPoet\Cron; namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue; use MailPoet\Cron\Workers\SendingQueue;
use MailPoet\Models\Newsletter;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
@ -35,8 +37,7 @@ class Daemon {
} }
$this->abortIfStopped($daemon); $this->abortIfStopped($daemon);
try { try {
$sending_queue = new SendingQueue($this->timer); do_action('mailpoet_cron_worker', $this->timer);
$sending_queue->process();
} catch(\Exception $e) { } catch(\Exception $e) {
} }
$elapsed_time = microtime(true) - $this->timer; $elapsed_time = microtime(true) - $this->timer;

View File

@ -0,0 +1,27 @@
<?php
namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class Scheduler {
public $timer;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
}
function process() {
}
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= CronHelper::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
}

View File

@ -10,6 +10,7 @@ use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes; use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -27,6 +28,7 @@ class SendingQueue {
'processBulkSubscribers' : 'processBulkSubscribers' :
'processIndividualSubscriber'; 'processIndividualSubscriber';
$this->timer = ($timer) ? $timer : microtime(true); $this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
} }
function process() { function process() {
@ -102,7 +104,7 @@ class SendingQueue {
} }
$this->updateQueue($queue); $this->updateQueue($queue);
$this->checkSendingLimit(); $this->checkSendingLimit();
$this->checkExecutionTimer(); CronHelper::checkExecutionTimer($this->timer);
return $queue->subscribers; return $queue->subscribers;
} }
@ -129,7 +131,7 @@ class SendingQueue {
$this->updateNewsletterStatistics($newsletter_statistics); $this->updateNewsletterStatistics($newsletter_statistics);
} }
$this->updateQueue($queue); $this->updateQueue($queue);
$this->checkExecutionTimer(); CronHelper::checkExecutionTimer($this->timer);
} }
return $queue->subscribers; return $queue->subscribers;
} }
@ -201,7 +203,9 @@ class SendingQueue {
$queue->subscribers->failed $queue->subscribers->failed
) )
); );
$queue->subscribers->to_process = array_values($queue->subscribers->to_process); $queue->subscribers->to_process = array_values(
$queue->subscribers->to_process
);
$queue->count_processed = $queue->count_processed =
count($queue->subscribers->processed) + count($queue->subscribers->failed); count($queue->subscribers->processed) + count($queue->subscribers->failed);
$queue->count_to_process = count($queue->subscribers->to_process); $queue->count_to_process = count($queue->subscribers->to_process);
@ -259,11 +263,4 @@ class SendingQueue {
} }
return; return;
} }
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= CronHelper::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
} }

View File

@ -112,4 +112,17 @@ abstract class Base {
&& strlen(trim($block['params']['value'])) > 0) && strlen(trim($block['params']['value'])) > 0)
? esc_attr(trim($block['params']['value'])) : ''; ? esc_attr(trim($block['params']['value'])) : '';
} }
protected static function getInputModifiers($block = array()) {
$modifiers = array();
if(isset($block['params']['readonly'])) {
$modifiers[] = 'readonly';
}
if(isset($block['params']['disabled'])) {
$modifiers[] = 'disabled';
}
return join(' ', $modifiers);
}
} }

View File

@ -20,18 +20,22 @@ class Checkbox extends Base {
foreach($options as $option) { foreach($options as $option) {
$html .= '<label class="mailpoet_checkbox_label">'; $html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="hidden" name="'.$field_name.'" value="" />';
$html .= '<input type="checkbox" class="mailpoet_checkbox" '; $html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'" '; $html .= 'name="'.$field_name.'" ';
$html .= 'value="1" '; $html .= 'value="1" ';
$html .= (isset($option['is_checked']) && $option['is_checked']) $html .= (
? 'checked="checked"' : ''; (isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block))
) ? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />'.$option['value']; $html .= ' /> '.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -33,18 +33,37 @@ class Date extends Base {
// generate an array of selectors based on date format // generate an array of selectors based on date format
$date_selectors = explode('/', $date_format); $date_selectors = explode('/', $date_format);
// format value if present
$value = self::getFieldValue($block);
$day = null;
$month = null;
$year = null;
if($value) {
$day = (int)strftime('%d', $value);
$month = (int)strftime('%m', $value);
$year = (int)strftime('%Y', $value);
} else if(!empty($block['params']['is_default_today'])) {
$day = (int)strftime('%d');
$month = (int)strftime('%m');
$year = (int)strftime('%Y');
}
foreach($date_selectors as $date_selector) { foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') { if($date_selector === 'dd') {
$block['selected'] = $day;
$html .= '<select class="mailpoet_date_day" '; $html .= '<select class="mailpoet_date_day" ';
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">'; $html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block); $html .= static::getDays($block);
$html .= '</select>'; $html .= '</select>';
} else if($date_selector === 'mm') { } else if($date_selector === 'mm') {
$block['selected'] = $month;
$html .= '<select class="mailpoet_date_month" '; $html .= '<select class="mailpoet_date_month" ';
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">'; $html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block); $html .= static::getMonths($block);
$html .= '</select>'; $html .= '</select>';
} else if($date_selector === 'yyyy') { } else if($date_selector === 'yyyy') {
$block['selected'] = $year;
$html .= '<select class="mailpoet_date_year" '; $html .= '<select class="mailpoet_date_year" ';
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">'; $html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block); $html .= static::getYears($block);
@ -84,11 +103,6 @@ class Date extends Base {
'selected' => null 'selected' => null
); );
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%m');
}
// merge block with defaults // merge block with defaults
$block = array_merge($defaults, $block); $block = array_merge($defaults, $block);

View File

@ -25,14 +25,24 @@ class Radio extends Base {
$html .= 'name="'.$field_name.'" '; $html .= 'name="'.$field_name.'" ';
$html .= 'value="'.esc_attr($option['value']).'" '; if(is_array($option['value'])) {
$value = key($option['value']);
$label = reset($option['value']);
} else {
$value = $option['value'];
$label = $option['value'];
}
$html .= 'value="'.esc_attr($value).'" ';
$html .= (
(isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block) === $value)
) ? 'checked="checked"' : '';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' /> '.esc_attr($label);
$html .= ' />&nbsp;'.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -28,7 +28,7 @@ class Segment extends Base {
$html .= 'name="'.$field_name.'[]" '; $html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$option['id'].'" '.$is_checked.' '; $html .= 'value="'.$option['id'].'" '.$is_checked.' ';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />'.$option['name']; $html .= ' /> '.esc_attr($option['name']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -10,9 +10,7 @@ class Select extends Base {
$field_validation = static::getInputValidation($block); $field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">'; $html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block); $html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">'; $html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within']) if(isset($block['params']['label_within'])
@ -20,11 +18,28 @@ class Select extends Base {
$html .= '<option value="">'.static::getFieldLabel($block).'</option>'; $html .= '<option value="">'.static::getFieldLabel($block).'</option>';
} }
foreach($block['params']['values'] as $option) { $options = (!empty($block['params']['values'])
$is_selected = (isset($option['is_checked']) && $option['is_checked']) ? $block['params']['values']
? 'selected="selected"' : ''; : array()
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>'; );
$html .= $option['value'];
foreach($options as $option) {
$is_selected = (
(isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block) === $option['value'])
) ? 'selected="selected"' : '';
if(is_array($option['value'])) {
$value = key($option['value']);
$label = reset($option['value']);
} else {
$value = $option['value'];
$label = $option['value'];
}
$html .= '<option value="'.$value.'" '.$is_selected.'>';
$html .= esc_attr($label);
$html .= '</option>'; $html .= '</option>';
} }
$html .= '</select>'; $html .= '</select>';

View File

@ -27,6 +27,8 @@ class Text extends Base {
$html .= static::getInputValidation($block); $html .= static::getInputValidation($block);
$html .= static::getInputModifiers($block);
$html .= '/>'; $html .= '/>';
$html .= '</p>'; $html .= '</p>';

View File

@ -19,6 +19,8 @@ class Textarea extends Base {
$html .= static::getInputValidation($block); $html .= static::getInputValidation($block);
$html .= static::getInputModifiers($block);
$html .= '></textarea>'; $html .= '></textarea>';
$html .= '</p>'; $html .= '</p>';

View File

@ -39,8 +39,7 @@ class Renderer {
} }
} }
// private: rendering methods static function renderBlocks($blocks = array()) {
private static function renderBlocks($blocks = array()) {
$html = ''; $html = '';
foreach ($blocks as $key => $block) { foreach ($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n"; $html .= static::renderBlock($block)."\n";
@ -49,7 +48,7 @@ class Renderer {
return $html; return $html;
} }
private static function renderBlock($block = array()) { static function renderBlock($block = array()) {
$html = ''; $html = '';
switch($block['type']) { switch($block['type']) {
case 'html': case 'html':

View File

@ -22,19 +22,21 @@ class Export {
), site_url()); ), site_url());
// generate iframe // generate iframe
return '<iframe '. return join(' ', array(
'width="100%" '. '<iframe',
'scrolling="no" '. 'width="100%"',
'frameborder="0" '. 'scrolling="no"',
'src="'.$iframe_url.'" '. 'frameborder="0"',
'class="mailpoet_form_iframe" '. 'src="'.$iframe_url.'"',
'vspace="0" '. 'class="mailpoet_form_iframe"',
'tabindex="0" '. 'vspace="0"',
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"'. 'tabindex="0"',
'marginwidth="0" '. 'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"',
'marginheight="0" '. 'marginwidth="0"',
'hspace="0" '. 'marginheight="0"',
'allowtransparency="true"></iframe>'; 'hspace="0"',
'allowtransparency="true"></iframe>'
));
break; break;
case 'php': case 'php':

View File

@ -17,7 +17,7 @@ class Styles {
} }
/* labels */ /* labels */
.mailpoet_input_label, .mailpoet_text_label,
.mailpoet_textarea_label, .mailpoet_textarea_label,
.mailpoet_select_label, .mailpoet_select_label,
.mailpoet_radio_label, .mailpoet_radio_label,
@ -28,7 +28,7 @@ class Styles {
} }
/* inputs */ /* inputs */
.mailpoet_input, .mailpoet_text,
.mailpoet_textarea, .mailpoet_textarea,
.mailpoet_select, .mailpoet_select,
.mailpoet_date { .mailpoet_date {
@ -36,9 +36,7 @@ class Styles {
} }
.mailpoet_checkbox { .mailpoet_checkbox {
display:inline;
margin-right: 5px;
vertical-align:middle;
} }
.mailpoet_validate_success { .mailpoet_validate_success {

View File

@ -4,6 +4,8 @@ namespace MailPoet\Listing;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Handler { class Handler {
const DEFAULT_LIMIT_PER_PAGE = 20;
private $data = array(); private $data = array();
private $model = null; private $model = null;
@ -14,7 +16,10 @@ class Handler {
$this->data = array( $this->data = array(
// pagination // pagination
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0), 'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
'limit' => (isset($data['limit']) ? (int)$data['limit'] : 50), 'limit' => (isset($data['limit'])
? (int)$data['limit']
: self::DEFAULT_LIMIT_PER_PAGE
),
// searching // searching
'search' => (isset($data['search']) ? $data['search'] : null), 'search' => (isset($data['search']) ? $data['search'] : null),
// sorting // sorting
@ -84,11 +89,11 @@ class Handler {
$items = $this->model $items = $this->model
->offset($this->data['offset']) ->offset($this->data['offset'])
->limit($this->data['limit']) ->limit($this->data['limit'])
->findArray(); ->findMany();
return array( return array(
'count' => $count, 'count' => $count,
'filters' => $this->model->filter('filters'), 'filters' => $this->model->filter('filters', $this->data['group']),
'groups' => $this->model->filter('groups'), 'groups' => $this->model->filter('groups'),
'items' => $items 'items' => $items
); );

View File

@ -25,8 +25,7 @@ class PHPMail {
} }
function buildMailer() { function buildMailer() {
$transport = \Swift_SmtpTransport::newInstance(); $transport = \Swift_MailTransport::newInstance();
$transport->setTimeout(10);
return \Swift_Mailer::newInstance($transport); return \Swift_Mailer::newInstance($transport);
} }

View File

@ -70,19 +70,23 @@ class Model extends \Sudzy\ValidModel {
} }
static function bulkTrash($orm) { static function bulkTrash($orm) {
$models = $orm->findResultSet(); $model = get_called_class();
$models->set_expr('deleted_at', 'NOW()')->save(); return self::bulkAction($orm, function($ids) use($model) {
return $models->count(); self::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at`=NOW()',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($ids)), ',').')'
)),
$ids
);
});
} }
static function bulkDelete($orm) { static function bulkDelete($orm) {
$models = $orm->findMany(); $model = get_called_class();
$count = 0; return self::bulkAction($orm, function($ids) use($model) {
foreach($models as $model) { $model::whereIn('id', $ids)->deleteMany();
$model->delete(); });
$count++;
}
return $count;
} }
function restore() { function restore() {
@ -90,9 +94,37 @@ class Model extends \Sudzy\ValidModel {
} }
static function bulkRestore($orm) { static function bulkRestore($orm) {
$models = $orm->findResultSet(); $model = get_called_class();
$models->set_expr('deleted_at', 'NULL')->save(); return self::bulkAction($orm, function($ids) use($model) {
return $models->count(); self::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at`=NULL',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($ids)), ',').')'
)),
$ids
);
});
}
static function bulkAction($orm, $callback = false) {
$total = $orm->count();
if($total > 0) {
$models = $orm->select(static::$_table.'.id')
->offset(null)
->limit(null)
->findArray();
$ids = array_map(function($model) {
return (int)$model['id'];
}, $models);
if(is_callable($callback)) {
$callback($ids);
}
}
return $total;
} }
function duplicate($data = array()) { function duplicate($data = array()) {

View File

@ -52,6 +52,11 @@ class Newsletter extends Model {
); );
} }
function withSegments() {
$this->segments = $this->segments()->findArray();
return $this;
}
function options() { function options() {
return $this->has_many_through( return $this->has_many_through(
__NAMESPACE__.'\NewsletterOptionField', __NAMESPACE__.'\NewsletterOptionField',
@ -67,11 +72,21 @@ class Newsletter extends Model {
->findOne(); ->findOne();
} }
function withSendingQueue() {
$queue = $this->getQueue();
if($queue === false) {
$this->queue = false;
} else {
$this->queue = $queue->asArray();
}
return $this;
}
static function search($orm, $search = '') { static function search($orm, $search = '') {
return $orm->where_like('subject', '%' . $search . '%'); return $orm->where_like('subject', '%' . $search . '%');
} }
static function filters() { static function filters($orm, $group = 'all') {
$segments = Segment::orderByAsc('name')->findMany(); $segments = Segment::orderByAsc('name')->findMany();
$segment_list = array(); $segment_list = array();
$segment_list[] = array( $segment_list[] = array(
@ -80,7 +95,9 @@ class Newsletter extends Model {
); );
foreach($segments as $segment) { foreach($segments as $segment) {
$newsletters_count = $segment->newsletters()->count(); $newsletters_count = $segment->newsletters()
->filter('groupBy', $group)
->count();
if($newsletters_count > 0) { if($newsletters_count > 0) {
$segment_list[] = array( $segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $newsletters_count), 'label' => sprintf('%s (%d)', $segment->name, $newsletters_count),

View File

@ -35,16 +35,7 @@ class Segment extends Model {
__NAMESPACE__.'\SubscriberSegment', __NAMESPACE__.'\SubscriberSegment',
'segment_id', 'segment_id',
'subscriber_id' 'subscriber_id'
); )->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', 'subscribed');
}
function segmentFilters() {
return $this->has_many_through(
__NAMESPACE__.'\Filter',
__NAMESPACE__.'\SegmentFilter',
'segment_id',
'filter_id'
);
} }
function duplicate($data = array()) { function duplicate($data = array()) {
@ -76,6 +67,32 @@ class Segment extends Model {
->delete(); ->delete();
} }
function withSubscribersCount() {
$this->subscribers_count = SubscriberSegment::table_alias('relation')
->where('relation.segment_id', $this->id)
->join(
MP_SUBSCRIBERS_TABLE,
'subscribers.id = relation.subscriber_id',
'subscribers'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
'subscribed'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
'unsubscribed'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
'unconfirmed'
)
->findOne()
->asArray();
return $this;
}
static function getWPUsers() { static function getWPUsers() {
return self::where('type', 'wp_users')->findOne(); return self::where('type', 'wp_users')->findOne();
} }
@ -169,6 +186,6 @@ class Segment extends Model {
} }
static function getPublic() { static function getPublic() {
return self::getPublished()->where('type', 'default'); return self::getPublished()->where('type', 'default')->orderByAsc('name');
} }
} }

View File

@ -6,6 +6,13 @@ if (!defined('ABSPATH')) exit;
class Setting extends Model { class Setting extends Model {
public static $_table = MP_SETTINGS_TABLE; public static $_table = MP_SETTINGS_TABLE;
public static $defaults = null;
const DEFAULT_SENDING_METHOD_GROUP = 'website';
const DEFAULT_SENDING_METHOD = 'PHPMail';
const DEFAULT_SENDING_FREQUENCY_EMAILS = 25;
const DEFAULT_SENDING_FREQUENCY_INTERVAL = 15; // in minutes
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -14,8 +21,34 @@ class Setting extends Model {
)); ));
} }
public static function getDefaults() {
if(self::$defaults === null) {
self::loadDefaults();
}
return self::$defaults;
}
public static function loadDefaults() {
self::$defaults = array(
'mta_group' => self::DEFAULT_SENDING_METHOD_GROUP,
'mta' => array(
'method' => self::DEFAULT_SENDING_METHOD,
'frequency' => array(
'emails' => self::DEFAULT_SENDING_FREQUENCY_EMAILS,
'interval' => self::DEFAULT_SENDING_FREQUENCY_INTERVAL
)
),
'signup_confirmation' => array(
'enabled' => true,
'subject' => sprintf(__('Confirm your subscription to %1$s'), get_option('blogname')),
'body' => __("Hello!\n\nHurray! You've subscribed to our site.\nWe need you to activate your subscription to the list(s): [lists_to_confirm] by clicking the link below: \n\n[activation_link]Click here to confirm your subscription.[/activation_link]\n\nThank you,\n\nThe team!")
)
);
}
public static function getValue($key, $default = null) { public static function getValue($key, $default = null) {
$keys = explode('.', $key); $keys = explode('.', $key);
$defaults = self::getDefaults();
if(count($keys) === 1) { if(count($keys) === 1) {
$setting = Setting::where('name', $key)->findOne(); $setting = Setting::where('name', $key)->findOne();
@ -23,9 +56,14 @@ class Setting extends Model {
return $default; return $default;
} else { } else {
if(is_serialized($setting->value)) { if(is_serialized($setting->value)) {
return unserialize($setting->value); $value = unserialize($setting->value);
} else { } else {
return $setting->value; $value = $setting->value;
}
if(is_array($value) && array_key_exists($key, $defaults)) {
return array_replace_recursive($defaults[$key], $value);
} else {
return $value;
} }
} }
} else { } else {
@ -93,7 +131,7 @@ class Setting extends Model {
$settings[$setting->name] = $value; $settings[$setting->name] = $value;
} }
} }
return $settings; return array_replace_recursive(self::getDefaults(), $settings);
} }
public static function createOrUpdate($data = array()) { public static function createOrUpdate($data = array()) {

View File

@ -1,12 +1,19 @@
<?php <?php
namespace MailPoet\Models; namespace MailPoet\Models;
use MailPoet\Mailer\Mailer;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
use MailPoet\Subscription;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Subscriber extends Model { class Subscriber extends Model {
public static $_table = MP_SUBSCRIBERS_TABLE; public static $_table = MP_SUBSCRIBERS_TABLE;
const STATUS_SUBSCRIBED = 'subscribed';
const STATUS_UNSUBSCRIBED = 'unsubscribed';
const STATUS_UNCONFIRMED = 'unconfirmed';
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -16,13 +23,22 @@ class Subscriber extends Model {
)); ));
} }
static function findOne($id = null) {
if(is_int($id) || (string)(int)$id === $id) {
return parent::findOne($id);
} else {
return parent::where('email', $id)->findOne();
}
}
function segments() { function segments() {
return $this->has_many_through( return $this->has_many_through(
__NAMESPACE__.'\Segment', __NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment', __NAMESPACE__.'\SubscriberSegment',
'subscriber_id', 'subscriber_id',
'segment_id' 'segment_id'
); )
->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', self::STATUS_SUBSCRIBED);
} }
function delete() { function delete() {
@ -33,8 +49,17 @@ class Subscriber extends Model {
} }
function addToSegments(array $segment_ids = array()) { function addToSegments(array $segment_ids = array()) {
$wp_users_segment = Segment::getWPUsers();
if($wp_users_segment !== false) {
// delete all relations to segments except WP users
SubscriberSegment::where('subscriber_id', $this->id)
->whereNotEqual('segment_id', $wp_users_segment->id)
->deleteMany();
} else {
// delete all relations to segments // delete all relations to segments
SubscriberSegment::where('subscriber_id', $this->id)->deleteMany(); SubscriberSegment::where('subscriber_id', $this->id)->deleteMany();
}
if(!empty($segment_ids)) { if(!empty($segment_ids)) {
$segments = Segment::whereIn('id', $segment_ids)->findMany(); $segments = Segment::whereIn('id', $segment_ids)->findMany();
@ -48,9 +73,79 @@ class Subscriber extends Model {
} }
function sendConfirmationEmail() { function sendConfirmationEmail() {
$this->set('status', 'unconfirmed'); if($this->status === self::STATUS_UNCONFIRMED) {
$signup_confirmation = Setting::getValue('signup_confirmation');
// TODO $segments = $this->segments()->findMany();
$segment_names = array_map(function($segment) {
return $segment->name;
}, $segments);
$body = nl2br($signup_confirmation['body']);
// replace list of segments shortcode
$body = str_replace(
'[lists_to_confirm]',
'<strong>'.join(', ', $segment_names).'</strong>',
$body
);
// replace activation link
$body = str_replace(
array(
'[activation_link]',
'[/activation_link]'
),
array(
'<a href="'.esc_attr(Subscription\Url::getConfirmationUrl($this)).'">',
'</a>'
),
$body
);
// build email data
$email = array(
'subject' => $signup_confirmation['subject'],
'body' => array(
'html' => $body,
'text' => $body
)
);
// convert subscriber to array
$subscriber = $this->asArray();
// set from
$from = (
!empty($signup_confirmation['from'])
&& !empty($signup_confirmation['from']['email'])
) ? $signup_confirmation['from']
: false;
// set reply to
$reply_to = (
!empty($signup_confirmation['reply_to'])
&& !empty($signup_confirmation['reply_to']['email'])
) ? $signup_confirmation['reply_to']
: false;
// send email
try {
$mailer = new Mailer(false, $from, $reply_to);
return $mailer->send($email, $subscriber);
} catch(\Exception $e) {
$this->setError($e->getMessage());
return false;
}
}
return false;
}
static function generateToken($email = null) {
if($email !== null) {
return md5(AUTH_KEY.$email);
}
return false;
} }
static function subscribe($subscriber_data = array(), $segment_ids = array()) { static function subscribe($subscriber_data = array(), $segment_ids = array()) {
@ -59,23 +154,27 @@ class Subscriber extends Model {
} }
$subscriber = self::createOrUpdate($subscriber_data); $subscriber = self::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
if($errors === false && $subscriber->id > 0) {
$subscriber = self::findOne($subscriber->id);
if($subscriber !== false && $subscriber->id() > 0) {
// restore deleted subscriber // restore deleted subscriber
if($subscriber->deleted_at !== NULL) { if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL'); $subscriber->setExpr('deleted_at', 'NULL');
} }
if((bool)Setting::getValue('signup_confirmation.enabled')) { if((bool)Setting::getValue('signup_confirmation.enabled')) {
if($subscriber->status !== 'subscribed') { if($subscriber->status !== self::STATUS_SUBSCRIBED) {
$subscriber->sendConfirmationEmail(); $subscriber->sendConfirmationEmail();
} }
} else { } else {
$subscriber->set('status', 'subscribed'); $subscriber->set('status', self::STATUS_SUBSCRIBED);
} }
if($subscriber->save()) { if($subscriber->save()) {
$subscriber->addToSegments($segment_ids); $subscriber->addToSegments($segment_ids);
Scheduler::welcomeForSegmentSubscription($subscriber->id, $segment_ids);
} }
} }
@ -93,7 +192,7 @@ class Subscriber extends Model {
); );
} }
static function filters() { static function filters($orm, $group = 'all') {
$segments = Segment::orderByAsc('name')->findMany(); $segments = Segment::orderByAsc('name')->findMany();
$segment_list = array(); $segment_list = array();
$segment_list[] = array( $segment_list[] = array(
@ -110,8 +209,9 @@ class Subscriber extends Model {
foreach($segments as $segment) { foreach($segments as $segment) {
$subscribers_count = $segment->subscribers() $subscribers_count = $segment->subscribers()
->whereNull('deleted_at') ->filter('groupBy', $group)
->count(); ->count();
$segment_list[] = array( $segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count), 'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
'value' => $segment->id() 'value' => $segment->id()
@ -152,19 +252,19 @@ class Subscriber extends Model {
'count' => self::getPublished()->count() 'count' => self::getPublished()->count()
), ),
array( array(
'name' => 'subscribed', 'name' => self::STATUS_SUBSCRIBED,
'label' => __('Subscribed'), 'label' => __('Subscribed'),
'count' => self::filter('subscribed')->count() 'count' => self::filter(self::STATUS_SUBSCRIBED)->count()
), ),
array( array(
'name' => 'unconfirmed', 'name' => self::STATUS_UNCONFIRMED,
'label' => __('Unconfirmed'), 'label' => __('Unconfirmed'),
'count' => self::filter('unconfirmed')->count() 'count' => self::filter(self::STATUS_UNCONFIRMED)->count()
), ),
array( array(
'name' => 'unsubscribed', 'name' => self::STATUS_UNSUBSCRIBED,
'label' => __('Unsubscribed'), 'label' => __('Unsubscribed'),
'count' => self::filter('unsubscribed')->count() 'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count()
), ),
array( array(
'name' => 'trash', 'name' => 'trash',
@ -238,6 +338,9 @@ class Subscriber extends Model {
static function createOrUpdate($data = array()) { static function createOrUpdate($data = array()) {
$subscriber = false; $subscriber = false;
if(is_array($data) && !empty($data)) {
$data = stripslashes_deep($data);
}
if(isset($data['id']) && (int)$data['id'] > 0) { if(isset($data['id']) && (int)$data['id'] > 0) {
$subscriber = self::findOne((int)$data['id']); $subscriber = self::findOne((int)$data['id']);
@ -252,9 +355,8 @@ class Subscriber extends Model {
} }
// segments // segments
$segment_ids = array(); $segment_ids = false;
if(array_key_exists('segments', $data)) {
if(isset($data['segments'])) {
$segment_ids = (array)$data['segments']; $segment_ids = (array)$data['segments'];
unset($data['segments']); unset($data['segments']);
} }
@ -264,16 +366,25 @@ class Subscriber extends Model {
foreach($data as $key => $value) { foreach($data as $key => $value) {
if(strpos($key, 'cf_') === 0) { if(strpos($key, 'cf_') === 0) {
if(is_array($value)) {
$value = array_filter($value);
$value = reset($value);
}
$custom_fields[(int)substr($key, 3)] = $value; $custom_fields[(int)substr($key, 3)] = $value;
unset($data[$key]); unset($data[$key]);
} }
} }
$old_status = false;
$new_status = false;
if($subscriber === false) { if($subscriber === false) {
$subscriber = self::create(); $subscriber = self::create();
$subscriber->hydrate($data); $subscriber->hydrate($data);
} else { } else {
$old_status = $subscriber->status;
$subscriber->set($data); $subscriber->set($data);
$new_status = $subscriber->status;
} }
if($subscriber->save()) { if($subscriber->save()) {
@ -282,7 +393,20 @@ class Subscriber extends Model {
$subscriber->setCustomField($custom_field_id, $value); $subscriber->setCustomField($custom_field_id, $value);
} }
} }
$subscriber->addToSegments($segment_ids);
// check for status change
if(
($old_status === self::STATUS_SUBSCRIBED)
&&
($new_status === self::STATUS_UNSUBSCRIBED)
) {
// make sure we unsubscribe the user from all lists
SubscriberSegment::setSubscriptions($subscriber, array());
} else {
if($segment_ids !== false) {
SubscriberSegment::setSubscriptions($subscriber, $segment_ids);
}
}
} }
return $subscriber; return $subscriber;
} }
@ -304,6 +428,17 @@ class Subscriber extends Model {
return $this; return $this;
} }
function withSegments() {
$this->segments = $this->segments()->findArray();
return $this;
}
function withSubscriptions() {
$this->subscriptions = SubscriberSegment::where('subscriber_id', $this->id())
->findArray();
return $this;
}
function getCustomField($custom_field_id, $default = null) { function getCustomField($custom_field_id, $default = null) {
$custom_field = SubscriberCustomField::select('value') $custom_field = SubscriberCustomField::select('value')
->where('custom_field_id', $custom_field_id) ->where('custom_field_id', $custom_field_id)
@ -391,27 +526,28 @@ class Subscriber extends Model {
static function bulkConfirmUnconfirmed($orm) { static function bulkConfirmUnconfirmed($orm) {
$subscribers = $orm->findResultSet(); $subscribers = $orm->findResultSet();
$subscribers->set('status', 'subscribed')->save(); $subscribers->set('status', self::STATUS_SUBSCRIBED)->save();
return $subscribers->count(); return $subscribers->count();
} }
static function bulkResendConfirmationEmail($orm) { static function bulkSendConfirmationEmail($orm) {
$subscribers = $orm $subscribers = $orm
->where('status', 'unconfirmed') ->where('status', self::STATUS_UNCONFIRMED)
->findResultSet(); ->findMany();
$emails_sent = 0;
if(!empty($subscribers)) { if(!empty($subscribers)) {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
$subscriber->sendConfirmationEmail(); if($subscriber->sendConfirmationEmail()) {
$emails_sent++;
} }
}
return $subscribers->count(); return $emails_sent;
} }
return false; return false;
} }
static function bulkAddToList($orm, $data = array()) { static function bulkAddToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0); $segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id); $segment = Segment::findOne($segment_id);
@ -435,22 +571,31 @@ class Subscriber extends Model {
return false; return false;
} }
static function bulkDelete($orm) {
return parent::bulkAction($orm, function($ids) {
// delete subscribers
Subscriber::whereIn('id', $ids)->deleteMany();
// delete subscribers' relations to segments
SubscriberSegment::whereIn('subscriber_id', $ids)->deleteMany();
});
}
static function subscribed($orm) { static function subscribed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'subscribed'); ->where('status', self::STATUS_SUBSCRIBED);
} }
static function unsubscribed($orm) { static function unsubscribed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'unsubscribed'); ->where('status', self::STATUS_UNSUBSCRIBED);
} }
static function unconfirmed($orm) { static function unconfirmed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'unconfirmed'); ->where('status', self::STATUS_UNCONFIRMED);
} }
static function withoutSegments($orm) { static function withoutSegments($orm) {
@ -480,7 +625,7 @@ class Subscriber extends Model {
); );
} }
static function updateMultiple($columns, $subscribers, $currentTime = false) { static function updateMultiple($columns, $subscribers, $updated_at = false) {
$ignoreColumnsOnUpdate = array( $ignoreColumnsOnUpdate = array(
'email', 'email',
'created_at' 'created_at'
@ -520,7 +665,7 @@ class Subscriber extends Model {
return self::rawExecute( return self::rawExecute(
'UPDATE `' . self::$_table . '` ' . 'UPDATE `' . self::$_table . '` ' .
'SET ' . implode(', ', $sql('statement')) . ' '. 'SET ' . implode(', ', $sql('statement')) . ' '.
(($currentTime) ? ', updated_at = "' . $currentTime . '" ' : '') . (($updated_at) ? ', updated_at = "' . $updated_at . '" ' : '') .
'WHERE email IN ' . 'WHERE email IN ' .
'(' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')', '(' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')',
array_merge( array_merge(

View File

@ -12,29 +12,55 @@ class SubscriberSegment extends Model {
parent::__construct(); parent::__construct();
} }
static function filterWithCustomFields($orm) { static function setSubscriptions($subscriber, $segment_ids = array()) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*'); if($subscriber->id > 0) {
$customFields = CustomField::findArray(); // unsubscribe from current subscriptions
foreach ($customFields as $customField) { SubscriberSegment::where('subscriber_id', $subscriber->id)
$orm = $orm->select_expr( ->findResultSet()
'CASE WHEN ' . ->set('status', Subscriber::STATUS_UNSUBSCRIBED)
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' . ->save();
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
// subscribe to segments
foreach($segment_ids as $segment_id) {
if((int)$segment_id > 0) {
self::createOrUpdate(array(
'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id,
'status' => Subscriber::STATUS_SUBSCRIBED
));
} }
$orm = $orm }
->left_outer_join( }
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=', return $subscriber;
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->left_outer_join(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
return $orm;
} }
static function subscribed($orm) { static function subscribed($orm) {
return $orm->where('status', 'subscribed'); return $orm->where('status', Subscriber::STATUS_SUBSCRIBED);
}
static function createOrUpdate($data = array()) {
$subscription = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$subscription = self::findOne((int)$data['id']);
}
if(isset($data['subscriber_id']) && isset($data['segment_id'])) {
$subscription = self::where('subscriber_id', (int)$data['subscriber_id'])
->where('segment_id', (int)$data['segment_id'])
->findOne();
}
if($subscription === false) {
$subscription = self::create();
$subscription->hydrate($data);
} else {
unset($data['id']);
$subscription->set($data);
}
return $subscription->save();
} }
static function createMultiple($segmnets, $subscribers) { static function createMultiple($segmnets, $subscribers) {

View File

@ -22,23 +22,35 @@ class PostTransformer {
$content = $content_manager->filterContent($content); $content = $content_manager->filterContent($content);
$structure_transformer = new StructureTransformer(); $structure_transformer = new StructureTransformer();
$structure = $structure_transformer->transform($content, $this->args['imageFullWidth'] === 'true'); $structure = $structure_transformer->transform($content, $this->args['imageFullWidth'] === true);
if($this->args['featuredImagePosition'] === 'aboveTitle') {
$structure = $this->appendPostTitle($post, $structure);
$structure = $this->appendFeaturedImage( $structure = $this->appendFeaturedImage(
$post, $post,
$this->args['displayType'], $this->args['displayType'],
$this->args['imageFullWidth'] === 'true', $this->args['imageFullWidth'] === 'true',
$structure $structure
); );
} else {
if($this->args['featuredImagePosition'] === 'belowTitle') {
$structure = $this->appendFeaturedImage(
$post,
$this->args['displayType'],
$this->args['imageFullWidth'] === 'true',
$structure
);
}
$structure = $this->appendPostTitle($post, $structure); $structure = $this->appendPostTitle($post, $structure);
}
$structure = $this->appendReadMore($post->ID, $structure); $structure = $this->appendReadMore($post->ID, $structure);
return $structure; return $structure;
} }
private function appendFeaturedImage($post, $display_type, $image_full_width, $structure) { private function appendFeaturedImage($post, $display_type, $image_full_width, $structure) {
if ($display_type === 'full') { if($display_type !== 'excerpt') {
// No featured images for full posts // Append featured images only on excerpts
return $structure; return $structure;
} }
@ -96,25 +108,12 @@ class PostTransformer {
private function appendPostTitle($post, $structure) { private function appendPostTitle($post, $structure) {
$title = $this->getPostTitle($post); $title = $this->getPostTitle($post);
if ($this->args['titlePosition'] === 'inTextBlock') { // Append title always at the top of the post structure
// Attach title to the first text block // Reuse an existing text block if needed
$text_block_index = null;
foreach ($structure as $index => $block) {
if ($block['type'] === 'text') {
$text_block_index = $index;
break;
}
}
if ($text_block_index === null) { if(count($structure) > 0 && $structure[0]['type'] === 'text') {
$structure[] = array( $structure[0]['text'] = $title . $structure[0]['text'];
'type' => 'text',
'text' => $title,
);
} else { } else {
$structure[$text_block_index]['text'] = $title . $structure[$text_block_index]['text'];
}
} elseif ($this->args['titlePosition'] === 'aboveBlock') {
array_unshift( array_unshift(
$structure, $structure,
array( array(
@ -133,14 +132,21 @@ class PostTransformer {
$button['url'] = get_permalink($post_id); $button['url'] = get_permalink($post_id);
$structure[] = $button; $structure[] = $button;
} else { } else {
$structure[] = array( $total_blocks = count($structure);
'type' => 'text', $read_more_text = sprintf(
'text' => sprintf( '<p><a href="%s">%s</a></p>',
'<a href="%s">%s</a>',
get_permalink($post_id), get_permalink($post_id),
$this->args['readMoreText'] $this->args['readMoreText']
),
); );
if($structure[$total_blocks - 1]['type'] === 'text') {
$structure[$total_blocks - 1]['text'] .= $read_more_text;
} else {
$structure[] = array(
'type' => 'text',
'text' => $read_more_text,
);
}
} }
return $structure; return $structure;

View File

@ -1,10 +1,12 @@
<?php <?php
namespace MailPoet\Newsletter\Renderer\Blocks; namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Button { class Button {
static function render($element) { static function render($element, $column_count) {
$element['styles']['block']['width'] = self::calculateWidth($element, $column_count);
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded" valign="top"> <td class="mailpoet_padded" valign="top">
@ -37,4 +39,13 @@ class Button {
</tr>'; </tr>';
return $template; return $template;
} }
static function calculateWidth($element, $column_count) {
$column_width = ColumnsHelper::columnWidth($column_count);
$column_width = $column_width - (StylesHelper::$padding_width * 2);
$column_width = ((int) $element['styles']['block']['width'] > $column_width) ?
$column_width . 'px' :
$element['styles']['block']['width'];
return $column_width;
}
} }

View File

@ -19,7 +19,7 @@ class Footer {
} }
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded_header_footer mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>

View File

@ -19,7 +19,7 @@ class Header {
} }
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded_header_footer mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>

View File

@ -5,15 +5,21 @@ use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Image { class Image {
static function render($element, $columnCount) { static function render($element, $column_count) {
$element['width'] = (int) $element['width']; $element['width'] = (int) $element['width'];
$element['height'] = (int) $element['height']; $element['height'] = (int) $element['height'];
$element = self::adjustImageDimensions($element, $columnCount); $element = self::adjustImageDimensions($element, $column_count);
$image_template = '
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/>
';
if(!empty($element['link'])) {
$image_template = '<a href="' . $element['link'] . '">' . $image_template . '</a>';
}
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded' : '') . '" align="center" valign="top"> <td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded' : '') . '" align="center" valign="top">
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '" ' . $image_template . '
width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/>
</td> </td>
</tr>'; </tr>';
return $template; return $template;

View File

@ -3,9 +3,13 @@ namespace MailPoet\Newsletter\Renderer\Blocks;
class Spacer { class Spacer {
static function render($element) { static function render($element) {
$height = (int) $element['styles']['block']['height'];
$background_color = $element['styles']['block']['backgroundColor'];
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_spacer" height="' . (int) $element['styles']['block']['height'] . '" valign="top"></td> <td class="mailpoet_spacer" ' .
(($background_color !== 'transparent') ? 'bgcolor="' . $background_color . '" ' : ' ') .
'height="' . $height . '" valign="top"></td>
</tr>'; </tr>';
return $template; return $template;
} }

View File

@ -11,6 +11,7 @@ class Renderer {
$this->getOneColumnTemplate($styles, $class) : $this->getOneColumnTemplate($styles, $class) :
$this->getMultipleColumnsTemplate($styles, $width, $alignment, $class); $this->getMultipleColumnsTemplate($styles, $width, $alignment, $class);
$result = array_map(function($content) use ($template) { $result = array_map(function($content) use ($template) {
$content = self::removePaddingFromLastElement($content);
return $template['content_start'] . $content . $template['content_end']; return $template['content_start'] . $content . $template['content_end'];
}, $columns_data); }, $columns_data);
$result = implode('', $result); $result = implode('', $result);
@ -75,4 +76,8 @@ class Renderer {
</tr>'; </tr>';
return $template; return $template;
} }
function removePaddingFromLastElement($element) {
return preg_replace('/mailpoet_padded(?!.*mailpoet_padded)/ism', '', $element);
}
} }

View File

@ -45,7 +45,10 @@ class Renderer {
function renderBody($content) { function renderBody($content) {
$content = array_map(function($content_block) { $content = array_map(function($content_block) {
$column_count = count($content_block['blocks']); $column_count = count($content_block['blocks']);
$column_data = $this->blocks_renderer->render($content_block, $column_count); $column_data = $this->blocks_renderer->render(
$content_block,
$column_count
);
return $this->columns_renderer->render( return $this->columns_renderer->render(
$content_block['styles'], $content_block['styles'],
$column_count, $column_count,

View File

@ -11,6 +11,7 @@ class StylesHelper {
'textDecoration' => 'text-decoration', 'textDecoration' => 'text-decoration',
'textAlign' => 'text-align', 'textAlign' => 'text-align',
'fontSize' => 'font-size', 'fontSize' => 'font-size',
'fontWeight' => 'font-weight',
'borderWidth' => 'border-width', 'borderWidth' => 'border-width',
'borderStyle' => 'border-style', 'borderStyle' => 'border-style',
'borderColor' => 'border-color', 'borderColor' => 'border-color',

View File

@ -40,7 +40,7 @@
padding-right: 20px; padding-right: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
.mailpoet_padded_header_footer { .mailpoet_header_footer_padded {
padding: 10px 20px; padding: 10px 20px;
} }
@media screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) { @media screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) {

View File

@ -0,0 +1,114 @@
<?php
namespace MailPoet\Newsletter\Scheduler;
use Carbon\Carbon;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\SendingQueue;
class Scheduler {
const seconds_in_hour = 3600;
const last_weekday_format = 'L';
static function postNotification($newsletter_id) {
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($newsletter_id)
->asArray();
$interval_type = $newsletter['intervalType'];
$hour = (int) $newsletter['timeOfDay'] / self::seconds_in_hour;
$week_day = $newsletter['weekDay'];
$month_day = $newsletter['monthDay'];
$nth_week_day = ($newsletter['nthWeekDay'] === self::last_weekday_format) ?
$newsletter['nthWeekDay'] :
'#' . $newsletter['nthWeekDay'];
switch($interval_type) {
case 'immediately':
$cron = '* * * * *';
break;
case 'immediate': //daily
$cron = sprintf('0 %s * * *', $hour);
break;
case 'weekly':
$cron = sprintf('0 %s * * %s', $hour, $week_day);
break;
case 'monthly':
$cron = sprintf('0 %s %s * *', $hour, $month_day);
break;
case 'nthWeekDay':
$cron = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day);
break;
}
$option_field = NewsletterOptionField::where('name', 'schedule')
->findOne()
->asArray();
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter['id'];
$relation->option_field_id = $option_field['id'];
$relation->value = $cron;
$relation->save();
}
static function welcomeForSegmentSubscription($subscriber_id, array $segments) {
$newsletters = self::getWelcomeNewsletters();
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'segment' &&
in_array($newsletter['segment'], $segments)
) {
self::createSendingQueueEntry($newsletter, $subscriber_id);
}
}
}
static function welcomeForNewWPUser($subscriber_id, array $wp_user) {
$newsletters = self::getWelcomeNewsletters();
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'user' &&
in_array($newsletter['role'], $wp_user['roles'])
) {
self::createSendingQueueEntry($newsletter, $subscriber_id);
}
}
}
private static function getWelcomeNewsletters() {
return Newsletter::where('type', 'welcome')
->filter('filterWithOptions')
->findArray();
}
private static function createSendingQueueEntry($newsletter, $subscriber_id) {
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter['id'];
$queue->subscribers = serialize(
array(
'to_process' => array($subscriber_id)
)
);
$queue->count_total = $queue->count_to_process = 1;
$after_time_type = $newsletter['afterTimeType'];
$after_time_number = $newsletter['afterTimeNumber'];
$scheduled_at = null;
switch($after_time_type) {
case 'hours':
$scheduled_at = Carbon::now()
->addHours($after_time_number);
break;
case 'days':
$scheduled_at = Carbon::now()
->addDays($after_time_number);
break;
case 'weeks':
$scheduled_at = Carbon::now()
->addWeeks($after_time_number);
break;
}
if($scheduled_at) {
$queue->status = 'scheduled';
$queue->scheduled_at = $scheduled_at;
}
$queue->save();
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Link {
/*
{
text: '<%= __('Unsubscribe link') %>',
shortcode: 'global:unsubscribe',
},
{
text: '<%= __('Edit subscription page link') %>',
shortcode: 'global:manage',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'global:browser',
}
*/
static function process($action) {
// TODO: implement
$actions = array(
'unsubscribe' => '',
'manage' => '',
'browser' => ''
);
return (isset($actions[$action])) ? $actions[$action] : false;
}
}

View File

@ -18,6 +18,14 @@ class Newsletter {
{ {
text: '<%= __('Issue number') %>', text: '<%= __('Issue number') %>',
shortcode: 'newsletter:number', shortcode: 'newsletter:number',
},
{
text: '<%= __('Issue number') %>',
shortcode: 'newsletter:number',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
} }
*/ */
static function process($action, $default_value = false, $newsletter) { static function process($action, $default_value = false, $newsletter) {
@ -27,17 +35,34 @@ class Newsletter {
switch($action) { switch($action) {
case 'subject': case 'subject':
return ($newsletter) ? $newsletter['subject'] : false; return ($newsletter) ? $newsletter['subject'] : false;
break;
case 'total': case 'total':
$posts = wp_count_posts(); $posts = wp_count_posts();
return $posts->publish; return $posts->publish;
break;
case 'post_title': case 'post_title':
$post = wp_get_recent_posts(array('numberposts' => 1)); $post = wp_get_recent_posts(array('numberposts' => 1));
return (isset($post[0])) ? $post[0]['post_title'] : false; return (isset($post[0])) ? $post[0]['post_title'] : false;
break;
case 'number': case 'number':
// TODO: implement // TODO: implement
return; return 1;
break;
case 'view_in_browser':
return '<a href="#TODO">'.__('View in your browser').'</a>';
break;
case 'view_in_browser_url':
return '#TODO';
break;
default: default:
return false; return false;
break;
} }
} }
} }

View File

@ -0,0 +1,48 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Subscription {
/*
{
text: '<%= __('Unsubscribe') %>',-
shortcode: 'subscription:unsubscribe',
},
{
text: '<%= __('Manage subscriptions') %>',
shortcode: 'subscription:manage',
},
*/
static function process(
$action,
$default_value = false,
$newsletter = false,
$subscriber = false
) {
switch($action) {
case 'unsubscribe':
return '<a target="_blank" href="'.
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
.'">'.__('Unsubscribe').'</a>';
break;
case 'unsubscribe_url':
return SubscriptionUrl::getUnsubscribeUrl($subscriber);
break;
case 'manage':
return '<a target="_blank" href="'.
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
.'">'.__('Manage subscription').'</a>';
break;
case 'manage_url':
return SubscriptionUrl::getManageUrl($subscriber);
break;
default:
return false;
break;
}
}
}

View File

@ -35,20 +35,31 @@ class User {
switch($action) { switch($action) {
case 'firstname': case 'firstname':
return ($subscriber) ? $subscriber['first_name'] : $default_value; return ($subscriber) ? $subscriber['first_name'] : $default_value;
break;
case 'lastname': case 'lastname':
return ($subscriber) ? $subscriber['last_name'] : $default_value; return ($subscriber) ? $subscriber['last_name'] : $default_value;
break;
case 'email': case 'email':
return ($subscriber) ? $subscriber['email'] : false; return ($subscriber) ? $subscriber['email'] : false;
break;
case 'displayname': case 'displayname':
if($subscriber && $subscriber['wp_user_id']) { if($subscriber && $subscriber['wp_user_id']) {
$wp_user = get_userdata($subscriber['wp_user_id']); $wp_user = get_userdata($subscriber['wp_user_id']);
return $wp_user->user_login; return $wp_user->user_login;
}; }
return $default_value; return $default_value;
break;
case 'count': case 'count':
return Subscriber::count(); return Subscriber::filter('subscribed')->count();
break;
default: default:
return false; return false;
break;
} }
} }
} }

View File

@ -9,7 +9,8 @@ class Shortcodes {
function __construct( function __construct(
$rendered_newsletter, $rendered_newsletter,
$newsletter = false, $newsletter = false,
$subscriber = false) { $subscriber = false
) {
$this->rendered_newsletter = $rendered_newsletter; $this->rendered_newsletter = $rendered_newsletter;
$this->newsletter = $newsletter; $this->newsletter = $newsletter;
$this->subscriber = $subscriber; $this->subscriber = $subscriber;
@ -23,13 +24,12 @@ class Shortcodes {
function process($shortcodes) { function process($shortcodes) {
$processed_shortcodes = array_map( $processed_shortcodes = array_map(
function ($shortcode) { function ($shortcode) {
// TODO: discuss renaming "global". It is a reserved name in PHP.
if($shortcode === 'global') $shortcode = 'link';
preg_match( preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/', '/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
$shortcode, $shortcode,
$shortcode_details $shortcode_details
); );
$shortcode_class = $shortcode_class =
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']); __NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
if(!class_exists($shortcode_class)) return false; if(!class_exists($shortcode_class)) return false;
@ -41,7 +41,7 @@ class Shortcodes {
$this->subscriber $this->subscriber
); );
}, $shortcodes); }, $shortcodes);
return array_filter($processed_shortcodes); return $processed_shortcodes;
} }
function replace() { function replace() {

View File

@ -13,11 +13,10 @@ class Forms {
function get($id = false) { function get($id = false) {
$form = Form::findOne($id); $form = Form::findOne($id);
if($form === false) { if($form !== false) {
return false; $form = $form->asArray();
} else {
return $form->asArray();
} }
return $form;
} }
function listing($data = array()) { function listing($data = array()) {
@ -29,19 +28,14 @@ class Forms {
$listing_data = $listing->get(); $listing_data = $listing->get();
// fetch segments relations for each returned item // fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) { foreach($listing_data['items'] as $key => $form) {
// form's segments $form = $form->asArray();
$form_settings = ( $form['segments'] = (
(is_serialized($item['settings'])) !empty($form['settings']['segments'])
? unserialize($item['settings']) ? $form['settings']['segments']
: array()
);
$item['segments'] = (
!empty($form_settings['segments'])
? $form_settings['segments']
: array() : array()
); );
$listing_data['items'][$key] = $form;
} }
return $listing_data; return $listing_data;

View File

@ -14,6 +14,7 @@ use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption; use MailPoet\Models\NewsletterOption;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Models\SendingQueue; use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Scheduler\Scheduler;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -206,20 +207,11 @@ class Newsletters {
$listing_data = $listing->get(); $listing_data = $listing->get();
foreach($listing_data['items'] as &$item) { foreach($listing_data['items'] as $key => $newsletter) {
// get segments $listing_data['items'][$key] = $newsletter
$segments = NewsletterSegment::select('segment_id') ->withSegments()
->where('newsletter_id', $item['id']) ->withSendingQueue()
->findMany(); ->asArray();
$item['segments'] = array_map(function($relation) {
return $relation->segment_id;
}, $segments);
// get queue
$queue = SendingQueue::where('newsletter_id', $item['id'])
->orderByDesc('updated_at')
->findOne();
$item['queue'] = ($queue !== false) ? $queue->asArray() : null;
} }
return $listing_data; return $listing_data;
@ -274,6 +266,12 @@ class Newsletters {
} }
} }
} }
if(!isset($data['id']) &&
isset($data['type']) &&
$data['type'] === 'notification'
) {
Scheduler::postNotification($newsletter->id);
}
return array( return array(
'result' => true, 'result' => true,
'newsletter' => $newsletter->asArray() 'newsletter' => $newsletter->asArray()

View File

@ -15,12 +15,26 @@ class Router {
); );
add_action( add_action(
'wp_ajax_mailpoet', 'wp_ajax_mailpoet',
array($this, 'setup') array($this, 'setupAdmin')
);
add_action(
'wp_ajax_nopriv_mailpoet',
array($this, 'setupPublic')
); );
} }
function setup() { function setupAdmin() {
$this->securityCheck(); $this->verifyToken();
$this->checkPermissions();
return $this->processRoute();
}
function setupPublic() {
$this->verifyToken();
return $this->processRoute();
}
function processRoute() {
$class = ucfirst($_POST['endpoint']); $class = ucfirst($_POST['endpoint']);
$endpoint = __NAMESPACE__ . "\\" . $class; $endpoint = __NAMESPACE__ . "\\" . $class;
$method = $_POST['method']; $method = $_POST['method'];
@ -43,8 +57,11 @@ class Router {
echo $global; echo $global;
} }
function securityCheck() { function checkPermissions() {
if(!current_user_can('manage_options')) { die(); } if(!current_user_can('manage_options')) { die(); }
}
function verifyToken() {
if(!wp_verify_nonce($_POST['token'], 'mailpoet_token')) { die(); } if(!wp_verify_nonce($_POST['token'], 'mailpoet_token')) { die(); }
} }
} }

View File

@ -30,36 +30,14 @@ class Segments {
$listing_data = $listing->get(); $listing_data = $listing->get();
// fetch segments relations for each returned item // fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) { foreach($listing_data['items'] as $key => $segment) {
$stats = SubscriberSegment::table_alias('relation') $segment->subscribers_url = admin_url(
->where( 'admin.php?page=mailpoet-subscribers#/filter[segment='.$segment->id.']'
'relation.segment_id',
$item['id']
)
->join(
MP_SUBSCRIBERS_TABLE,
'subscribers.id = relation.subscriber_id',
'subscribers'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
'subscribed'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
'unsubscribed'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
'unconfirmed'
)
->findOne()->asArray();
$item = array_merge($item, $stats);
$item['subscribers_url'] = admin_url(
'admin.php?page=mailpoet-subscribers#/filter[segment='.$item['id'].']'
); );
$listing_data['items'][$key] = $segment
->withSubscribersCount()
->asArray();
} }
return $listing_data; return $listing_data;

View File

@ -15,17 +15,14 @@ class Subscribers {
function __construct() { function __construct() {
} }
function get($id = false) { function get($id = null) {
$subscriber = Subscriber::findOne($id); $subscriber = Subscriber::findOne($id);
if($subscriber !== false && $subscriber->id() > 0) { if($subscriber !== false) {
$segments = $subscriber->segments()->findArray(); $subscriber = $subscriber
->withCustomFields()
$subscriber = $subscriber->withCustomFields()->asArray(); ->withSubscriptions()
$subscriber['segments'] = array_map(function($segment) { ->asArray();
return $segment['id'];
}, $segments);
} }
return $subscriber; return $subscriber;
} }
@ -38,19 +35,10 @@ class Subscribers {
$listing_data = $listing->get(); $listing_data = $listing->get();
// fetch segments relations for each returned item // fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) { foreach($listing_data['items'] as $key => $subscriber) {
// avatar $listing_data['items'][$key] = $subscriber
$item['avatar_url'] = get_avatar_url($item['email'], array( ->withSubscriptions()
'size' => 32 ->asArray();
));
// subscriber's segments
$relations = SubscriberSegment::select('segment_id')
->where('subscriber_id', $item['id'])
->findMany();
$item['segments'] = array_map(function($relation) {
return $relation->segment_id;
}, $relations);
} }
return $listing_data; return $listing_data;
@ -100,18 +88,10 @@ class Subscribers {
} }
$subscriber = Subscriber::subscribe($data, $segment_ids); $subscriber = Subscriber::subscribe($data, $segment_ids);
if($subscriber->getErrors() !== false) {
$result = false;
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
} else {
$result = true;
}
if(!empty($errors)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => $errors 'errors' => $subscriber->getErrors()
); );
} }

View File

@ -2,17 +2,15 @@
namespace MailPoet\Segments; namespace MailPoet\Segments;
use \MailPoet\Models\Subscriber; use \MailPoet\Models\Subscriber;
use \MailPoet\Models\Segment; use \MailPoet\Models\Segment;
use MailPoet\Newsletter\Scheduler\Scheduler;
class WP { class WP {
static function synchronizeUser($wp_user_id) { static function synchronizeUser($wp_user_id) {
$wpUser = \get_userdata($wp_user_id); $wp_user = \get_userdata($wp_user_id);
$segment = Segment::getWPUsers(); $segment = Segment::getWPUsers();
if($wp_user === false or $segment === false) return;
if($wpUser === false or $segment === false) return; $subscriber = Subscriber::where('wp_user_id', $wp_user->ID)
$subscriber = Subscriber::where('wp_user_id', $wpUser->ID)
->findOne(); ->findOne();
switch(current_filter()) { switch(current_filter()) {
case 'delete_user': case 'delete_user':
case 'deleted_user': case 'deleted_user':
@ -21,22 +19,21 @@ class WP {
$subscriber->delete(); $subscriber->delete();
} }
break; break;
case 'user_register': case 'user_register':
$new_user = (!$subscriber) ? true : false;
case 'added_existing_user': case 'added_existing_user':
case 'profile_update': case 'profile_update':
default: default:
// get first name & last name // get first name & last name
$first_name = $wpUser->first_name; $first_name = $wp_user->first_name;
$last_name = $wpUser->last_name; $last_name = $wp_user->last_name;
if(empty($wpUser->first_name) && empty($wpUser->last_name)) { if(empty($wp_user->first_name) && empty($wp_user->last_name)) {
$first_name = $wpUser->display_name; $first_name = $wp_user->display_name;
} }
// subscriber data // subscriber data
$data = array( $data = array(
'wp_user_id'=> $wpUser->ID, 'wp_user_id' => $wp_user->ID,
'email' => $wpUser->user_email, 'email' => $wp_user->user_email,
'first_name' => $first_name, 'first_name' => $first_name,
'last_name' => $last_name, 'last_name' => $last_name,
'status' => 'subscribed' 'status' => 'subscribed'
@ -46,10 +43,15 @@ class WP {
$data['id'] = $subscriber->id(); $data['id'] = $subscriber->id();
} }
$subscriber = Subscriber::createOrUpdate($data); $subscriber = Subscriber::createOrUpdate($data);
if($subscriber->getErrors() === false && $subscriber->id > 0) {
if($subscriber !== false && $subscriber->id()) {
if($segment !== false) { if($segment !== false) {
$segment->addSubscriber($subscriber->id()); $segment->addSubscriber($subscriber->id);
}
if(isset($new_user) && $new_user === true) {
Scheduler::welcomeForNewWPUser(
$subscriber->id,
(array) $wp_user
);
} }
} }
break; break;

View File

@ -13,8 +13,8 @@ class Pages {
), ),
'public' => true, 'public' => true,
'has_archive' => false, 'has_archive' => false,
'show_ui' => true, 'show_ui' => WP_DEBUG,
'show_in_menu' => false, 'show_in_menu' => WP_DEBUG,
'rewrite' => false, 'rewrite' => false,
'show_in_nav_menus' => false, 'show_in_nav_menus' => false,
'can_export' => false, 'can_export' => false,
@ -65,8 +65,9 @@ class Pages {
return array( return array(
'id' => $page->ID, 'id' => $page->ID,
'title' => $page->post_title, 'title' => $page->post_title,
'preview_url' => get_permalink($page->ID), 'preview_url' => add_query_arg(array(
'edit_url' => get_edit_post_link($page->ID) 'mailpoet_preview' => 1
), get_permalink($page->ID))
); );
} }
} }

View File

@ -17,6 +17,8 @@ class BootStrapMenu {
Segment::getSegmentsWithSubscriberCount() : Segment::getSegmentsWithSubscriberCount() :
Segment::getSegmentsForExport($with_confirmed_subscribers); Segment::getSegmentsForExport($with_confirmed_subscribers);
return array_map(function($segment) { return array_map(function($segment) {
if (!$segment['name']) $segment['name'] = __('Not In Segment');
if (!$segment['id']) $segment['id'] = 0;
return array( return array(
'id' => $segment['id'], 'id' => $segment['id'],
'name' => $segment['name'], 'name' => $segment['name'],
@ -31,7 +33,7 @@ class BootStrapMenu {
'first_name' => __('First name'), 'first_name' => __('First name'),
'last_name' => __('Last name'), 'last_name' => __('Last name'),
'status' => __('Status') 'status' => __('Status')
// TODO: add additional fiels from MP2 // TODO: add additional fields from MP2
/* /*
'confirmed_ip' => __('IP address') 'confirmed_ip' => __('IP address')
'confirmed_at' => __('Subscription date') 'confirmed_at' => __('Subscription date')

Some files were not shown because too many files have changed in this diff Show More