Compare commits

...

113 Commits

Author SHA1 Message Date
a25ea3ddf6 Bump up release version to 0.0.23 2016-04-09 02:08:05 +03:00
021349caee Merge pull request #415 from mailpoet/public_api_update
Updates query parameters for public API
2016-04-09 02:01:20 +03:00
89f2958d23 - Updates cron to use new public API query parameters 2016-04-08 18:53:57 -04:00
8c8435766e - Updates query parameters for public API 2016-04-08 18:40:45 -04:00
40dd1bbb3b Merge pull request #413 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-08 22:27:48 +03:00
ba40437eb9 - Enables dynamic line-height based on font-size 2016-04-08 15:19:14 -04:00
813db1ae33 - Sets LI bottom margin to 10px 2016-04-08 08:45:09 -04:00
9588397e4e - Moves all styles logic to the StylesHelper class
- Sets margin-bottom on h1-4 to be 0.3*font-size
2016-04-07 19:39:09 -04:00
ecf83ca419 - Disables color compression when tidying CSS 2016-04-07 19:39:09 -04:00
9cc494f0fa - Removes image height HTML attribute and sets "height:auto" style 2016-04-07 19:39:09 -04:00
dd4b7e4d1c - Adds lines heights to LIs 2016-04-07 19:39:09 -04:00
738b2f6c17 - Adds dynamic line breaks to paragraphs when the elment is followed by
list
2016-04-07 19:39:09 -04:00
33289342d3 - Implements dynamic margins on headings 2016-04-07 19:39:09 -04:00
a00f1efcfe - Updates unit test due to button style change 2016-04-07 19:38:15 -04:00
b89897e6d4 - Adds line breaks after headings IF they are followed by paragraph
- Adds line breaks after blocksquotes
- Updates margin on lists
- Adds line breaks after paragraphs IF they are not the last element
2016-04-07 19:38:15 -04:00
c539837896 - Makes button's stroke width = border width 2016-04-07 19:38:15 -04:00
27edf5f71d - Updates list styles 2016-04-07 19:38:15 -04:00
32cc5644f9 - Removes height:auto from images 2016-04-07 19:38:15 -04:00
8a664aa7f1 - Set left text alignment on all elements when alignment is otherwise
absent
2016-04-07 19:38:15 -04:00
7e5e8a4282 - Updates button padding 2016-04-07 19:38:15 -04:00
70d5d609e2 Merge pull request #412 from mailpoet/scheduled_sending
Fixes an issue with welcome emails not being sent to confirmed subscribers
2016-04-07 12:09:30 +03:00
19160c99e1 Merge pull request #411 from mailpoet/send_with_review
Send with review
2016-04-06 18:23:50 +03:00
99b2a7457e Merge pull request #410 from mailpoet/newsletter_send_preview_fix
Fixes an issue with shortcodes not rendering when sending newsletter
2016-04-06 18:05:39 +03:00
945d7edc70 Send with review
- updated MailPoet logo
- added SPF
- hide Dkim for beta
- added warning in case the number of emails/sec is too high
2016-04-06 16:57:10 +02:00
6a97badfed - Fixes an issue with welcome emails not being seint to confirmed
subscribers
2016-04-05 14:34:22 -04:00
5ec8e4ed52 - Fixes an issue with shortcodes not rendering when sending newsletter
preview
2016-04-05 09:54:04 -04:00
1569b5f80a Merge pull request #409 from mailpoet/scheduled_sending
Fixed an issue with newsletter schedule not being saved
2016-04-05 12:10:34 +03:00
e62ecc5036 - Fixes duplicate detection check 2016-04-04 21:09:15 -04:00
b0150e184b - Fixed an issue with newsletter schedule not being saved 2016-04-04 19:41:18 -04:00
8b96854f39 Bump up release version to 0.0.22 2016-04-01 14:57:11 +03:00
28d8600078 Merge pull request #408 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-01 12:37:11 +03:00
4f30158722 - Updates button height calculation
- Removes background color when it's set to "transparent"
- Removes second line break from paragraphs
- Introduces other changes based on Becks's testing
- Updates unit tests
2016-03-31 19:19:58 -04:00
4486f9c5b0 Merge pull request #407 from mailpoet/scheduled_sending
Prevents welcome emails to be sent to unconfirmed subsribers
2016-03-31 14:53:41 +03:00
8515dcf29b Merge pull request #406 from mailpoet/rendering_engine_update
Rendering engine update
2016-03-31 14:05:18 +03:00
d16eb87782 - Prevents welcome emails to be sent to unconfirmed subscribers
- Closes #384
2016-03-30 20:59:55 -04:00
3c77e5d25e - Removes dynamic line-height based on column width
- Updates logic behind removing last element bottom padding
- Properly sets font size for H1-4 tags, header, footer and text
2016-03-30 20:09:06 -04:00
aa1a2a0da9 - Updates button padding 2016-03-30 20:09:06 -04:00
7c9029b227 Bump up release version to 0.0.21 2016-03-25 13:10:37 +02:00
1823bf606a Merge pull request #403 from mailpoet/signup_confirmation_review
Signup confirmation review
2016-03-24 14:43:36 -04:00
525b7fdd65 Merge pull request #402 from mailpoet/scheduled_sending
Scheduled sending
2016-03-24 16:10:56 +02:00
11031d2b56 Remove unused cron translations, fix start and stop button translations 2016-03-24 16:10:39 +02:00
4d45635d03 - Implements "in any role" for WP user welcome e-mails 2016-03-24 10:02:04 -04:00
b81764402b - Updates hooks used to schedule welcome email
- Simplifies cron status message
- Implements newsletter change detection between scheduled queues
- Implements detection for subscribers who unsubscribed from list
- Implements detection for WP users who changed roles
- Updates scheduler worker logic
- Various fixes
2016-03-24 10:02:04 -04:00
947e1150d8 - Removes cron worker hooks as the execution timer can't be persisted
between them
2016-03-24 10:00:52 -04:00
97f0e512af populate from/reply_to for signup confirmation
- added loading screen to reinstall process
2016-03-24 14:22:37 +01:00
f15374de43 don't replace title on custom mailpoet pages 2016-03-24 14:22:37 +01:00
f082c065d1 Confirmation email + Subscription pages
- form as an iframe: increased marginY
- fixed issue with page titles (old themes using wp_title hook)
- redirect on reinstall instead of showing a message (to avoid being able to re-save old settings)
2016-03-24 14:22:37 +01:00
72a9951125 Merge pull request #400 from mailpoet/i18n
Javascript translations
2016-03-24 13:11:51 +01:00
a82d9a63d4 Add missing unsubscribedOn translation on subscribers form 2016-03-24 14:09:35 +02:00
a7e9979781 Merge pull request #398 from mailpoet/rendering_engine_update
Updates rendering engine based on Becs's comments
2016-03-24 13:18:06 +02:00
cebd1ee7ae Merge pull request #399 from mailpoet/editor_rendering
Adapts editor rendering to Becs' feedback
2016-03-23 18:21:00 -04:00
5faa467306 - Updates merge issue with template
- Updates button width/height based on border radius
2016-03-23 18:18:21 -04:00
53eb9cd2ae Translate JS code in listings and forms 2016-03-23 18:59:01 +02:00
7f6eed6d66 Translate cron, forms, newsletters, segments and subscribers 2016-03-23 14:52:06 +02:00
74f3fa65cd Add translatiosn for newsletter creation and listings 2016-03-23 14:52:06 +02:00
8723aa4e4e Remove obsolete test file 2016-03-23 14:52:06 +02:00
ccab8b4cf3 Add MailPoet.I18n for basic translation handling, removed MailPoetI18n 2016-03-23 14:52:06 +02:00
4c4a4ab31d Reduce max button width to 288px, max button border to 10px 2016-03-23 14:09:19 +02:00
45df02b0ec Merge pull request #395 from mailpoet/many_improvements
Many improvements
2016-03-23 13:38:40 +02:00
dd7067e590 Merge pull request #397 from mailpoet/scheduled_sending
Updates scheduling logic to work with the new normalized DB time
2016-03-23 11:51:58 +02:00
1219b5ba49 Merge pull request #396 from mailpoet/timezone_fix
Normalizes time difference between WP and database
2016-03-23 11:45:42 +02:00
01496ac813 Merge branch 'scheduled_sending' of mailpoet:mailpoet/mailpoet into scheduled_sending 2016-03-22 20:55:42 -04:00
3b3ccc18ce - Updates scheduling logic to work with the new normalized DB time 2016-03-22 20:43:16 -04:00
3dce951e66 - Updates rendering engine based on Becs's comments
- Prevents removing of side padding
- Enables font size on output
- Sets line-height in footer
- Adds image height:auto
- Adds mobile styles for buttons
- Updates style sheet
2016-03-22 20:21:39 -04:00
db1dc172aa - Updates scheduling logic to work with the new normalized DB time 2016-03-22 18:26:37 -04:00
26c5cc1e43 - Normalizes time difference between WP and database 2016-03-22 13:05:41 -04:00
f91bfbf473 handle form as iframe 2016-03-22 17:25:25 +01:00
3dae0ef13f increased page input in listings 2016-03-22 17:25:25 +01:00
3281ac390e Improved segment selection errors in form editor
- improved error display to make it more obvious (added border on select2 on error)
2016-03-22 17:25:25 +01:00
d731a6b432 toggle segment selection validation on form editor 2016-03-22 17:25:25 +01:00
bb869e8ae8 Fixed setWindowTitle for WP version < 4.4
- fixed variable name Env::temp_url (instead of temp_URL)
- updated cache folder to be in the temp folder (uploads) instead of views (within plugin)
2016-03-22 17:25:25 +01:00
7331e5cabd Merge pull request #392 from mailpoet/shortcodes_update
Updates shortcodes logic
2016-03-21 17:58:38 +02:00
ba05ca35af - Implements shortcodes rendering in subject line 2016-03-21 11:45:08 -04:00
91e7bf6336 Merge branch 'shortcodes_update' of mailpoet:mailpoet/mailpoet into shortcodes_update 2016-03-21 10:12:10 -04:00
ff58067d55 Change array_column to Helpers::arrayColumn method 2016-03-21 16:07:20 +02:00
2ba6bb339e Fix newsletter options format for newsletters 2016-03-21 15:50:48 +02:00
a47afdd313 - Fixes queue worker issue 2016-03-21 09:09:43 -04:00
608b559ee1 - Minor adjustment to the shortcodes logic 2016-03-21 09:09:43 -04:00
181ed45d0b - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-21 09:09:43 -04:00
29fac8d052 Merge pull request #394 from mailpoet/scheduled_sending
Updates scheduled sending
2016-03-21 15:06:30 +02:00
a3d7d53eea - Fixes queue worker issue 2016-03-21 08:58:51 -04:00
3f6caf5fa4 - Implements scheduler worker for welcome and post notifications
- Updates sending queue worker to save rendered newsletter body
- Updates sending queue router to schedule post notification newsletters
2016-03-20 22:01:01 -04:00
42c4139ba5 - Minor adjustment to the shortcodes logic 2016-03-20 11:37:59 -04:00
ad31b143d2 - Updates scheduler worker to process queued newsletters 2016-03-19 12:29:10 -04:00
6ec15bec22 - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-19 11:19:22 -04:00
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
168 changed files with 5637 additions and 2025 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

@ -563,3 +563,6 @@ handle_icon = '../img/handle.png'
.CodeMirror .CodeMirror
border: 1px solid #eee border: 1px solid #eee
/* Settings */
#mailpoet_form_segments.parsley-error + span .select2-selection
border: 1px solid #b94a48

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -48,28 +48,22 @@ define(
}) })
.done(function(response) { .done(function(response) {
if(!response.result) { if(!response.result) {
MailPoet.Notice.error(MailPoetI18n.daemonControlError); MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
} }
}.bind(this)); }.bind(this));
}, },
render: function() { render: function() {
if(this.state.status === 'loading') { if(this.state.status === 'loading') {
return(<div>Loading daemon status...</div>); return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
} }
switch(this.state.status) { switch(this.state.status) {
case 'started': case 'started':
return( return(
<div> <div>
Cron daemon is running. {MailPoet.I18n.t('cronDaemonIsRunning')}
<br/> <br/>
<br/> <br/>
It was started <a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
<strong> {this.state.timeSinceStart} </strong> and last executed
<strong> {this.state.timeSinceUpdate} </strong> for a total of
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
</div> </div>
); );
break; break;
@ -77,17 +71,17 @@ define(
case 'stopping': case 'stopping':
return( return(
<div> <div>
Daemon is {this.state.status} {MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
</div> </div>
); );
break; break;
case 'stopped': case 'stopped':
return( return(
<div> <div>
Daemon is {this.state.status} {MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
<br /> <br />
<br /> <br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a> <a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
</div> </div>
); );
break; break;
@ -103,4 +97,4 @@ define(
container container
); );
} }
}); });

View File

@ -167,7 +167,7 @@ define(
<input <input
className="button button-primary" className="button button-primary"
type="submit" type="submit"
value="Save" value={MailPoet.I18n.t('save')}
disabled={this.state.loading} /> disabled={this.state.loading} />
); );
} }
@ -199,4 +199,4 @@ define(
return Form; return Form;
} }
); );

View File

@ -402,11 +402,30 @@ var WysijaForm = {
} }
}); });
// hide list selection if a list widget has been dragged into the editor var hasSegmentSelection = WysijaForm.hasSegmentSelection();
$('mailpoet_settings_segment_selection')[
(($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0) === true) if(hasSegmentSelection) {
? 'hide' : 'show' $('mailpoet_form_segments').writeAttribute('required', false).disable();
](); $('mailpoet_settings_segment_selection').hide();
} else {
$('mailpoet_form_segments').writeAttribute('required', true).enable();
$('mailpoet_settings_segment_selection').show();
}
},
hasSegmentSelection: function() {
return ($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
},
isSegmentSelectionValid: function() {
var segment_selection = $$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
if(segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
return (
(block.params.values !== undefined)
&&
(block.params.values.length > 0)
);
}
return false;
}, },
setBlockPositions: function(event, target) { setBlockPositions: function(event, target) {
// release dragging lock // release dragging lock

View File

@ -8,17 +8,17 @@ import MailPoet from 'mailpoet'
const columns = [ const columns = [
{ {
name: 'name', name: 'name',
label: 'Name', label: MailPoet.I18n.t('formName'),
sortable: true sortable: true
}, },
{ {
name: 'segments', name: 'segments',
label: 'Lists', label: MailPoet.I18n.t('segments'),
sortable: false sortable: false
}, },
{ {
name: 'created_at', name: 'created_at',
label: 'Created on', label: MailPoet.I18n.t('createdOn'),
sortable: true sortable: true
} }
]; ];
@ -30,11 +30,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 form was moved to the trash.' MailPoet.I18n.t('oneFormTrashed')
); );
} else { } else {
message = ( message = (
'%$1d forms were moved to the trash.' MailPoet.I18n.t('multipleFormsTrashed')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -45,11 +45,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 form was permanently deleted.' MailPoet.I18n.t('oneFormDeleted')
); );
} else { } else {
message = ( message = (
'%$1d forms were permanently deleted.' MailPoet.I18n.t('multipleFormsDeleted')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -60,11 +60,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 form has been restored from the trash.' MailPoet.I18n.t('oneFormRestored')
); );
} else { } else {
message = ( message = (
'%$1d forms have been restored from the trash.' MailPoet.I18n.t('multipleFormsRestored')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -74,16 +74,16 @@ const messages = {
const item_actions = [ const item_actions = [
{ {
name: 'edit', name: 'edit',
label: 'Edit', label: MailPoet.I18n.t('edit'),
link: function(item) { link: function(item) {
return ( return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a> <a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
); );
} }
}, },
{ {
name: 'duplicate_form', name: 'duplicate_form',
label: 'Duplicate', label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) { onClick: function(item, refresh) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
endpoint: 'forms', endpoint: 'forms',
@ -91,7 +91,7 @@ const item_actions = [
data: item.id data: item.id
}).done(function(response) { }).done(function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name) (MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
); );
refresh(); refresh();
}); });
@ -105,7 +105,7 @@ const item_actions = [
const bulk_actions = [ const bulk_actions = [
{ {
name: 'trash', name: 'trash',
label: 'Trash', label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash onSuccess: messages.onTrash
} }
]; ];
@ -142,10 +142,10 @@ const FormList = React.createClass({
</strong> </strong>
{ actions } { actions }
</td> </td>
<td className="column-format" data-colname="Lists"> <td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Created on"> <td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr> <abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
</td> </td>
</div> </div>
@ -155,11 +155,11 @@ const FormList = React.createClass({
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Forms <a {MailPoet.I18n.t('pageTitle')} <a
className="add-new-h2" className="add-new-h2"
href="javascript:;" href="javascript:;"
onClick={ this.createForm } onClick={ this.createForm }
>New</a> >{MailPoet.I18n.t('new')}</a>
</h2> </h2>
<Listing <Listing
@ -179,4 +179,4 @@ const FormList = React.createClass({
} }
}); });
module.exports = FormList; module.exports = FormList;

25
assets/js/src/i18n.js Normal file
View File

@ -0,0 +1,25 @@
define('i18n',
[
'mailpoet',
'underscore',
], function(
MailPoet,
_
) {
'use strict';
var translations = {};
MailPoet.I18n = {
add: function(key, value) {
translations[key] = value;
},
t: function(key) {
return translations[key] || 'TRANSLATION "%$1s" NOT FOUND'.replace("%$1s", key);
},
all: function() {
return translations;
}
};
});

23
assets/js/src/iframe.js Normal file
View File

@ -0,0 +1,23 @@
define('iframe', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
'use strict';
MailPoet.Iframe = {
marginY: 20,
autoSize: function(iframe) {
if(!iframe) return;
this.setSize(
iframe,
iframe.contentWindow.document.body.scrollHeight
);
},
setSize: function(iframe, i) {
if(!iframe) return;
iframe.style.height = (
parseInt(i) + this.marginY
) + "px";
}
};
return MailPoet;
});

View File

@ -1,8 +1,10 @@
define([ define([
'react' 'react',
'mailpoet'
], ],
function( function(
React React,
MailPoet
) { ) {
var ListingBulkActions = React.createClass({ var ListingBulkActions = React.createClass({
getInitialState: function() { getInitialState: function() {
@ -82,7 +84,7 @@ function(
<label <label
className="screen-reader-text" className="screen-reader-text"
htmlFor="bulk-action-selector-top"> htmlFor="bulk-action-selector-top">
Select bulk action {MailPoet.I18n.t('selectBulkAction')}
</label> </label>
<select <select
@ -91,7 +93,7 @@ function(
value={ this.state.action } value={ this.state.action }
onChange={this.handleChangeAction} onChange={this.handleChangeAction}
> >
<option value="">Bulk Actions</option> <option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map(function(action, index) { { this.props.bulk_actions.map(function(action, index) {
return ( return (
<option <option
@ -104,7 +106,7 @@ function(
<input <input
onClick={ this.handleApplyAction } onClick={ this.handleApplyAction }
type="submit" type="submit"
defaultValue="Apply" defaultValue={MailPoet.I18n.t('apply')}
className="button action" /> className="button action" />
{ this.state.extra } { this.state.extra }
@ -114,4 +116,4 @@ function(
}); });
return ListingBulkActions; return ListingBulkActions;
}); });

View File

@ -1,10 +1,12 @@
define([ define([
'react', 'react',
'jquery' 'jquery',
'mailpoet'
], ],
function( function(
React, React,
jQuery jQuery,
MailPoet
) { ) {
var ListingFilters = React.createClass({ var ListingFilters = React.createClass({
handleFilterAction: function() { handleFilterAction: function() {
@ -69,7 +71,7 @@ function(
id="post-query-submit" id="post-query-submit"
onClick={ this.handleFilterAction } onClick={ this.handleFilterAction }
type="submit" type="submit"
defaultValue="Filter" defaultValue={MailPoet.I18n.t('filter')}
className="button" /> className="button" />
); );
} }
@ -80,7 +82,7 @@ function(
<input <input
onClick={ this.handleEmptyTrash } onClick={ this.handleEmptyTrash }
type="submit" type="submit"
value="Empty Trash" value={MailPoet.I18n.t('emptyTrash')}
className="button" className="button"
/> />
); );

View File

@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) { define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingHeader = React.createClass({ var ListingHeader = React.createClass({
handleSelectItems: function() { handleSelectItems: function() {
@ -28,7 +36,7 @@ define(['react', 'classnames'], function(React, classNames) {
<th <th
className="manage-column column-cb check-column"> className="manage-column column-cb check-column">
<label className="screen-reader-text"> <label className="screen-reader-text">
{ 'Select All' } {MailPoet.I18n.t('selectAll')}
</label> </label>
<input <input
type="checkbox" type="checkbox"
@ -87,4 +95,4 @@ define(['react', 'classnames'], function(React, classNames) {
}); });
return ListingHeader; return ListingHeader;
}); });

View File

@ -98,7 +98,7 @@ define(
null, null,
this.props.item.id this.props.item.id
) }> ) }>
Trash {MailPoet.I18n.t('trash')}
</a> </a>
</span> </span>
); );
@ -145,7 +145,7 @@ define(
} else { } else {
item_actions = ( item_actions = (
<span className="edit"> <span className="edit">
<Link to={ `/edit/${ this.props.item.id }` }>Edit</Link> <Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link>
</span> </span>
); );
} }
@ -161,7 +161,7 @@ define(
null, null,
this.props.item.id this.props.item.id
)} )}
>Restore</a> >{MailPoet.I18n.t('restore')}</a>
</span> </span>
{ ' | ' } { ' | ' }
<span className="delete"> <span className="delete">
@ -172,13 +172,13 @@ define(
null, null,
this.props.item.id this.props.item.id
)} )}
>Delete permanently</a> >{MailPoet.I18n.t('deletePermanently')}</a>
</span> </span>
</div> </div>
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
</div> </div>
); );
@ -191,7 +191,7 @@ define(
<button <button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) } onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button"> className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span> <span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button> </button>
</div> </div>
); );
@ -223,8 +223,8 @@ define(
className="colspanchange"> className="colspanchange">
{ {
(this.props.loading === true) (this.props.loading === true)
? MailPoetI18n.loadingItems ? MailPoet.I18n.t('loadingItems')
: MailPoetI18n.noItemsFound : MailPoet.I18n.t('noItemsFound')
} }
</td> </td>
</tr> </tr>
@ -250,8 +250,8 @@ define(
}> }>
{ {
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoetI18n.selectAllLabel ? MailPoet.I18n.t('selectAllLabel')
: MailPoetI18n.selectedAllLabel.replace( : MailPoet.I18n.t('selectedAllLabel').replace(
'%d', '%d',
this.props.count this.props.count
) )
@ -261,8 +261,8 @@ define(
onClick={ this.props.onSelectAll } onClick={ this.props.onSelectAll }
href="javascript:;">{ href="javascript:;">{
(this.props.selection !== 'all') (this.props.selection !== 'all')
? MailPoetI18n.selectAllLink ? MailPoet.I18n.t('selectAllLink')
: MailPoetI18n.clearSelection : MailPoet.I18n.t('clearSelection')
}</a> }</a>
</td> </td>
</tr> </tr>
@ -519,7 +519,7 @@ define(
group: 'trash' group: 'trash'
}, function(response) { }, function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoetI18n.permanentlyDeleted.replace('%d', response) MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
); );
}); });
}, },
@ -678,12 +678,12 @@ define(
bulk_actions = [ bulk_actions = [
{ {
name: 'restore', name: 'restore',
label: 'Restore', label: MailPoet.I18n.t('restore'),
onSuccess: this.props.messages.onRestore onSuccess: this.props.messages.onRestore
}, },
{ {
name: 'delete', name: 'delete',
label: 'Delete permanently', label: MailPoet.I18n.t('deletePermanently'),
onSuccess: this.props.messages.onDelete onSuccess: this.props.messages.onDelete
} }
]; ];

View File

@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) { define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingPages = React.createClass({ var ListingPages = React.createClass({
getInitialState: function() { getInitialState: function() {
@ -72,7 +80,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setPreviousPage } onClick={ this.setPreviousPage }
className="prev-page"> className="prev-page">
<span className="screen-reader-text">Previous page</span> <span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
); );
@ -83,7 +91,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setFirstPage } onClick={ this.setFirstPage }
className="first-page"> className="first-page">
<span className="screen-reader-text">First page</span> <span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span> <span aria-hidden="true">«</span>
</a> </a>
); );
@ -94,7 +102,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setNextPage } onClick={ this.setNextPage }
className="next-page"> className="next-page">
<span className="screen-reader-text">Next page</span> <span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
); );
@ -105,7 +113,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;" <a href="javascript:;"
onClick={ this.setLastPage } onClick={ this.setLastPage }
className="last-page"> className="last-page">
<span className="screen-reader-text">Last page</span> <span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span> <span aria-hidden="true">»</span>
</a> </a>
); );
@ -125,20 +133,20 @@ define(['react', 'classnames'], function(React, classNames) {
<span className="paging-input"> <span className="paging-input">
<label <label
className="screen-reader-text" className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label> htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input <input
type="text" type="text"
onChange={ this.handleChangeManualPage } onChange={ this.handleChangeManualPage }
onKeyUp={ this.handleSetManualPage } onKeyUp={ this.handleSetManualPage }
onBlur={ this.handleBlurManualPage } onBlur={ this.handleBlurManualPage }
aria-describedby="table-paging" aria-describedby="table-paging"
size="1" size="2"
ref="page" ref="page"
value={ pageValue } value={ pageValue }
name="paged" name="paged"
id="current-page-selector" id="current-page-selector"
className="current-page" /> className="current-page" />
&nbsp;of&nbsp; &nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages"> <span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)} {Math.ceil(this.props.count / this.props.limit)}
</span> </span>
@ -158,7 +166,9 @@ define(['react', 'classnames'], function(React, classNames) {
return ( return (
<div className={ classes }> <div className={ classes }>
<span className="displaying-num">{ this.props.count } items</span> <span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count)
}</span>
{ pagination } { pagination }
</div> </div>
); );
@ -167,4 +177,4 @@ define(['react', 'classnames'], function(React, classNames) {
}); });
return ListingPages; return ListingPages;
}); });

View File

@ -1,4 +1,10 @@
define(['react'], function(React) { define([
'mailpoet',
'react'
], function(
MailPoet,
React
) {
var ListingSearch = React.createClass({ var ListingSearch = React.createClass({
handleSearch: function(e) { handleSearch: function(e) {
@ -18,7 +24,7 @@ define(['react'], function(React) {
<form name="search" onSubmit={this.handleSearch}> <form name="search" onSubmit={this.handleSearch}>
<p className="search-box"> <p className="search-box">
<label htmlFor="search_input" className="screen-reader-text"> <label htmlFor="search_input" className="screen-reader-text">
Search {MailPoet.I18n.t('searchLabel')}
</label> </label>
<input <input
type="search" type="search"
@ -28,7 +34,7 @@ define(['react'], function(React) {
defaultValue={this.props.search} /> defaultValue={this.props.search} />
<input <input
type="submit" type="submit"
defaultValue={MailPoetI18n.searchLabel} defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" /> className="button" />
</p> </p>
</form> </form>

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";
@ -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(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
}, },
/** /**

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',
@ -79,7 +79,7 @@ define([
}, },
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(), mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'), mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
}); });
}, },
}); });

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',
@ -79,7 +79,7 @@ define([
}, },
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(), mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'), mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
}); });
}, },
}); });

View File

@ -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(MailPoet.I18n.t('failedToFetchAvailablePosts'));
}); });
}, },
_refreshTransformedPosts: function() { _refreshTransformedPosts: function() {
@ -118,7 +118,7 @@ define([
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(MailPoet.I18n.t('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(MailPoet.I18n.t('failedToFetchRenderedPosts'));
}); });
}, },
}); });

View File

@ -77,7 +77,7 @@ define([
}, },
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(), mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'), mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
}); });
} }
}, },

View File

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

View File

@ -191,7 +191,7 @@ define([
if (templateName === '') { if (templateName === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'), MailPoet.I18n.t('templateNameMissing'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
@ -199,30 +199,27 @@ define([
); );
} else if (templateDescription === '') { } else if (templateDescription === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'), MailPoet.I18n.t('templateDescriptionMissing'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
} }
); );
} 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'), MailPoet.I18n.t('templateSaved'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
} }
); );
}).fail(function() { }).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'), MailPoet.I18n.t('templateSaveFailed'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
@ -247,7 +244,7 @@ define([
if (templateName === '') { if (templateName === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'), MailPoet.I18n.t('templateNameMissing'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
@ -255,14 +252,13 @@ define([
); );
} else if (templateDescription === '') { } else if (templateDescription === '') {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'), MailPoet.I18n.t('templateDescriptionMissing'),
{ {
positionAfter: that.$el, positionAfter: that.$el,
scroll: true, scroll: true,
} }
); );
} else { } else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({ Module.exportTemplate({
name: templateName, name: templateName,
description: templateDescription, description: templateDescription,
@ -278,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');
} }
}, },
@ -289,8 +284,8 @@ 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(MailPoet.I18n.t('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";
@ -245,8 +256,6 @@ define([
}); });
}, },
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 $emailField = this.$('#mailpoet_preview_to_email');
var data = { var data = {
@ -256,7 +265,7 @@ define([
if (data.subscriber.length <= 0) { if (data.subscriber.length <= 0) {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewEmailMissing'), MailPoet.I18n.t('newsletterPreviewEmailMissing'),
{ {
positionAfter: $emailField, positionAfter: $emailField,
scroll: true, scroll: true,
@ -270,7 +279,7 @@ define([
CommunicationComponent.previewNewsletter(data).done(function(response) { CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.result !== undefined && response.result === true) { if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true }); MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
} else { } else {
if (_.isArray(response.errors)) { if (_.isArray(response.errors)) {
response.errors.map(function(error) { response.errors.map(function(error) {
@ -278,7 +287,7 @@ define([
}); });
} else { } else {
MailPoet.Notice.error( MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewFailedToSend'), MailPoet.I18n.t('newsletterPreviewFailedToSend'),
{ {
scroll: true, scroll: true,
static: true, static: true,

View File

@ -2,12 +2,14 @@ define(
[ [
'react', 'react',
'react-router', 'react-router',
'classnames' 'classnames',
'mailpoet'
], ],
function( function(
React, React,
Router, Router,
classNames classNames,
MailPoet
) { ) {
var Link = Router.Link; var Link = Router.Link;
@ -21,20 +23,20 @@ define(
steps: [ steps: [
{ {
name: 'type', name: 'type',
label: 'Select type', label: MailPoet.I18n.t('selectType'),
link: '/new' link: '/new'
}, },
{ {
name: 'template', name: 'template',
label: 'Template' label: MailPoet.I18n.t('template')
}, },
{ {
name: 'editor', name: 'editor',
label: 'Designer' label: MailPoet.I18n.t('designer')
}, },
{ {
name: 'send', name: 'send',
label: 'Send' label: MailPoet.I18n.t('send')
} }
] ]
}; };
@ -73,4 +75,4 @@ define(
return Breadcrumb; return Breadcrumb;
} }
); );

View File

@ -4,39 +4,41 @@ define(
'react-router', 'react-router',
'listing/listing.jsx', 'listing/listing.jsx',
'classnames', 'classnames',
'jquery' 'jquery',
'mailpoet'
], ],
function( function(
React, React,
Router, Router,
Listing, Listing,
classNames, classNames,
jQuery jQuery,
MailPoet
) { ) {
var Link = Router.Link; var Link = Router.Link;
var columns = [ var columns = [
{ {
name: 'subject', name: 'subject',
label: 'Subject', label: MailPoet.I18n.t('subject'),
sortable: true sortable: true
}, },
{ {
name: 'status', name: 'status',
label: 'Status' label: MailPoet.I18n.t('status')
}, },
{ {
name: 'segments', name: 'segments',
label: 'Lists' label: MailPoet.I18n.t('lists')
}, },
{ {
name: 'created_at', name: 'created_at',
label: 'Created on', label: MailPoet.I18n.t('createdOn'),
sortable: true sortable: true
}, },
{ {
name: 'updated_at', name: 'updated_at',
label: 'Last modified on', label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true sortable: true
} }
]; ];
@ -48,11 +50,11 @@ define(
if(count === 1) { if(count === 1) {
message = ( message = (
'1 newsletter was moved to the trash.' MailPoet.I18n.t('oneNewsletterTrashed')
); );
} else { } else {
message = ( message = (
'%$1d newsletters were moved to the trash.' MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -63,11 +65,11 @@ define(
if(count === 1) { if(count === 1) {
message = ( message = (
'1 newsletter was permanently deleted.' MailPoet.I18n.t('oneNewsletterDeleted')
); );
} else { } else {
message = ( message = (
'%$1d newsletters were permanently deleted.' MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -78,11 +80,11 @@ define(
if(count === 1) { if(count === 1) {
message = ( message = (
'1 newsletter has been restored from the trash.' MailPoet.I18n.t('oneNewsletterRestored')
); );
} else { } else {
message = ( message = (
'%$1d newsletters have been restored from the trash.' MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -92,7 +94,7 @@ define(
var bulk_actions = [ var bulk_actions = [
{ {
name: 'trash', name: 'trash',
label: 'Trash', label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash onSuccess: messages.onTrash
} }
]; ];
@ -103,7 +105,7 @@ define(
link: function(item) { link: function(item) {
return ( return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }> <a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit {MailPoet.I18n.t('edit')}
</a> </a>
); );
} }
@ -137,7 +139,7 @@ define(
renderStatus: function(item) { renderStatus: function(item) {
if(!item.queue) { if(!item.queue) {
return ( return (
<span>Not sent yet.</span> <span>{MailPoet.I18n.t('notSentYet')}</span>
); );
} else { } else {
var progressClasses = classNames( var progressClasses = classNames(
@ -155,9 +157,11 @@ define(
if(item.queue.status === 'completed') { if(item.queue.status === 'completed') {
label = ( label = (
<span> <span>
Sent to { {
item.queue.count_processed - item.queue.count_failed MailPoet.I18n.t('newsletterQueueCompleted')
} out of { item.queue.count_total }. .replace("%$1d", item.queue.count_processed - item.queue.count_failed)
.replace("%$2d", item.queue.count_total)
}
</span> </span>
); );
} else { } else {
@ -171,14 +175,14 @@ define(
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }} style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.resumeSending.bind(null, item) } onClick={ this.resumeSending.bind(null, item) }
>Resume</a> >{MailPoet.I18n.t('resume')}</a>
<a <a
id={ 'pause_'+item.id } id={ 'pause_'+item.id }
className="button mailpoet_pause" className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }} style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.pauseSending.bind(null, item) } onClick={ this.pauseSending.bind(null, item) }
>Pause</a> >{MailPoet.I18n.t('pause')}</a>
</span> </span>
); );
} }
@ -239,7 +243,7 @@ define(
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Newsletters <Link className="add-new-h2" to="/new">New</Link> {MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2> </h2>
<Listing <Listing
@ -258,4 +262,4 @@ define(
return NewsletterList; return NewsletterList;
} }
); );

View File

@ -21,21 +21,20 @@ define(
var fields = [ var fields = [
{ {
name: 'subject', name: 'subject',
label: 'Subject line', label: MailPoet.I18n.t('subjectLine'),
tip: "Be creative! It's the first thing your subscribers see."+ tip: MailPoet.I18n.t('subjectLineTip'),
"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.' 'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
} }
}, },
{ {
name: 'segments', name: 'segments',
label: 'Segments', label: MailPoet.I18n.t('segments'),
tip: "The subscriber segment that will be used for this campaign.", tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection', type: 'selection',
placeholder: "Select a segment", placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments", id: "mailpoet_segments",
endpoint: "segments", endpoint: "segments",
multiple: true, multiple: true,
@ -44,18 +43,18 @@ define(
}, },
validation: { validation: {
'data-parsley-required': true, 'data-parsley-required': true,
'data-parsley-required-message': 'You need to select a segment.' 'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
} }
}, },
{ {
name: 'sender', name: 'sender',
label: 'Sender', label: MailPoet.I18n.t('sender'),
tip: "Name & email of yourself or your company.", tip: MailPoet.I18n.t('senderTip'),
fields: [ fields: [
{ {
name: 'sender_name', name: 'sender_name',
type: 'text', type: 'text',
placeholder: 'John Doe', placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '', defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: { validation: {
'data-parsley-required': true 'data-parsley-required': true
@ -64,7 +63,7 @@ define(
{ {
name: 'sender_address', name: 'sender_address',
type: 'text', type: 'text',
placeholder: 'john.doe@email.com', placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '', defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: { validation: {
'data-parsley-required': true, 'data-parsley-required': true,
@ -75,21 +74,20 @@ define(
}, },
{ {
name: 'reply-to', name: 'reply-to',
label: 'Reply-to', label: MailPoet.I18n.t('replyTo'),
tip: 'When the subscribers hit "reply" this is who will receive their '+ tip: MailPoet.I18n.t('replyToTip'),
'email.',
inline: true, inline: true,
fields: [ fields: [
{ {
name: 'reply_to_name', name: 'reply_to_name',
type: 'text', type: 'text',
placeholder: 'John Doe', placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '', defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
}, },
{ {
name: 'reply_to_address', name: 'reply_to_address',
type: 'text', type: 'text',
placeholder: 'john.doe@email.com', placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : '' defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
}, },
] ]
@ -98,10 +96,10 @@ define(
var messages = { var messages = {
onUpdate: function() { onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!'); MailPoet.Notice.success(MailPoet.I18n.t('newsletterUpdated'));
}, },
onCreate: function() { onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!'); MailPoet.Notice.success(MailPoet.I18n.t('newsletterAdded'));
} }
}; };
@ -138,15 +136,14 @@ define(
if(response.result === true) { if(response.result === true) {
this.history.pushState(null, '/'); this.history.pushState(null, '/');
MailPoet.Notice.success( MailPoet.Notice.success(
'The newsletter is being sent...' MailPoet.I18n.t('newsletterIsBeingSent')
); );
} else { } else {
if(response.errors) { if(response.errors) {
MailPoet.Notice.error(response.errors); MailPoet.Notice.error(response.errors);
} else { } else {
MailPoet.Notice.error( MailPoet.Notice.error(
'An error occurred while trying to send. '+ MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
'<a href="?page=mailpoet-settings">Check your settings.</a>'
); );
} }
} }
@ -157,7 +154,7 @@ define(
render: function() { render: function() {
return ( return (
<div> <div>
<h1>Final step: last details</h1> <h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
<Breadcrumb step="send" /> <Breadcrumb step="send" />
@ -174,18 +171,18 @@ define(
className="button button-primary" className="button button-primary"
type="button" type="button"
onClick={ this.handleSend } onClick={ this.handleSend }
value="Send" /> value={MailPoet.I18n.t('send')} />
&nbsp; &nbsp;
<input <input
className="button button-secondary" className="button button-secondary"
type="submit" type="submit"
value="Save as draft and close" /> value={MailPoet.I18n.t('saveDraftAndClose')} />
&nbsp;or simply&nbsp; &nbsp;{MailPoet.I18n.t('orSimply')}&nbsp;
<a <a
href={ href={
'?page=mailpoet-newsletter-editor&id='+this.props.params.id '?page=mailpoet-newsletter-editor&id='+this.props.params.id
}> }>
go back to design {MailPoet.I18n.t('goBackToDesign')}
</a>. </a>.
</p> </p>
</Form> </Form>
@ -196,4 +193,4 @@ define(
return NewsletterSend; return NewsletterSend;
} }
); );

View File

@ -54,7 +54,7 @@ define(
try { try {
saveTemplate(JSON.parse(e.target.result)); saveTemplate(JSON.parse(e.target.result));
} catch (err) { } catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.'); MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
} }
}.bind(this); }.bind(this);
@ -63,15 +63,15 @@ define(
render: function() { render: function() {
return ( return (
<div> <div>
<h2>Import a template</h2> <h2>{MailPoet.I18n.t('importTemplateTitle')}</h2>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" /> <input type="file" placeholder={MailPoet.I18n.t('selectJsonFileToUpload')} ref="templateFile" />
<p className="submit"> <p className="submit">
<input <input
className="button button-primary" className="button button-primary"
type="submit" type="submit"
value="Upload" /> value={MailPoet.I18n.t('upload')} />
</p> </p>
</form> </form>
</div> </div>
@ -108,9 +108,9 @@ define(
response = [ response = [
{ {
name: name:
"MailPoet's Guide", MailPoet.I18n.t('mailpoetGuideTemplateTitle'),
description: description:
"This is the standard template that comes with MailPoet.", MailPoet.I18n.t('mailpoetGuideTemplateDescription'),
readonly: "1" readonly: "1"
} }
] ]
@ -155,7 +155,9 @@ define(
this.setState({ loading: true }); this.setState({ loading: true });
if( if(
window.confirm( window.confirm(
'You are about to delete the template named "'+ template.name +'"' (
MailPoet.I18n.t('confirmTemplateDeletion')
).replace("%$1s", template.name)
) )
) { ) {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
@ -187,7 +189,7 @@ define(
href="javascript:;" href="javascript:;"
onClick={ this.handleDeleteTemplate.bind(null, template) } onClick={ this.handleDeleteTemplate.bind(null, template) }
> >
Delete {MailPoet.I18n.t('delete')}
</a> </a>
</div> </div>
), thumbnail = ''; ), thumbnail = '';
@ -218,7 +220,7 @@ define(
className="button button-primary" className="button button-primary"
onClick={ this.handleSelectTemplate.bind(null, template) } onClick={ this.handleSelectTemplate.bind(null, template) }
> >
Select {MailPoet.I18n.t('select')}
</a> </a>
&nbsp; &nbsp;
<a <a
@ -226,7 +228,7 @@ define(
className="button button-secondary" className="button button-secondary"
onClick={ this.handlePreviewTemplate.bind(null, template) } onClick={ this.handlePreviewTemplate.bind(null, template) }
> >
Preview {MailPoet.I18n.t('preview')}
</a> </a>
</div> </div>
{ (template.readonly === "1") ? false : deleteLink } { (template.readonly === "1") ? false : deleteLink }
@ -242,7 +244,7 @@ define(
return ( return (
<div> <div>
<h1>Select a template</h1> <h1>{MailPoet.I18n.t('selectTemplateTitle')}</h1>
<Breadcrumb step="template" /> <Breadcrumb step="template" />

View File

@ -26,7 +26,7 @@ define(
action: 'create', action: 'create',
data: { data: {
type: type, type: type,
subject: 'Draft newsletter', subject: MailPoet.I18n.t('draftNewsletterTitle'),
} }
}).done(function(response) { }).done(function(response) {
if(response.result && response.newsletter.id) { if(response.result && response.newsletter.id) {
@ -43,7 +43,7 @@ define(
render: function() { render: function() {
return ( return (
<div> <div>
<h1>Pick a type of campaign</h1> <h1>{MailPoet.I18n.t('pickCampaignType')}</h1>
<Breadcrumb step="type" /> <Breadcrumb step="type" />
@ -52,10 +52,9 @@ define(
<div className="mailpoet_thumbnail"></div> <div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description"> <div className="mailpoet_description">
<h3>Newsletter</h3> <h3>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h3>
<p> <p>
Send a newsletter with images, buttons, dividers, {MailPoet.I18n.t('regularNewsletterTypeDescription')}
and social bookmarks. Or a simple email.
</p> </p>
</div> </div>
@ -64,7 +63,7 @@ define(
className="button button-primary" className="button button-primary"
onClick={ this.createNewsletter.bind(null, 'standard') } onClick={ this.createNewsletter.bind(null, 'standard') }
> >
Create {MailPoet.I18n.t('create')}
</a> </a>
</div> </div>
</li> </li>
@ -73,9 +72,9 @@ define(
<div className="mailpoet_thumbnail"></div> <div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description"> <div className="mailpoet_description">
<h3>Welcome email</h3> <h3>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h3>
<p> <p>
Send an email for new users. {MailPoet.I18n.t('welcomeNewsletterTypeDescription')}
</p> </p>
</div> </div>
@ -84,7 +83,7 @@ define(
className="button button-primary" className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'welcome') } onClick={ this.setupNewsletter.bind(null, 'welcome') }
> >
Set up {MailPoet.I18n.t('setUp')}
</a> </a>
</div> </div>
</li> </li>
@ -93,9 +92,9 @@ define(
<div className="mailpoet_thumbnail"></div> <div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description"> <div className="mailpoet_description">
<h3>Post notifications</h3> <h3>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h3>
<p> <p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like. {MailPoet.I18n.t('postNotificationsNewsletterTypeDescription')}
</p> </p>
</div> </div>
@ -104,7 +103,7 @@ define(
className="button button-primary" className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') } onClick={ this.setupNewsletter.bind(null, 'notification') }
> >
Set up {MailPoet.I18n.t('setUp')}
</a> </a>
</div> </div>
</li> </li>

View File

@ -25,11 +25,11 @@ define(
var intervalField = { var intervalField = {
name: 'interval', name: 'interval',
values: { values: {
'daily': 'Once a day at...', 'daily': MailPoet.I18n.t('daily'),
'weekly': 'Weekly on...', 'weekly': MailPoet.I18n.t('weekly'),
'monthly': 'Monthly on the...', 'monthly': MailPoet.I18n.t('monthly'),
'nthWeekDay': 'Monthly every...', 'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
'immediately': 'Immediately.', 'immediately': MailPoet.I18n.t('immediately'),
}, },
}; };
@ -53,13 +53,13 @@ define(
var weekDayField = { var weekDayField = {
name: 'weekDay', name: 'weekDay',
values: { values: {
0: 'Monday', 0: MailPoet.I18n.t('sunday'),
1: 'Tuesday', 1: MailPoet.I18n.t('monday'),
2: 'Wednesday', 2: MailPoet.I18n.t('tuesday'),
3: 'Thursday', 3: MailPoet.I18n.t('wednesday'),
4: 'Friday', 4: MailPoet.I18n.t('thursday'),
5: 'Saturday', 5: MailPoet.I18n.t('friday'),
6: 'Sunday', 6: MailPoet.I18n.t('saturday')
}, },
}; };
@ -69,14 +69,19 @@ define(
values: _.object(_.map( values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }), _.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) { function(day) {
var suffixes = { var labels = {
0: 'st', 0: MailPoet.I18n.t('first'),
1: 'nd', 1: MailPoet.I18n.t('second'),
2: 'rd' 2: MailPoet.I18n.t('third')
}; },
var suffix = suffixes[day] || 'th'; label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
}
return [day, (day + 1).toString() + suffix]; return [day, label];
}, },
)), )),
}; };
@ -84,10 +89,10 @@ define(
var nthWeekDayField = { var nthWeekDayField = {
name: 'nthWeekDay', name: 'nthWeekDay',
values: { values: {
'0': '1st', '1': MailPoet.I18n.t('first'),
'1': '2nd', '2': MailPoet.I18n.t('second'),
'2': '3rd', '3': MailPoet.I18n.t('third'),
'3': 'last', 'L': MailPoet.I18n.t('last'),
}, },
}; };
@ -99,9 +104,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) {
@ -197,7 +202,7 @@ define(
return ( return (
<div> <div>
<h1>Post notifications</h1> <h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" /> <Breadcrumb step="type" />
<Select <Select
@ -215,7 +220,7 @@ define(
className="button button-primary" className="button button-primary"
type="button" type="button"
onClick={ this.handleNext } onClick={ this.handleNext }
value="Next" /> value={MailPoet.I18n.t('next')} />
</p> </p>
</div> </div>
); );

View File

@ -47,7 +47,7 @@ define(
render: function() { render: function() {
return ( return (
<div> <div>
<h1>Newsletter</h1> <h1>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" /> <Breadcrumb step="type" />
</div> </div>
); );

View File

@ -28,8 +28,8 @@ define(
var events = { var events = {
name: 'event', name: 'event',
values: { values: {
'segment': 'When someone subscribes to the list...', 'segment': MailPoet.I18n.t('onSubscriptionToList'),
'user': 'When a new Wordrpess user is added to your site...', 'user': MailPoet.I18n.t('onWordpressUserRegistration'),
} }
}; };
@ -57,10 +57,10 @@ define(
var afterTimeTypeField = { var afterTimeTypeField = {
name: 'afterTimeType', name: 'afterTimeType',
values: { values: {
'immediate': 'immediately', 'immediate': MailPoet.I18n.t('delayImmediately'),
'hours': 'hour(s) after', 'hours': MailPoet.I18n.t('delayHoursAfter'),
'days': 'day(s) after', 'days': MailPoet.I18n.t('delayDaysAfter'),
'weeks': 'week(s) after', 'weeks': MailPoet.I18n.t('delayWeeksAfter'),
} }
}; };
@ -152,9 +152,11 @@ define(
} }
return ( return (
<div> <div>
<h1>Welcome email</h1> <h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" /> <Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
<Select <Select
field={events} field={events}
item={this.state} item={this.state}
@ -174,7 +176,7 @@ define(
className="button button-primary" className="button button-primary"
type="button" type="button"
onClick={ this.handleNext } onClick={ this.handleNext }
value="Next" /> value={MailPoet.I18n.t('next')} />
</p> </p>
</div> </div>
); );

View File

@ -20,13 +20,18 @@ function(
$('form.mailpoet_form').each(function() { $('form.mailpoet_form').each(function() {
var form = $(this); var form = $(this);
form.parsley().on('form:submit', function(parsley) { form.parsley().on('form:validated', function(parsley) {
var data = form.serializeObject() || {};
// clear messages // clear messages
form.find('.mailpoet_message').html(''); form.find('.mailpoet_message').html('');
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
});
form.parsley().on('form:submit', function(parsley) {
var data = form.serializeObject() || {};
// check if we're on the same domain // check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) { if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request // non ajax post request
@ -68,6 +73,11 @@ function(
// reset validation // reset validation
parsley.reset(); parsley.reset();
} }
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
}); });
} }
return false; return false;

View File

@ -15,22 +15,22 @@ define(
let fields = [ let fields = [
{ {
name: 'name', name: 'name',
label: 'Name', label: MailPoet.I18n.t('name'),
type: 'text' type: 'text'
}, },
{ {
name: 'description', name: 'description',
label: 'Description', label: MailPoet.I18n.t('description'),
type: 'textarea' type: 'textarea'
} }
]; ];
const messages = { const messages = {
onUpdate: function() { onUpdate: function() {
MailPoet.Notice.success('Segment successfully updated!'); MailPoet.Notice.success(MailPoet.I18n.t('segmentUpdated'));
}, },
onCreate: function() { onCreate: function() {
MailPoet.Notice.success('Segment successfully added!'); MailPoet.Notice.success(MailPoet.I18n.t('segmentAdded'));
} }
}; };
@ -42,7 +42,7 @@ define(
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Segment {MailPoet.I18n.t('segment')}
</h2> </h2>
<Form <Form

View File

@ -10,32 +10,32 @@ import Listing from 'listing/listing.jsx'
var columns = [ var columns = [
{ {
name: 'name', name: 'name',
label: 'Name', label: MailPoet.I18n.t('name'),
sortable: true sortable: true
}, },
{ {
name: 'description', name: 'description',
label: 'Description', label: MailPoet.I18n.t('description'),
sortable: false sortable: false
}, },
{ {
name: 'subscribed', name: 'subscribed',
label: 'Subscribed', label: MailPoet.I18n.t('subscribed'),
sortable: false sortable: false
}, },
{ {
name: 'unconfirmed', name: 'unconfirmed',
label: 'Unconfirmed', label: MailPoet.I18n.t('unconfirmed'),
sortable: false sortable: false
}, },
{ {
name: 'unsubscribed', name: 'unsubscribed',
label: 'Unsubscribed', label: MailPoet.I18n.t('unsubscribed'),
sortable: false sortable: false
}, },
{ {
name: 'created_at', name: 'created_at',
label: 'Created on', label: MailPoet.I18n.t('createdOn'),
sortable: true sortable: true
} }
]; ];
@ -47,11 +47,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 segment was moved to the trash.' MailPoet.I18n.t('oneSegmentTrashed')
); );
} else { } else {
message = ( message = (
'%$1d segments were moved to the trash.' MailPoet.I18n.t('multipleSegmentsTrashed')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -62,11 +62,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 segment was permanently deleted.' MailPoet.I18n.t('oneSegmentDeleted')
); );
} else { } else {
message = ( message = (
'%$1d segments were permanently deleted.' MailPoet.I18n.t('multipleSegmentsDeleted')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -77,11 +77,11 @@ const messages = {
if(count === 1) { if(count === 1) {
message = ( message = (
'1 segment has been restored from the trash.' MailPoet.I18n.t('oneSegmentRestored')
); );
} else { } else {
message = ( message = (
'%$1d segments have been restored from the trash.' MailPoet.I18n.t('multipleSegmentsRestored')
).replace('%$1d', count); ).replace('%$1d', count);
} }
MailPoet.Notice.success(message); MailPoet.Notice.success(message);
@ -91,10 +91,10 @@ const messages = {
const item_actions = [ const item_actions = [
{ {
name: 'edit', name: 'edit',
label: 'Edit', label: MailPoet.I18n.t('edit'),
link: function(item) { link: function(item) {
return ( return (
<Link to={ `/edit/${item.id}` }>Edit</Link> <Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
); );
}, },
display: function(segment) { display: function(segment) {
@ -111,7 +111,7 @@ const item_actions = [
data: item.id data: item.id
}).done(function(response) { }).done(function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name) (MailPoet.I18n.t('listDuplicated')).replace('%$1s', response.name)
); );
refresh(); refresh();
}); });
@ -122,7 +122,7 @@ const item_actions = [
}, },
{ {
name: 'synchronize_segment', name: 'synchronize_segment',
label: 'Update', label: MailPoet.I18n.t('update'),
className: 'update', className: 'update',
onClick: function(item, refresh) { onClick: function(item, refresh) {
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);
@ -133,7 +133,7 @@ const item_actions = [
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
if(response === true) { if(response === true) {
MailPoet.Notice.success( MailPoet.Notice.success(
('List "%$1s" has been synchronized.').replace('%$1s', item.name) (MailPoet.I18n.t('listSynchronized')).replace('%$1s', item.name)
); );
refresh(); refresh();
} }
@ -147,7 +147,7 @@ const item_actions = [
name: 'view_subscribers', name: 'view_subscribers',
link: function(item) { link: function(item) {
return ( return (
<a href={ item.subscribers_url }>View subscribers</a> <a href={ item.subscribers_url }>{MailPoet.I18n.t('viewSubscribers')}</a>
); );
} }
}, },
@ -199,7 +199,7 @@ const SegmentList = React.createClass({
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link> {MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2> </h2>
<Listing <Listing
@ -219,4 +219,4 @@ const SegmentList = React.createClass({
} }
}); });
module.exports = SegmentList; module.exports = SegmentList;

View File

@ -34,13 +34,16 @@ define(
// show sending methods // show sending methods
jQuery('.mailpoet_sending_methods').fadeIn(); jQuery('.mailpoet_sending_methods').fadeIn();
} else { } else {
// hide DKIM option when using MailPoet's API // toggle SPF/DKIM (hidden if the sending method is MailPoet)
jQuery('#mailpoet_mta_dkim')[ jQuery('#mailpoet_mta_spf')[
(group === 'mailpoet') (group === 'mailpoet')
? 'hide' ? 'hide'
: 'show' : 'show'
](); ]();
// (HIDDEN FOR BETA)
jQuery('#mailpoet_mta_dkim').hide();
// hide sending methods // hide sending methods
jQuery('.mailpoet_sending_methods').hide(); jQuery('.mailpoet_sending_methods').hide();

View File

@ -14,34 +14,34 @@ define(
var fields = [ var fields = [
{ {
name: 'email', name: 'email',
label: 'E-mail', label: MailPoet.I18n.t('email'),
type: 'text' type: 'text'
}, },
{ {
name: 'first_name', name: 'first_name',
label: 'Firstname', label: MailPoet.I18n.t('firstname'),
type: 'text' type: 'text'
}, },
{ {
name: 'last_name', name: 'last_name',
label: 'Lastname', label: MailPoet.I18n.t('lastname'),
type: 'text' type: 'text'
}, },
{ {
name: 'status', name: 'status',
label: 'Status', label: MailPoet.I18n.t('status'),
type: 'select', type: 'select',
values: { values: {
'unconfirmed': 'Unconfirmed', 'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'subscribed': 'Subscribed', 'subscribed': MailPoet.I18n.t('subscribed'),
'unsubscribed': 'Unsubscribed' 'unsubscribed': MailPoet.I18n.t('unsubscribed')
} }
}, },
{ {
name: 'segments', name: 'segments',
label: 'Lists', label: MailPoet.I18n.t('lists'),
type: 'selection', type: 'selection',
placeholder: "Select a list", placeholder: MailPoet.I18n.t('selectList'),
endpoint: "segments", endpoint: "segments",
multiple: true, multiple: true,
selected: function(subscriber) { selected: function(subscriber) {
@ -69,7 +69,13 @@ define(
if (subscription.status === 'unsubscribed') { if (subscription.status === 'unsubscribed') {
const unsubscribed_at = MailPoet.Date const unsubscribed_at = MailPoet.Date
.format(subscription.updated_at); .format(subscription.updated_at);
label += ' (Unsubscribed on '+unsubscribed_at+')'; label += ' (%$1s)'.replace(
'%$1s',
MailPoet.I18n.t('unsubscribedOn').replace(
'%$1s',
unsubscribed_at
)
);
} }
} }
}); });
@ -99,10 +105,10 @@ define(
var messages = { var messages = {
onUpdate: function() { onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!'); MailPoet.Notice.success(MailPoet.I18n.t('subscriberUpdated'));
}, },
onCreate: function() { onCreate: function() {
MailPoet.Notice.success('Subscriber successfully added!'); MailPoet.Notice.success(MailPoet.I18n.t('subscriberAdded'));
} }
}; };
@ -116,7 +122,7 @@ define(
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Subscriber {MailPoet.I18n.t('subscriber')}
</h2> </h2>
<Form <Form

View File

@ -150,7 +150,7 @@ define(
if (response.result === false) { if (response.result === false) {
MailPoet.Notice.error(response.errors); MailPoet.Notice.error(response.errors);
} else { } else {
resultMessage = MailPoetI18n.exportMessage resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>') .replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >') .replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>'); .replace('[/link]', '</a>');
@ -162,7 +162,7 @@ define(
.error(function (error) { .error(function (error) {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.' MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
); );
}); });
}); });

View File

@ -7,7 +7,8 @@ define(
'handlebars', 'handlebars',
'papaparse', 'papaparse',
'select2', 'select2',
'asyncqueue' 'asyncqueue',
'xss'
], ],
function ( function (
Backbone, Backbone,
@ -16,7 +17,8 @@ define(
MailPoet, MailPoet,
Handlebars, Handlebars,
Papa, Papa,
AsyncQueue AsyncQueue,
xss
) { ) {
if (!jQuery('#mailpoet_subscribers_import').length) { if (!jQuery('#mailpoet_subscribers_import').length) {
return; return;
@ -126,7 +128,7 @@ define(
// get an approximate size of textarea paste in bytes // get an approximate size of textarea paste in bytes
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(MailPoet.I18n.t('maxPostSizeNotice'), {
timeout: noticeTimeout, timeout: noticeTimeout,
}); });
return; return;
@ -146,7 +148,7 @@ define(
var ext = this.value.match(/\.(.+)$/); var ext = this.value.match(/\.(.+)$/);
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(MailPoet.I18n.t('wrongFileFormat'), {
timeout: noticeTimeout, timeout: noticeTimeout,
}); });
} }
@ -210,7 +212,7 @@ define(
.removeClass() .removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok'); .addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok');
if (!response.data) { if (!response.data) {
jQuery('.mailpoet_mailchimp-key-status').html(MailPoetI18n.noMailChimpLists); jQuery('.mailpoet_mailchimp-key-status').html(MailPoet.I18n.t('noMailChimpLists'));
mailChimpListsContainerElement.hide(); mailChimpListsContainerElement.hide();
toggleNextStepButton(mailChimpProcessButtonElement, 'off'); toggleNextStepButton(mailChimpProcessButtonElement, 'off');
} else { } else {
@ -221,7 +223,7 @@ define(
}).error(function (error) { }).error(function (error) {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout, timeout: noticeTimeout,
} }
); );
@ -256,7 +258,7 @@ define(
}).error(function () { }).error(function () {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + result.statusText.toLowerCase() + '.', { MailPoet.I18n.t('serverError') + result.statusText.toLowerCase() + '.', {
timeout: noticeTimeout, timeout: noticeTimeout,
} }
); );
@ -348,14 +350,14 @@ define(
comments: advancedOptionComments, comments: advancedOptionComments,
error: function () { error: function () {
MailPoet.Notice.hide(); MailPoet.Notice.hide();
MailPoet.Notice.error(MailPoetI18n.dataProcessingError, { MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'), {
timeout: noticeTimeout, 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
@ -429,8 +431,8 @@ define(
} }
else { else {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
var errorNotice = MailPoetI18n.noValidRecords; var errorNotice = MailPoet.I18n.t('noValidRecords');
errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink); errorNotice = errorNotice.replace('[link]', MailPoet.I18n.t('csvKBLink'));
errorNotice = errorNotice.replace('[/link]', '</a>'); errorNotice = errorNotice.replace('[/link]', '</a>');
MailPoet.Notice.error(errorNotice, { MailPoet.Notice.error(errorNotice, {
timeout: noticeTimeout, timeout: noticeTimeout,
@ -499,17 +501,17 @@ define(
} }
var import_results = { var import_results = {
notice: MailPoetI18n.importNoticeSkipped.replace( notice: MailPoet.I18n.t('importNoticeSkipped').replace(
'%1$s', '%1$s',
'<strong>' + (subscribers.invalid.length + subscribers.duplicate.length) + '</strong>' '<strong>' + (subscribers.invalid.length + subscribers.duplicate.length) + '</strong>'
), ),
invalid: (subscribers.invalid.length) invalid: (subscribers.invalid.length)
? MailPoetI18n.importNoticeInvalid ? MailPoet.I18n.t('importNoticeInvalid')
.replace('%1$s', '<strong>' + subscribers.invalid.length + '</strong>') .replace('%1$s', '<strong>' + subscribers.invalid.length + '</strong>')
.replace('%2$s', subscribers.invalid.join(', ')) .replace('%2$s', subscribers.invalid.join(', '))
: null, : null,
duplicate: (subscribers.duplicate.length) duplicate: (subscribers.duplicate.length)
? MailPoetI18n.importNoticeDuplicate ? MailPoet.I18n.t('importNoticeDuplicate')
.replace('%1$s', '<strong>' + subscribers.duplicate.length + '</strong>') .replace('%1$s', '<strong>' + subscribers.duplicate.length + '</strong>')
.replace('%2$s', subscribers.duplicate.join(', ')) .replace('%2$s', subscribers.duplicate.join(', '))
: null : null
@ -525,8 +527,8 @@ define(
jQuery(details).toggle(); jQuery(details).toggle();
this.text = this.text =
(jQuery(details).is(":visible")) (jQuery(details).is(":visible"))
? MailPoetI18n.hideDetails ? MailPoet.I18n.t('hideDetails')
: MailPoetI18n.showDetails; : MailPoet.I18n.t('showDetails');
}); });
// show available segments // show available segments
@ -562,7 +564,7 @@ define(
var segmentSelectionNotice = jQuery('[data-id="notice_segmentSelection"]'); var segmentSelectionNotice = jQuery('[data-id="notice_segmentSelection"]');
if (!this.value) { if (!this.value) {
if (!segmentSelectionNotice.length) { if (!segmentSelectionNotice.length) {
MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, { MailPoet.Notice.error(MailPoet.I18n.t('segmentSelectionRequired'), {
static: true, static: true,
timeout: noticeTimeout, timeout: noticeTimeout,
scroll: true, scroll: true,
@ -582,7 +584,7 @@ define(
jQuery('.mailpoet_create_segment').click(function () { jQuery('.mailpoet_create_segment').click(function () {
MailPoet.Modal.popup({ MailPoet.Modal.popup({
title: MailPoetI18n.addNewList, title: MailPoet.I18n.t('addNewList'),
template: jQuery('#new_segment_template').html() template: jQuery('#new_segment_template').html()
}) })
jQuery('#new_segment_name').keypress(function (e) { jQuery('#new_segment_name').keypress(function (e) {
@ -642,7 +644,7 @@ define(
else { else {
MailPoet.Modal.close(); MailPoet.Modal.close();
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.segmentCreateError + response.message + '.', { MailPoet.I18n.t('segmentCreateError') + response.message + '.', {
timeout: noticeTimeout, timeout: noticeTimeout,
} }
); );
@ -651,7 +653,7 @@ define(
.error(function (error) { .error(function (error) {
MailPoet.Modal.close(); MailPoet.Modal.close();
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout timeout: noticeTimeout
} }
); );
@ -768,7 +770,7 @@ define(
selectEvent.preventDefault(); selectEvent.preventDefault();
jQuery(selectElement).select2('close'); jQuery(selectElement).select2('close');
MailPoet.Modal.popup({ MailPoet.Modal.popup({
title: MailPoetI18n.addNewColumn, title: MailPoet.I18n.t('addNewColumn'),
template: jQuery('#new_column_template').html() template: jQuery('#new_column_template').html()
}); });
jQuery('#new_column_name').keypress(function (e) { jQuery('#new_column_name').keypress(function (e) {
@ -832,7 +834,7 @@ define(
// if this is the first custom column, create an "optgroup" // if this is the first custom column, create an "optgroup"
if (mailpoetColumnsSelect2.length === 2) { if (mailpoetColumnsSelect2.length === 2) {
mailpoetColumnsSelect2.push({ mailpoetColumnsSelect2.push({
'name': MailPoetI18n.userColumns, 'name': MailPoet.I18n.t('userColumns'),
'children': [] 'children': []
}); });
} }
@ -858,7 +860,7 @@ define(
filterSubscribers(); filterSubscribers();
} }
else { else {
MailPoet.Notice.error(MailPoetI18n.customFieldCreateError, { MailPoet.Notice.error(MailPoet.I18n.t('customFieldCreateError'), {
timeout: noticeTimeout, timeout: noticeTimeout,
}); });
} }
@ -867,7 +869,7 @@ define(
.error(function (error) { .error(function (error) {
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);
MailPoet.Notice.error( MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', { MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout, timeout: noticeTimeout,
} }
); );
@ -888,7 +890,7 @@ define(
// if another column has the same value and it's not an 'ignore', prompt user // if another column has the same value and it's not an 'ignore', prompt user
if (elementId === selectedOptionId if (elementId === selectedOptionId
&& elementId !== 'ignore') { && elementId !== 'ignore') {
if (confirm(MailPoetI18n.selectedValueAlreadyMatched + ' ' + MailPoetI18n.confirmCorrespondingColumn)) { if (confirm(MailPoet.I18n.t('selectedValueAlreadyMatched') + ' ' + MailPoet.I18n.t('confirmCorrespondingColumn'))) {
jQuery(element).data('column-id', 'ignore'); jQuery(element).data('column-id', 'ignore');
} }
else { else {
@ -931,7 +933,7 @@ define(
if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn])) { if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn])) {
preventNextStep = true; preventNextStep = true;
if (!jQuery('[data-id="notice_invalidEmail"]').length) { if (!jQuery('[data-id="notice_invalidEmail"]').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, { MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidElement'), {
static: true, static: true,
timeout: noticeTimeout, timeout: noticeTimeout,
scroll: true, scroll: true,
@ -951,18 +953,18 @@ define(
date = new Date(rowData.replace(/-/g, '/')), // IE doesn't like date = new Date(rowData.replace(/-/g, '/')), // IE doesn't like
// dashes as date separators // dashes as date separators
month_name = [ month_name = [
MailPoetI18n.january, MailPoet.I18n.t('january'),
MailPoetI18n.february, MailPoet.I18n.t('february'),
MailPoetI18n.march, MailPoet.I18n.t('march'),
MailPoetI18n.april, MailPoet.I18n.t('april'),
MailPoetI18n.may, MailPoet.I18n.t('may'),
MailPoetI18n.june, MailPoet.I18n.t('june'),
MailPoetI18n.july, MailPoet.I18n.t('july'),
MailPoetI18n.august, MailPoet.I18n.t('august'),
MailPoetI18n.september, MailPoet.I18n.t('september'),
MailPoetI18n.october, MailPoet.I18n.t('october'),
MailPoetI18n.november, MailPoet.I18n.t('november'),
MailPoetI18n.december MailPoet.I18n.t('december')
]; ];
if (position !== fillterPosition) { if (position !== fillterPosition) {
@ -975,8 +977,8 @@ define(
if (rowData.trim() === '') { if (rowData.trim() === '') {
data[matchedColumn] = data[matchedColumn] =
'<span class="mailpoet_data_match mailpoet_import_error" title="' '<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoetI18n.noDateFieldMatch + '">' + MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoetI18n.emptyDate + MailPoet.I18n.t('emptyDate')
+ '</span>'; + '</span>';
preventNextStep = true; preventNextStep = true;
return; return;
@ -993,25 +995,25 @@ define(
+ ((date.getMinutes() < 10 ? '0' : '') + ((date.getMinutes() < 10 ? '0' : '')
+ date.getMinutes()) + ' ' + date.getMinutes()) + ' '
+ ((date.getHours() >= 12) + ((date.getHours() >= 12)
? MailPoetI18n.pm ? MailPoet.I18n.t('pm')
: MailPoetI18n.am : MailPoet.I18n.t('am')
); );
data[matchedColumn] += data[matchedColumn] +=
'<span class="mailpoet_data_match" title="' '<span class="mailpoet_data_match" title="'
+ MailPoetI18n.verifyDateMatch + '">' + MailPoet.I18n.t('verifyDateMatch') + '">'
+ date + '</span>'; + date + '</span>';
} }
else { else {
data[matchedColumn] += data[matchedColumn] +=
'<span class="mailpoet_data_match mailpoet_import_error" title="' '<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoetI18n.noDateFieldMatch + '">' + MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoetI18n.dateMatchError + '</span>'; + MailPoet.I18n.t('dateMatchError') + '</span>';
preventNextStep = true; preventNextStep = true;
} }
} }
}); });
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) { if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidDate, { MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidDate'), {
static: true, static: true,
timeout: noticeTimeout, timeout: noticeTimeout,
scroll: true, scroll: true,
@ -1112,7 +1114,7 @@ define(
}) })
.error(function (error) { .error(function (error) {
importResults.errors.push( importResults.errors.push(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.' MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
); );
queue.run(); queue.run();
}); });
@ -1169,12 +1171,12 @@ define(
exportMenuElement = jQuery('span.mailpoet_export'), exportMenuElement = jQuery('span.mailpoet_export'),
importResults = { importResults = {
created: (importData.step2.created) created: (importData.step2.created)
? MailPoetI18n.subscribersCreated ? MailPoet.I18n.t('subscribersCreated')
.replace('%1$s', '<strong>' + importData.step2.created + '</strong>') .replace('%1$s', '<strong>' + importData.step2.created + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"') .replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false, : false,
updated: (importData.step2.updated) updated: (importData.step2.updated)
? MailPoetI18n.subscribersUpdated ? MailPoet.I18n.t('subscribersUpdated')
.replace('%1$s', '<strong>' + importData.step2.updated + '</strong>') .replace('%1$s', '<strong>' + importData.step2.updated + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"') .replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false, : false,
@ -1210,4 +1212,4 @@ define(
Backbone.history.start(); Backbone.history.start();
} }
}); });
}); });

View File

@ -11,28 +11,28 @@ import Selection from 'form/fields/selection.jsx'
const columns = [ const columns = [
{ {
name: 'email', name: 'email',
label: 'Subscriber', label: MailPoet.I18n.t('subscriber'),
sortable: true sortable: true
}, },
{ {
name: 'status', name: 'status',
label: 'Status', label: MailPoet.I18n.t('status'),
sortable: true sortable: true
}, },
{ {
name: 'segments', name: 'segments',
label: 'Lists', label: MailPoet.I18n.t('lists'),
sortable: false sortable: false
}, },
{ {
name: 'created_at', name: 'created_at',
label: 'Subscribed on', label: MailPoet.I18n.t('subscribedOn'),
sortable: true sortable: true
}, },
{ {
name: 'updated_at', name: 'updated_at',
label: 'Last modified on', label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true sortable: true
}, },
]; ];
@ -43,11 +43,11 @@ const messages = {
var message = null; var message = null;
if(~~response === 1) { if(~~response === 1) {
message = ( message = (
'1 subscriber was moved to the trash.' MailPoet.I18n.t('oneSubscriberTrashed')
); );
} else if(~~response > 1) { } else if(~~response > 1) {
message = ( message = (
'%$1d subscribers were moved to the trash.' MailPoet.I18n.t('multipleSubscribersTrashed')
).replace('%$1d', ~~response); ).replace('%$1d', ~~response);
} }
@ -61,11 +61,11 @@ const messages = {
var message = null; var message = null;
if(~~response === 1) { if(~~response === 1) {
message = ( message = (
'1 subscriber was permanently deleted.' MailPoet.I18n.t('oneSubscriberDeleted')
); );
} else if(~~response > 1) { } else if(~~response > 1) {
message = ( message = (
'%$1d subscribers were permanently deleted.' MailPoet.I18n.t('multipleSubscribersDeleted')
).replace('%$1d', ~~response); ).replace('%$1d', ~~response);
} }
@ -79,11 +79,11 @@ const messages = {
var message = null; var message = null;
if(~~response === 1) { if(~~response === 1) {
message = ( message = (
'1 subscriber has been restored from the trash.' MailPoet.I18n.t('oneSubscriberRestored')
); );
} else if(~~response > 1) { } else if(~~response > 1) {
message = ( message = (
'%$1d subscribers have been restored from the trash.' MailPoet.I18n.t('multipleSubscribersRestored')
).replace('%$1d', ~~response); ).replace('%$1d', ~~response);
} }
@ -97,7 +97,7 @@ const messages = {
const bulk_actions = [ const bulk_actions = [
{ {
name: 'moveToList', name: 'moveToList',
label: 'Move to list...', label: MailPoet.I18n.t('moveToList'),
onSelect: function() { onSelect: function() {
let field = { let field = {
id: 'move_to_segment', id: 'move_to_segment',
@ -120,7 +120,7 @@ const bulk_actions = [
}, },
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.' MailPoet.I18n.t('multipleSubscribersMovedToList')
.replace('%$1d', ~~response.subscribers) .replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment) .replace('%$2s', response.segment)
); );
@ -128,7 +128,7 @@ const bulk_actions = [
}, },
{ {
name: 'addToList', name: 'addToList',
label: 'Add to list...', label: MailPoet.I18n.t('addToList'),
onSelect: function() { onSelect: function() {
let field = { let field = {
id: 'add_to_segment', id: 'add_to_segment',
@ -151,7 +151,7 @@ const bulk_actions = [
}, },
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.' MailPoet.I18n.t('multipleSubscribersAddedToList')
.replace('%$1d', ~~response.subscribers) .replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment) .replace('%$2s', response.segment)
); );
@ -159,7 +159,7 @@ const bulk_actions = [
}, },
{ {
name: 'removeFromList', name: 'removeFromList',
label: 'Remove from list...', label: MailPoet.I18n.t('removeFromList'),
onSelect: function() { onSelect: function() {
let field = { let field = {
id: 'remove_from_segment', id: 'remove_from_segment',
@ -182,7 +182,7 @@ const bulk_actions = [
}, },
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.' MailPoet.I18n.t('multipleSubscribersRemovedFromList')
.replace('%$1d', ~~response.subscribers) .replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment) .replace('%$2s', response.segment)
); );
@ -190,37 +190,37 @@ const bulk_actions = [
}, },
{ {
name: 'removeFromAllLists', name: 'removeFromAllLists',
label: 'Remove from all lists', label: MailPoet.I18n.t('removeFromAllLists'),
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.' MailPoet.I18n.t('multipleSubscribersRemovedFromAllLists')
.replace('%$1d', ~~response) .replace('%$1d', ~~response)
); );
} }
}, },
{ {
name: 'confirmUnconfirmed', name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed', label: MailPoet.I18n.t('confirmUnconfirmed'),
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d subscribers have been confirmed.' MailPoet.I18n.t('multipleSubscribersConfirmed')
.replace('%$1d', ~~response) .replace('%$1d', ~~response)
); );
} }
}, },
{ {
name: 'sendConfirmationEmail', name: 'sendConfirmationEmail',
label: 'Resend confirmation email', label: MailPoet.I18n.t('resendConfirmationEmail'),
onSuccess: function(response) { onSuccess: function(response) {
MailPoet.Notice.success( MailPoet.Notice.success(
'%$1d confirmation emails have been sent.' MailPoet.I18n.t('multipleConfirmationEmailsSent')
.replace('%$1d', ~~response) .replace('%$1d', ~~response)
); );
} }
}, },
{ {
name: 'trash', name: 'trash',
label: 'Trash', label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash onSuccess: messages.onTrash
} }
]; ];
@ -228,10 +228,10 @@ const bulk_actions = [
const item_actions = [ const item_actions = [
{ {
name: 'edit', name: 'edit',
label: 'Edit', label: MailPoet.I18n.t('edit'),
link: function(item) { link: function(item) {
return ( return (
<Link to={ `/edit/${item.id}` }>Edit</Link> <Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
); );
} }
}, },
@ -262,15 +262,15 @@ const SubscriberList = React.createClass({
switch(subscriber.status) { switch(subscriber.status) {
case 'subscribed': case 'subscribed':
status = 'Subscribed'; status = MailPoet.I18n.t('subscribed');
break; break;
case 'unconfirmed': case 'unconfirmed':
status = 'Unconfirmed'; status = MailPoet.I18n.t('unconfirmed');
break; break;
case 'unsubscribed': case 'unsubscribed':
status = 'Unsubscribed'; status = MailPoet.I18n.t('unsubscribed');
break; break;
} }
@ -302,7 +302,7 @@ const SubscriberList = React.createClass({
</span> </span>
<span <span
className="mailpoet_segments_unsubscribed" className="mailpoet_segments_unsubscribed"
title="Lists to which the subscriber was subscribed." title={MailPoet.I18n.t('listsToWhichSubscriberWasSubscribed')}
> >
{ unsubscribed_segments.join(', ') } { unsubscribed_segments.join(', ') }
</span> </span>
@ -334,16 +334,16 @@ const SubscriberList = React.createClass({
</p> </p>
{ actions } { actions }
</td> </td>
<td className="column" data-colname="Status"> <td className="column" data-colname={MailPoet.I18n.t('status')}>
{ status } { status }
</td> </td>
<td className="column" data-colname="Lists"> <td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Subscribed on"> <td className="column-date" data-colname={MailPoet.I18n.t('subscribedOn')}>
<abbr>{ MailPoet.Date.full(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={MailPoet.I18n.t('lastModifiedOn')}>
<abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr> <abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
</td> </td>
</div> </div>
@ -356,9 +356,9 @@ const SubscriberList = React.createClass({
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link> {MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">Import</a> <a className="add-new-h2" href="?page=mailpoet-import#step1">{MailPoet.I18n.t('import')}</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">Export</a> <a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">{MailPoet.I18n.t('export')}</a>
</h2> </h2>
<Listing <Listing
@ -378,4 +378,4 @@ const SubscriberList = React.createClass({
} }
}); });
module.exports = SubscriberList; module.exports = SubscriberList;

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"
}, },

331
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",
@ -401,16 +401,16 @@
}, },
{ {
"name": "soundasleep/html2text", "name": "soundasleep/html2text",
"version": "0.3.0", "version": "0.3.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/soundasleep/html2text.git", "url": "https://github.com/soundasleep/html2text.git",
"reference": "55be17ddbb7234650722f66816ba2b10f66e565f" "reference": "2a5fd94bf58b653a5a9d777ac6a5593f82d087db"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/soundasleep/html2text/zipball/55be17ddbb7234650722f66816ba2b10f66e565f", "url": "https://api.github.com/repos/soundasleep/html2text/zipball/2a5fd94bf58b653a5a9d777ac6a5593f82d087db",
"reference": "55be17ddbb7234650722f66816ba2b10f66e565f", "reference": "2a5fd94bf58b653a5a9d777ac6a5593f82d087db",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -447,7 +447,7 @@
"php", "php",
"text" "text"
], ],
"time": "2015-12-18 02:30:26" "time": "2016-02-25 00:00:36"
}, },
{ {
"name": "sunra/php-simple-html-dom-parser", "name": "sunra/php-simple-html-dom-parser",
@ -547,7 +547,7 @@
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.1.0", "version": "v1.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
@ -606,16 +606,16 @@
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "bc0b666903944858f4ffec01c4e50c63e5c276c0" "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/bc0b666903944858f4ffec01c4e50c63e5c276c0", "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"reference": "bc0b666903944858f4ffec01c4e50c63e5c276c0", "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -666,7 +666,7 @@
], ],
"description": "Symfony Translation Component", "description": "Symfony Translation Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41" "time": "2016-02-02 09:49:18"
}, },
{ {
"name": "tburry/pquery", "name": "tburry/pquery",
@ -785,41 +785,42 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "codeception/codeception", "name": "codeception/codeception",
"version": "2.1.6", "version": "2.1.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Codeception/Codeception.git", "url": "https://github.com/Codeception/Codeception.git",
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89" "reference": "65971b0dee4972710365b6102154cd412a9bf7b1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89", "url": "https://api.github.com/repos/Codeception/Codeception/zipball/65971b0dee4972710365b6102154cd412a9bf7b1",
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89", "reference": "65971b0dee4972710365b6102154cd412a9bf7b1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"facebook/webdriver": ">=1.0.1", "facebook/webdriver": ">=1.0.1 <2.0",
"guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/guzzle": ">=4.1.4 <7.0",
"guzzlehttp/psr7": "~1.0", "guzzlehttp/psr7": "~1.0",
"php": ">=5.4.0", "php": ">=5.4.0 <8.0",
"phpunit/phpunit": "~4.8.0", "phpunit/php-code-coverage": ">=2.1.3",
"symfony/browser-kit": ">=2.4|<3.1", "phpunit/phpunit": ">4.8.20 <6.0",
"symfony/console": ">=2.4|<3.1", "symfony/browser-kit": ">=2.5 <3.1",
"symfony/css-selector": ">=2.4|<3.1", "symfony/console": ">=2.5 <3.1",
"symfony/dom-crawler": ">=2.4|<3.1", "symfony/css-selector": ">=2.5 <3.1",
"symfony/event-dispatcher": ">=2.4|<3.1", "symfony/dom-crawler": ">=2.5 <3.1",
"symfony/finder": ">=2.4|<3.1", "symfony/event-dispatcher": ">=2.5 <3.1",
"symfony/yaml": ">=2.4|<3.1" "symfony/finder": ">=2.5 <3.1",
"symfony/yaml": ">=2.5 <3.1"
}, },
"require-dev": { "require-dev": {
"codeception/specify": "~0.3", "codeception/specify": "~0.3",
"facebook/php-sdk-v4": "~4.0", "facebook/php-sdk-v4": "~5.0",
"flow/jsonpath": "~0.2", "flow/jsonpath": "~0.2",
"monolog/monolog": "~1.8", "monolog/monolog": "~1.8",
"pda/pheanstalk": "~2.0", "pda/pheanstalk": "~2.0",
"videlalvaro/php-amqplib": "~2.4" "php-amqplib/php-amqplib": "~2.4"
}, },
"suggest": { "suggest": {
"codeception/phpbuiltinserver": "Extension to start and stop PHP built-in web server for your tests", "codeception/phpbuiltinserver": "Extension to start and stop PHP built-in web server for your tests",
@ -861,7 +862,7 @@
"functional testing", "functional testing",
"unit testing" "unit testing"
], ],
"time": "2016-02-09 22:27:48" "time": "2016-03-12 01:15:25"
}, },
{ {
"name": "codeception/verify", "name": "codeception/verify",
@ -898,33 +899,36 @@
}, },
{ {
"name": "codegyre/robo", "name": "codegyre/robo",
"version": "0.6.0", "version": "0.7.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Codegyre/Robo.git", "url": "https://github.com/Codegyre/Robo.git",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552" "reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/d18185f0494c854d36aa5ee0ad931ee23bbef552", "url": "https://api.github.com/repos/Codegyre/Robo/zipball/1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552", "reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"henrikbjorn/lurker": "1.0.*@dev", "henrikbjorn/lurker": "~1.0",
"php": ">=5.4.0", "php": ">=5.4.0",
"symfony/console": "~2.5", "symfony/console": "~2.5|~3.0",
"symfony/filesystem": "~2.5", "symfony/filesystem": "~2.5|~3.0",
"symfony/finder": "~2.5", "symfony/finder": "~2.5|~3.0",
"symfony/process": "~2.5" "symfony/process": "~2.5|~3.0"
}, },
"require-dev": { "require-dev": {
"codeception/aspect-mock": "0.5.*", "codeception/aspect-mock": "0.5.4",
"codeception/base": "~2.1", "codeception/base": "~2.1.5",
"codeception/codeception": "2.1",
"codeception/verify": "0.2.*", "codeception/verify": "0.2.*",
"natxet/cssmin": "~3.0", "natxet/cssmin": "~3.0",
"patchwork/jsqueeze": "~1.0" "patchwork/jsqueeze": "~1.0",
"pear/archive_tar": "~1.0"
},
"suggest": {
"pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively."
}, },
"bin": [ "bin": [
"robo" "robo"
@ -946,7 +950,7 @@
} }
], ],
"description": "Modern task runner", "description": "Modern task runner",
"time": "2015-10-30 11:29:52" "time": "2016-02-25 19:28:51"
}, },
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
@ -1109,16 +1113,16 @@
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "1.0.3", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
"reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1156,20 +1160,20 @@
"keywords": [ "keywords": [
"promise" "promise"
], ],
"time": "2015-10-15 22:28:00" "time": "2016-03-08 01:15:46"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "1.2.2", "version": "1.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb" "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb", "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb", "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1214,20 +1218,20 @@
"stream", "stream",
"uri" "uri"
], ],
"time": "2016-01-23 01:23:02" "time": "2016-02-18 21:54:00"
}, },
{ {
"name": "henrikbjorn/lurker", "name": "henrikbjorn/lurker",
"version": "1.0.0", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/flint/Lurker.git", "url": "https://github.com/flint/Lurker.git",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419" "reference": "ab45f9cefe600065cc3137a238217598d3a1d062"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/flint/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419", "url": "https://api.github.com/repos/flint/Lurker/zipball/ab45f9cefe600065cc3137a238217598d3a1d062",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419", "reference": "ab45f9cefe600065cc3137a238217598d3a1d062",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1255,18 +1259,16 @@
], ],
"authors": [ "authors": [
{ {
"name": "Henrik Bjornskov", "name": "Yaroslav Kiliba",
"email": "henrik@bjrnskov.dk", "email": "om.dattaya@gmail.com"
"homepage": "http://henrik.bjrnskov.dk"
}, },
{ {
"name": "Konstantin Kudryashov", "name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com", "email": "ever.zet@gmail.com"
"homepage": "http://everzet.com"
}, },
{ {
"name": "Yaroslav Kiliba", "name": "Henrik Bjrnskov",
"email": "om.dattaya@gmail.com" "email": "henrik@bjrnskov.dk"
} }
], ],
"description": "Resource Watcher.", "description": "Resource Watcher.",
@ -1275,7 +1277,7 @@
"resource", "resource",
"watching" "watching"
], ],
"time": "2013-05-24 06:47:29" "time": "2015-10-27 09:19:19"
}, },
{ {
"name": "phpdocumentor/reflection-docblock", "name": "phpdocumentor/reflection-docblock",
@ -1630,16 +1632,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "4.8.23", "version": "4.8.24",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483" "reference": "a1066c562c52900a142a0e2bbf0582994671385e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483", "reference": "a1066c562c52900a142a0e2bbf0582994671385e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1698,7 +1700,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2016-02-11 14:56:33" "time": "2016-03-14 06:16:08"
}, },
{ {
"name": "phpunit/phpunit-mock-objects", "name": "phpunit/phpunit-mock-objects",
@ -1974,16 +1976,16 @@
}, },
{ {
"name": "sebastian/environment", "name": "sebastian/environment",
"version": "1.3.3", "version": "1.3.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/environment.git", "url": "https://github.com/sebastianbergmann/environment.git",
"reference": "6e7133793a8e5a5714a551a8324337374be209df" "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df", "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"reference": "6e7133793a8e5a5714a551a8324337374be209df", "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2020,7 +2022,7 @@
"environment", "environment",
"hhvm" "hhvm"
], ],
"time": "2015-12-02 08:37:27" "time": "2016-02-26 18:40:46"
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
@ -2229,7 +2231,7 @@
}, },
{ {
"name": "symfony/browser-kit", "name": "symfony/browser-kit",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/browser-kit.git", "url": "https://github.com/symfony/browser-kit.git",
@ -2286,22 +2288,25 @@
}, },
{ {
"name": "symfony/config", "name": "symfony/config",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/config.git", "url": "https://github.com/symfony/config.git",
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74" "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
}, },
"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/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74", "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9", "php": ">=5.3.9",
"symfony/filesystem": "~2.3|~3.0.0" "symfony/filesystem": "~2.3|~3.0.0"
}, },
"suggest": {
"symfony/yaml": "To use the yaml reference dumper"
},
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -2332,30 +2337,30 @@
], ],
"description": "Symfony Config Component", "description": "Symfony Config Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41" "time": "2016-02-22 16:12:45"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v2.8.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d" "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/d0239fb42f98dd02e7d342f793c5d2cdee0c478d", "url": "https://api.github.com/repos/symfony/console/zipball/2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d", "reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9", "php": ">=5.5.9",
"symfony/polyfill-mbstring": "~1.0" "symfony/polyfill-mbstring": "~1.0"
}, },
"require-dev": { "require-dev": {
"psr/log": "~1.0", "psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1|~3.0.0", "symfony/event-dispatcher": "~2.8|~3.0",
"symfony/process": "~2.1|~3.0.0" "symfony/process": "~2.8|~3.0"
}, },
"suggest": { "suggest": {
"psr/log": "For using the console logger", "psr/log": "For using the console logger",
@ -2365,7 +2370,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.8-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -2392,11 +2397,11 @@
], ],
"description": "Symfony Console Component", "description": "Symfony Console Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-14 08:33:16" "time": "2016-02-28 16:24:34"
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
@ -2449,16 +2454,16 @@
}, },
{ {
"name": "symfony/dom-crawler", "name": "symfony/dom-crawler",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dom-crawler.git", "url": "https://github.com/symfony/dom-crawler.git",
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d" "reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d", "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d", "reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2501,20 +2506,20 @@
], ],
"description": "Symfony DomCrawler Component", "description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-25 09:56:57" "time": "2016-02-28 16:24:34"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda" "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda", "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2561,20 +2566,20 @@
], ],
"description": "Symfony EventDispatcher Component", "description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-13 10:28:07" "time": "2016-01-27 05:14:19"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c" "reference": "65cb36b6539b1d446527d60457248f30d045464d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c", "reference": "65cb36b6539b1d446527d60457248f30d045464d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2610,29 +2615,29 @@
], ],
"description": "Symfony Filesystem Component", "description": "Symfony Filesystem Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-13 10:28:07" "time": "2016-02-22 15:02:30"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v2.8.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "c90fabdd97e431ee19b6383999cf35334dff27da" "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/c90fabdd97e431ee19b6383999cf35334dff27da", "url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723",
"reference": "c90fabdd97e431ee19b6383999cf35334dff27da", "reference": "623bda0abd9aa29e529c8e9c08b3b84171914723",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9" "php": ">=5.5.9"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.8-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -2659,20 +2664,20 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-14 08:26:52" "time": "2016-01-27 05:14:46"
}, },
{ {
"name": "symfony/form", "name": "symfony/form",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/form.git", "url": "https://github.com/symfony/form.git",
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085" "reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06"
}, },
"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/25b71aec36879b4a84c2ea06603dbe7498b31f06",
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085", "reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2733,20 +2738,20 @@
], ],
"description": "Symfony Form Component", "description": "Symfony Form Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-12 17:46:01" "time": "2016-02-28 15:05:09"
}, },
{ {
"name": "symfony/intl", "name": "symfony/intl",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/intl.git", "url": "https://github.com/symfony/intl.git",
"reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc" "reference": "cafee6f65148dab9058cdb6de5631222aca820d0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc", "url": "https://api.github.com/repos/symfony/intl/zipball/cafee6f65148dab9058cdb6de5631222aca820d0",
"reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc", "reference": "cafee6f65148dab9058cdb6de5631222aca820d0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2808,20 +2813,20 @@
"l10n", "l10n",
"localization" "localization"
], ],
"time": "2016-01-27 05:14:46" "time": "2016-02-23 15:16:06"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/options-resolver.git", "url": "https://github.com/symfony/options-resolver.git",
"reference": "b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51" "reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51", "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"reference": "b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51", "reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2862,26 +2867,29 @@
"configuration", "configuration",
"options" "options"
], ],
"time": "2016-01-03 15:33:41" "time": "2016-01-21 09:05:51"
}, },
{ {
"name": "symfony/polyfill-intl-icu", "name": "symfony/polyfill-intl-icu",
"version": "v1.1.0", "version": "v1.1.1",
"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": "66b0bb4abda229bc073eff6bbc8f2685bdaac165" "reference": "8328069d9f5322f0e7b3c3518485acfdc94c3942"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/66b0bb4abda229bc073eff6bbc8f2685bdaac165", "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/8328069d9f5322f0e7b3c3518485acfdc94c3942",
"reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165", "reference": "8328069d9f5322f0e7b3c3518485acfdc94c3942",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.3", "php": ">=5.3.3",
"symfony/intl": "~2.3|~3.0" "symfony/intl": "~2.3|~3.0"
}, },
"suggest": {
"ext-intl": "For best performance"
},
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -2917,29 +2925,29 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2016-01-20 09:13:37" "time": "2016-02-26 16:18:12"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v2.8.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac" "reference": "dfecef47506179db2501430e732adbf3793099c8"
}, },
"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/dfecef47506179db2501430e732adbf3793099c8",
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", "reference": "dfecef47506179db2501430e732adbf3793099c8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.9" "php": ">=5.5.9"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.8-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -2966,20 +2974,20 @@
], ],
"description": "Symfony Process Component", "description": "Symfony Process Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-06 09:59:23" "time": "2016-02-02 13:44:19"
}, },
{ {
"name": "symfony/property-access", "name": "symfony/property-access",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/property-access.git", "url": "https://github.com/symfony/property-access.git",
"reference": "95363dbabd606e404b6c75095669993bf7ddae4b" "reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/95363dbabd606e404b6c75095669993bf7ddae4b", "url": "https://api.github.com/repos/symfony/property-access/zipball/f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"reference": "95363dbabd606e404b6c75095669993bf7ddae4b", "reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3026,20 +3034,20 @@
"property path", "property path",
"reflection" "reflection"
], ],
"time": "2016-01-03 15:35:16" "time": "2016-02-13 09:23:44"
}, },
{ {
"name": "symfony/routing", "name": "symfony/routing",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/routing.git", "url": "https://github.com/symfony/routing.git",
"reference": "5451a8a1874fd4e6a4dd347ea611d86cd8441735" "reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/5451a8a1874fd4e6a4dd347ea611d86cd8441735", "url": "https://api.github.com/repos/symfony/routing/zipball/ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"reference": "5451a8a1874fd4e6a4dd347ea611d86cd8441735", "reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3062,6 +3070,7 @@
"symfony/config": "For using the all-in-one router or any loader", "symfony/config": "For using the all-in-one router or any loader",
"symfony/dependency-injection": "For loading routes from a service", "symfony/dependency-injection": "For loading routes from a service",
"symfony/expression-language": "For using expression matching", "symfony/expression-language": "For using expression matching",
"symfony/http-foundation": "For using a Symfony Request object",
"symfony/yaml": "For using the YAML loader" "symfony/yaml": "For using the YAML loader"
}, },
"type": "library", "type": "library",
@ -3100,20 +3109,20 @@
"uri", "uri",
"url" "url"
], ],
"time": "2016-01-11 16:43:36" "time": "2016-02-04 13:53:00"
}, },
{ {
"name": "symfony/twig-bridge", "name": "symfony/twig-bridge",
"version": "v2.8.2", "version": "v2.8.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/twig-bridge.git", "url": "https://github.com/symfony/twig-bridge.git",
"reference": "e33b512de4b769a1c728cd6775e22668ae89fca9" "reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e33b512de4b769a1c728cd6775e22668ae89fca9", "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ba898e4714734948dc00c4d776e1d6be165ff8c4",
"reference": "e33b512de4b769a1c728cd6775e22668ae89fca9", "reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3181,20 +3190,20 @@
], ],
"description": "Symfony Twig Bridge", "description": "Symfony Twig Bridge",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-01-12 17:46:01" "time": "2016-02-22 15:02:30"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.0.2", "version": "v3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a" "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a", "url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a", "reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3230,7 +3239,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2016-02-02 13:44:19" "time": "2016-02-23 15:16:06"
}, },
{ {
"name": "twig/extensions", "name": "twig/extensions",

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ class Env {
static $assets_path; static $assets_path;
static $assets_url; static $assets_url;
static $temp_path; static $temp_path;
static $temp_URL; static $temp_url;
static $languages_path; static $languages_path;
static $lib_path; static $lib_path;
static $plugin_prefix; static $plugin_prefix;
@ -26,6 +26,7 @@ class Env {
static $db_username; static $db_username;
static $db_password; static $db_password;
static $db_charset; static $db_charset;
static $db_timezone_offset;
static function init($file, $version) { static function init($file, $version) {
global $wpdb; global $wpdb;
@ -38,7 +39,7 @@ class Env {
self::$assets_url = plugins_url('/assets', $file); self::$assets_url = plugins_url('/assets', $file);
$wp_upload_dir = wp_upload_dir(); $wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['path']; self::$temp_path = $wp_upload_dir['path'];
self::$temp_URL = $wp_upload_dir['url']; self::$temp_url = $wp_upload_dir['url'];
self::$languages_path = self::$path . '/lang'; self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib'; self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_'; self::$plugin_prefix = 'mailpoet_';
@ -58,6 +59,7 @@ class Env {
self::$db_password = DB_PASSWORD; self::$db_password = DB_PASSWORD;
self::$db_charset = $wpdb->get_charset_collate(); self::$db_charset = $wpdb->get_charset_collate();
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port); self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
self::$db_timezone_offset = self::getDbTimezoneOffset();
} }
private static function dbSourceName($host, $socket, $port) { private static function dbSourceName($host, $socket, $port) {
@ -73,4 +75,13 @@ class Env {
); );
return implode('', $source_name); return implode('', $source_name);
} }
private static function getDbTimezoneOffset() {
$mins = get_option('gmt_offset') * 60;
$sgn = ($mins < 0 ? -1 : 1);
$mins = abs($mins);
$hrs = floor($mins / 60);
$mins -= $hrs * 60;
return sprintf('%+d:%02d', $hrs * $sgn, $mins);
}
} }

View File

@ -13,7 +13,6 @@ class Hooks {
$this->setupWPUsers(); $this->setupWPUsers();
$this->setupImageSize(); $this->setupImageSize();
$this->setupListing(); $this->setupListing();
$this->setupCronWorkers();
} }
function setupSubscribe() { function setupSubscribe() {
@ -98,7 +97,7 @@ class Hooks {
add_action( add_action(
'profile_update', 'profile_update',
'\MailPoet\Segments\WP::synchronizeUser', '\MailPoet\Segments\WP::synchronizeUser',
1 1,2
); );
add_action( add_action(
'delete_user', 'delete_user',
@ -147,19 +146,4 @@ class Hooks {
return $status; 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

@ -65,7 +65,8 @@ class Initializer {
\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 = "' . Env::$db_timezone_offset. '"'
)); ));
$subscribers = Env::$db_prefix . 'subscribers'; $subscribers = Env::$db_prefix . 'subscribers';
@ -130,7 +131,7 @@ class Initializer {
} }
function setupWidget() { function setupWidget() {
$widget = new Widget(); $widget = new Widget($this->renderer);
$widget->init(); $widget->init();
} }

View File

@ -367,6 +367,8 @@ class Menu {
$data['segments'] = Segment::findArray(); $data['segments'] = Segment::findArray();
$data['settings'] = Setting::getAll(); $data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names(); $data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role');
echo $this->renderer->render('newsletters.html', $data); echo $this->renderer->render('newsletters.html', $data);
} }
@ -385,7 +387,7 @@ class Menu {
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

@ -214,6 +214,9 @@ class Migrator {
$attributes = array( $attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,', 'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,', 'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,', 'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,', 'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,', 'priority mediumint(9) NOT NULL DEFAULT 0,',
@ -221,6 +224,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,',
@ -267,4 +271,4 @@ class Migrator {
return implode("\n", $sql); return implode("\n", $sql);
} }
} }

View File

@ -70,35 +70,36 @@ 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() {
$current_user = wp_get_current_user(); $current_user = wp_get_current_user();
// user name // default sender info based on current user
$user_name = ''; $sender = array(
if($current_user->user_firstname) { 'name' => $current_user->display_name,
$user_name = $current_user->user_firstname; 'address' => $current_user->user_email
} );
if($current_user->user_lastname) {
if($user_name) {
$user_name .= ' '.$current_user->user_lastname;
}
}
if(!$user_name) {
$user_name = $current_user->display_name;
}
// default from name & address // default from name & address
Setting::setValue('sender', array( Setting::setValue('sender', $sender);
'name' => $user_name,
'address' => $current_user->user_email
));
// enable signup confirmation by default // enable signup confirmation by default
Setting::setValue('signup_confirmation.enabled', true); Setting::setValue('signup_confirmation', array(
'enabled' => true,
'from' => array(
'name' => get_option('blogname'),
'address' => get_option('admin_email')
),
'reply_to' => $sender
));
} }
private function createDefaultSegments() { private function createDefaultSegments() {
@ -175,6 +176,14 @@ class Populator {
'name' => 'nthWeekDay', 'name' => 'nthWeekDay',
'newsletter_type' => 'notification', 'newsletter_type' => 'notification',
), ),
array(
'name' => 'schedule',
'newsletter_type' => 'notification',
),
array(
'name' => 'segments',
'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"
@ -280,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"

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

@ -8,38 +8,38 @@ if(!defined('ABSPATH')) exit;
class PublicAPI { class PublicAPI {
public $api; public $api;
public $section; public $endpoint;
public $action; public $action;
public $request_payload; public $data;
function __construct() { function __construct() {
# http://example.com/?mailpoet-api&section=&action=&request_payload= # http://example.com/?mailpoet&endpoint=&action=&data=
$this->api = isset($_GET['mailpoet-api']) ? true : false; $this->api = isset($_GET['mailpoet']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false; $this->endpoint = isset($_GET['endpoint']) ?
Helpers::underscoreToCamelCase($_GET['endpoint']) :
false;
$this->action = isset($_GET['action']) ? $this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) : Helpers::underscoreToCamelCase($_GET['action']) :
false; false;
$this->request_payload = isset($_GET['request_payload']) ? $this->data = isset($_GET['data']) ? $_GET['data'] : false;
unserialize(base64_decode($_GET['request_payload'])) :
false;
} }
function init() { function init() {
if(!$this->api && !$this->section) return; if(!$this->api && !$this->endpoint) return;
$this->_checkAndCallMethod($this, $this->section, $terminate = true); $this->_checkAndCallMethod($this, $this->endpoint, $terminate_request = true);
} }
function queue() { function queue() {
try { try {
$queue = new Daemon($this->request_payload); $queue = new Daemon($this->data);
$this->_checkAndCallMethod($queue, $this->action); $this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) { } catch(\Exception $e) {
} }
} }
private function _checkAndCallMethod($class, $method, $terminate = false) { private function _checkAndCallMethod($class, $method, $terminate_request = false) {
if(!method_exists($class, $method)) { if(!method_exists($class, $method)) {
if(!$terminate) return; if(!$terminate_request) return;
header('HTTP/1.0 404 Not Found'); header('HTTP/1.0 404 Not Found');
exit; exit;
} }

View File

@ -59,7 +59,7 @@ class Renderer {
} }
function detectCache() { function detectCache() {
$cache_path = Env::$views_path . '/cache'; $cache_path = Env::$temp_path . '/cache';
if(WP_DEBUG === false) { if(WP_DEBUG === false) {
return $cache_path; return $cache_path;
} }

View File

@ -1,11 +1,17 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use \MailPoet\Util\Security; use \MailPoet\Util\Security;
use \MailPoet\Models\Form;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Widget { class Widget {
function __construct() { private $renderer = null;
function __construct($renderer = null) {
if($renderer !== null) {
$this->renderer = $renderer;
}
} }
function init() { function init() {
@ -13,11 +19,67 @@ class Widget {
if(!is_admin()) { if(!is_admin()) {
$this->setupDependencies(); $this->setupDependencies();
$this->setupIframe();
} else { } else {
$this->setupAdminDependencies(); $this->setupAdminDependencies();
} }
} }
function setupIframe() {
$form_id = (isset($_GET['mailpoet_form_iframe']) ? (int)$_GET['mailpoet_form_iframe'] : 0);
if($form_id > 0) {
$form = Form::findOne($form_id);
if($form !== false) {
$form_widget = new \MailPoet\Form\Widget();
$form_html = $form_widget->widget(array(
'form' => $form_id,
'form_type' => 'iframe'
));
// capture javascripts
ob_start();
wp_print_scripts('jquery');
wp_print_scripts('mailpoet_vendor');
wp_print_scripts('mailpoet_public');
$scripts = ob_get_contents();
ob_end_clean();
// language attributes
$language_attributes = array();
$is_rtl = (bool)(function_exists('is_rtl') && is_rtl());
if($is_rtl) {
$language_attributes[] = 'dir="rtl"';
}
if($lang = get_bloginfo('language')) {
if(get_option('html_type') === 'text/html') {
$language_attributes[] = "lang=\"$lang\"";
}
}
$language_attributes = apply_filters(
'language_attributes', implode(' ', $language_attributes)
);
$data = array(
'language_attributes' => $language_attributes,
'scripts' => $scripts,
'form' => $form_html,
'mailpoet_form' => array(
'ajax_url' => admin_url('admin-ajax.php', 'absolute'),
'is_rtl' => $is_rtl,
'token' => Security::generateToken()
)
);
echo $this->renderer->render('form/iframe.html', $data);
}
exit();
}
}
function registerWidget() { function registerWidget() {
register_widget('\MailPoet\Form\Widget'); register_widget('\MailPoet\Form\Widget');
} }

View File

@ -38,9 +38,9 @@ class CronHelper {
} }
static function accessDaemon($token, $timeout = self::daemon_request_timeout) { static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
$payload = serialize(array('token' => $token)); $data = serialize(array('token' => $token));
$url = '/?mailpoet-api&section=queue&action=run&request_payload=' . $url = '/?mailpoet&endpoint=queue&action=run&data=' .
base64_encode($payload); base64_encode($data);
$args = array( $args = array(
'timeout' => $timeout, 'timeout' => $timeout,
'user-agent' => 'MailPoet (www.mailpoet.com) Cron' 'user-agent' => 'MailPoet (www.mailpoet.com) Cron'

View File

@ -3,7 +3,6 @@ namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler; 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');
@ -11,17 +10,18 @@ if(!defined('ABSPATH')) exit;
class Daemon { class Daemon {
public $daemon; public $daemon;
public $request_payload; public $data;
public $refreshed_token; public $refreshed_token;
const daemon_request_timeout = 5; const daemon_request_timeout = 5;
private $timer; private $timer;
function __construct($request_payload = array()) { function __construct($data) {
if (!$data) $this->abortWithError(__('Invalid or missing cron data.'));
set_time_limit(0); set_time_limit(0);
ignore_user_abort(); ignore_user_abort();
$this->daemon = CronHelper::getDaemon(); $this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken(); $this->token = CronHelper::createToken();
$this->request_payload = $request_payload; $this->data = unserialize(base64_decode($data));
$this->timer = microtime(true); $this->timer = microtime(true);
} }
@ -30,14 +30,17 @@ class Daemon {
if(!$daemon) { if(!$daemon) {
$this->abortWithError(__('Daemon does not exist.')); $this->abortWithError(__('Daemon does not exist.'));
} }
if(!isset($this->request_payload['token']) || if(!isset($this->data['token']) ||
$this->request_payload['token'] !== $daemon['token'] $this->data['token'] !== $daemon['token']
) { ) {
$this->abortWithError(__('Invalid or missing token.')); $this->abortWithError(__('Invalid or missing token.'));
} }
$this->abortIfStopped($daemon); $this->abortIfStopped($daemon);
try { try {
do_action('mailpoet_cron_worker', $this->timer); $scheduler = new Scheduler();
$scheduler->process($this->timer);
$queue = new SendingQueue();
$queue->process($this->timer);
} catch(\Exception $e) { } catch(\Exception $e) {
} }
$elapsed_time = microtime(true) - $this->timer; $elapsed_time = microtime(true) - $this->timer;
@ -47,7 +50,7 @@ class Daemon {
// after each execution, re-read daemon data in case it was deleted or // after each execution, re-read daemon data in case it was deleted or
// its status has changed // its status has changed
$daemon = CronHelper::getDaemon(); $daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['token'] !== $this->request_payload['token']) { if(!$daemon || $daemon['token'] !== $this->data['token']) {
exit; exit;
} }
$daemon['counter']++; $daemon['counter']++;

View File

@ -1,9 +1,17 @@
<?php <?php
namespace MailPoet\Cron\Workers; namespace MailPoet\Cron\Workers;
use Carbon\Carbon;
use Cron\CronExpression as Cron;
use MailPoet\Cron\CronHelper; use MailPoet\Cron\CronHelper;
use MailPoet\Models\Setting; use MailPoet\Models\Newsletter;
use MailPoet\Util\Security; use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Util\Helpers;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -16,12 +24,125 @@ class Scheduler {
} }
function process() { function process() {
} $scheduled_queues = SendingQueue::where('status', 'scheduled')
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
function checkExecutionTimer() { ->findMany();
$elapsed_time = microtime(true) - $this->timer; if(!count($scheduled_queues)) return;
if($elapsed_time >= CronHelper::daemon_execution_limit) { foreach($scheduled_queues as $queue) {
throw new \Exception(__('Maximum execution time reached.')); $newsletter = Newsletter::filter('filterWithOptions')
->findOne($queue->newsletter_id);
if(!$newsletter || $newsletter->deleted_at !== null) {
$queue->delete();
}
else if($newsletter->type === 'welcome') {
$this->processWelcomeNewsletter($newsletter, $queue);
}
else if($newsletter->type === 'notification') {
$this->processPostNotificationNewsletter($newsletter, $queue);
}
CronHelper::checkExecutionTimer($this->timer);
} }
} }
function processWelcomeNewsletter($newsletter, $queue) {
$subscriber = unserialize($queue->subscribers);
$subscriber_id = $subscriber['to_process'][0];
if($newsletter->event === 'segment') {
if ($this->verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
}
}
else if($newsletter->event === 'user') {
if ($this->verifyWPSubscriber($subscriber_id, $newsletter) === false) {
$queue->delete();
return;
}
}
$queue->status = null;
$queue->save();
}
function processPostNotificationNewsletter($newsletter, $queue) {
$next_run_date = $this->getQueueNextRunDate($newsletter->schedule);
$segments = unserialize($newsletter->segments);
$subscribers = SubscriberSegment::whereIn('segment_id', $segments)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
if(!count($subscribers) || !$this->checkIfNewsletterChanged($newsletter)) {
$queue->scheduled_at = $next_run_date;
$queue->save();
return;
}
// update current queue
$queue->subscribers = serialize(
array(
'to_process' => $subscribers
)
);
$queue->count_total = $queue->count_to_process = count($subscribers);
$queue->status = null;
$queue->save();
// schedule newsletter for next delivery
$new_queue = SendingQueue::create();
$new_queue->newsletter_id = $newsletter->id;
$new_queue->scheduled_at = $next_run_date;
$new_queue->status = 'scheduled';
$new_queue->save();
}
private function verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) {
// check if subscriber is in proper segment
$subscriber_in_segment =
SubscriberSegment::where('subscriber_id', $subscriber_id)
->where('segment_id', $newsletter->segment)
->where('status', 'subscribed')
->findOne();
if (!$subscriber_in_segment) {
$queue->delete();
return false;
}
// check if subscriber is confirmed (subscribed)
$subscriber = $subscriber_in_segment->subscriber()->findOne();
if ($subscriber->status !== 'subscribed') {
// reschedule delivery in 5 minutes
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
$queue->scheduled_at = $scheduled_at->addMinutes(5);
$queue->save();
return false;
}
return true;
}
private function verifyWPSubscriber($subscriber_id, $newsletter) {
// check if user has the proper role
$subscriber = Subscriber::findOne($subscriber_id);
if(!$subscriber || $subscriber->wp_user_id === null) {
return false;
}
$wp_user = (array) get_userdata($subscriber->wp_user_id);
if(!in_array($newsletter->role, $wp_user['roles'])) {
return false;
}
return true;
}
private function checkIfNewsletterChanged($newsletter) {
$last_run_queue = SendingQueue::where('status', 'completed')
->where('newsletter_id', $newsletter->id)
->orderByDesc('id')
->findOne();
if(!$last_run_queue) return true;
$renderer = new Renderer($newsletter->asArray());
$rendered_newsletter = $renderer->render();
$new_hash = md5($rendered_newsletter['html']);
$old_hash = $last_run_queue->newsletter_rendered_body_hash;
return $new_hash === $old_hash;
}
private function getQueueNextRunDate($schedule) {
$schedule = Cron::factory($schedule);
return $schedule->getNextRunDate(current_time('mysql'))
->format('Y-m-d H:i:s');
}
} }

View File

@ -10,7 +10,6 @@ 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;
@ -35,8 +34,11 @@ class SendingQueue {
foreach($this->getQueues() as $queue) { foreach($this->getQueues() as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id); $newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) { if(!$newsletter) {
$queue->delete();
continue; continue;
} }
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->getNewsletterBodyAndSubject($queue, $newsletter);
$queue->subscribers = (object) unserialize($queue->subscribers); $queue->subscribers = (object) unserialize($queue->subscribers);
if(!isset($queue->subscribers->processed)) { if(!isset($queue->subscribers->processed)) {
$queue->subscribers->processed = array(); $queue->subscribers->processed = array();
@ -44,8 +46,6 @@ class SendingQueue {
if(!isset($queue->subscribers->failed)) { if(!isset($queue->subscribers->failed)) {
$queue->subscribers->failed = array(); $queue->subscribers->failed = array();
} }
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->renderNewsletter($newsletter);
$mailer = $this->configureMailer($newsletter); $mailer = $this->configureMailer($newsletter);
foreach(array_chunk($queue->subscribers->to_process, self::batch_size) as foreach(array_chunk($queue->subscribers->to_process, self::batch_size) as
$subscribers_ids) { $subscribers_ids) {
@ -67,6 +67,20 @@ class SendingQueue {
} }
} }
function getNewsletterBodyAndSubject($queue, $newsletter) {
// check if newsletter has been rendered, in which case return its contents
// or render & and for future use
if($queue->newsletter_rendered_body === null) {
$newsletter['body'] = $this->renderNewsletter($newsletter);
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
$queue->newsletter_rendered_body_hash = md5($newsletter['body']['text']);
$queue->save();
} else {
$newsletter['body'] = json_decode($queue->newsletter_rendered_body);
}
return (array) $newsletter['body'];
}
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) { function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
$processed_newsletters[] = $processed_newsletters[] =
@ -87,7 +101,7 @@ class SendingQueue {
); );
} else { } else {
$newsletter_statistics = $newsletter_statistics =
array_map(function ($data) use ($newsletter, $subscribers_ids, $queue) { array_map(function($data) use ($newsletter, $subscribers_ids, $queue) {
return array( return array(
$newsletter['id'], $newsletter['id'],
$subscribers_ids[$data], $subscribers_ids[$data],
@ -112,6 +126,9 @@ class SendingQueue {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
$this->checkSendingLimit(); $this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber); $processed_newsletter = $this->processNewsletter($newsletter, $subscriber);
if (!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
$transformed_subscriber = $mailer->transformSubscriber($subscriber); $transformed_subscriber = $mailer->transformSubscriber($subscriber);
$result = $this->sendNewsletter( $result = $this->sendNewsletter(
$mailer, $mailer,
@ -119,7 +136,7 @@ class SendingQueue {
$transformed_subscriber $transformed_subscriber
); );
if(!$result) { if(!$result) {
$queue->subscribers->failed[] = $subscriber['id'];; $queue->subscribers->failed[] = $subscriber['id'];
} else { } else {
$queue->subscribers->processed[] = $subscriber['id']; $queue->subscribers->processed[] = $subscriber['id'];
$newsletter_statistics = array( $newsletter_statistics = array(
@ -147,13 +164,17 @@ class SendingQueue {
function processNewsletter($newsletter, $subscriber = false) { function processNewsletter($newsletter, $subscriber = false) {
$divider = '***MailPoet***'; $divider = '***MailPoet***';
$data_for_shortcodes =
array_merge(array($newsletter['subject']), $newsletter['body']);
$body = implode($divider, $data_for_shortcodes);
$shortcodes = new Shortcodes( $shortcodes = new Shortcodes(
implode($divider, $newsletter['body']),
$newsletter, $newsletter,
$subscriber $subscriber
); );
list($newsletter['body']['html'], $newsletter['body']['text']) = list($newsletter['subject'],
explode($divider, $shortcodes->replace()); $newsletter['body']['html'],
$newsletter['body']['text']
) = explode($divider, $shortcodes->replace($body));
return $newsletter; return $newsletter;
} }
@ -203,7 +224,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);

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

@ -25,18 +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 .= ( $html .= (
(isset($option['is_checked']) && $option['is_checked']) (isset($option['is_checked']) && $option['is_checked'])
|| ||
(self::getFieldValue($block) === $option['value']) (self::getFieldValue($block) === $value)
) ? 'checked="checked"' : ''; ) ? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' /> '.esc_attr($label);
$html .= ' /> '.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -18,7 +18,12 @@ 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'])
? $block['params']['values']
: array()
);
foreach($options as $option) {
$is_selected = ( $is_selected = (
(isset($option['is_checked']) && $option['is_checked']) (isset($option['is_checked']) && $option['is_checked'])
|| ||

View File

@ -6,7 +6,7 @@ class Text extends Base {
static function render($block) { static function render($block) {
$type = 'text'; $type = 'text';
if($block['id'] === 'email') { if($block['id'] === 'email') {
$type = 'email'; $type = 'email';
} }
$html = ''; $html = '';
@ -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

@ -17,8 +17,7 @@ class Export {
case 'iframe': case 'iframe':
// generate url to load iframe's content // generate url to load iframe's content
$iframe_url = add_query_arg(array( $iframe_url = add_query_arg(array(
'mailpoet_page' => 'mailpoet_form_iframe', 'mailpoet_form_iframe' => $form['id']
'mailpoet_form' => $form['id']
), site_url()); ), site_url());
// generate iframe // generate iframe
@ -31,7 +30,7 @@ class Export {
'class="mailpoet_form_iframe"', 'class="mailpoet_form_iframe"',
'vspace="0"', 'vspace="0"',
'tabindex="0"', 'tabindex="0"',
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"', 'onload="MailPoet.Iframe.autoSize(this);"',
'marginwidth="0"', 'marginwidth="0"',
'marginheight="0"', 'marginheight="0"',
'hspace="0"', 'hspace="0"',

View File

@ -154,7 +154,7 @@ class Widget extends \WP_Widget {
$output = ''; $output = '';
if(!empty($body)) { if(!empty($body)) {
$form_id = $this->id_base.'_'.$this->number; $form_id = $this->id_base.'_'.$form['id'];
$data = array( $data = array(
'form_id' => $form_id, 'form_id' => $form_id,

View File

@ -8,6 +8,11 @@ class Setting extends Model {
public static $defaults = null; 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 = 5; // in minutes
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -25,10 +30,18 @@ class Setting extends Model {
public static function loadDefaults() { public static function loadDefaults() {
self::$defaults = array( 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( 'signup_confirmation' => array(
'enabled' => true, 'enabled' => true,
'subject' => sprintf(__('Confirm your subscription to %1$s'), get_option('blogname')), '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!") 'body' => __("Hello!\n\nHurray! You've subscribed to our site.\n\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!")
) )
); );
} }

View File

@ -1,7 +1,9 @@
<?php <?php
namespace MailPoet\Models; namespace MailPoet\Models;
use MailPoet\Mailer\Mailer; 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;
@ -70,42 +72,6 @@ class Subscriber extends Model {
} }
} }
function getConfirmationUrl() {
$post = get_post(Setting::getValue('signup_confirmation.page'));
return $this->getSubscriptionUrl($post, 'confirm');
}
function getEditSubscriptionUrl() {
$post = get_post(Setting::getValue('subscription.page'));
return $this->getSubscriptionUrl($post, 'edit');
}
function getUnsubscribeUrl() {
$post = get_post(Setting::getValue('subscription.page'));
return $this->getSubscriptionUrl($post, 'unsubscribe');
}
private function getSubscriptionUrl($post = null, $action = null) {
if($post === null || $action === null) return;
$url = get_permalink($post);
$params = array(
'mailpoet_action='.$action,
'mailpoet_token='.self::generateToken($this->email),
'mailpoet_email='.$this->email
);
// add parameters
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?').join('&', $params);
$url_params = parse_url($url);
if(empty($url_params['scheme'])) {
$url = get_bloginfo('url').$url;
}
return $url;
}
function sendConfirmationEmail() { function sendConfirmationEmail() {
if($this->status === self::STATUS_UNCONFIRMED) { if($this->status === self::STATUS_UNCONFIRMED) {
$signup_confirmation = Setting::getValue('signup_confirmation'); $signup_confirmation = Setting::getValue('signup_confirmation');
@ -131,7 +97,7 @@ class Subscriber extends Model {
'[/activation_link]' '[/activation_link]'
), ),
array( array(
'<a href="'.htmlentities($this->getConfirmationUrl()).'">', '<a href="'.esc_attr(Subscription\Url::getConfirmationUrl($this)).'">',
'</a>' '</a>'
), ),
$body $body
@ -152,14 +118,14 @@ class Subscriber extends Model {
// set from // set from
$from = ( $from = (
!empty($signup_confirmation['from']) !empty($signup_confirmation['from'])
&& !empty($signup_confirmation['from']['email']) && !empty($signup_confirmation['from']['address'])
) ? $signup_confirmation['from'] ) ? $signup_confirmation['from']
: false; : false;
// set reply to // set reply to
$reply_to = ( $reply_to = (
!empty($signup_confirmation['reply_to']) !empty($signup_confirmation['reply_to'])
&& !empty($signup_confirmation['reply_to']['email']) && !empty($signup_confirmation['reply_to']['address'])
) ? $signup_confirmation['reply_to'] ) ? $signup_confirmation['reply_to']
: false; : false;
@ -187,6 +153,10 @@ class Subscriber extends Model {
return false; return false;
} }
$signup_confirmation_enabled = (bool)Setting::getValue(
'signup_confirmation.enabled'
);
$subscriber = self::createOrUpdate($subscriber_data); $subscriber = self::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors(); $errors = $subscriber->getErrors();
@ -198,16 +168,22 @@ class Subscriber extends Model {
$subscriber->setExpr('deleted_at', 'NULL'); $subscriber->setExpr('deleted_at', 'NULL');
} }
if((bool)Setting::getValue('signup_confirmation.enabled')) { // auto subscribe when signup confirmation is disabled
if($subscriber->status !== self::STATUS_SUBSCRIBED) { if($signup_confirmation_enabled === false) {
$subscriber->sendConfirmationEmail();
}
} else {
$subscriber->set('status', self::STATUS_SUBSCRIBED); $subscriber->set('status', self::STATUS_SUBSCRIBED);
} }
if($subscriber->save()) { if($subscriber->save()) {
// link subscriber to segments
$subscriber->addToSegments($segment_ids); $subscriber->addToSegments($segment_ids);
// signup confirmation
if($subscriber->status !== self::STATUS_SUBSCRIBED) {
$subscriber->sendConfirmationEmail();
}
// welcome email
Scheduler::welcomeForSegmentSubscription($subscriber->id, $segment_ids);
} }
} }
@ -371,6 +347,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']);
@ -396,16 +375,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()) {
@ -414,8 +402,19 @@ class Subscriber extends Model {
$subscriber->setCustomField($custom_field_id, $value); $subscriber->setCustomField($custom_field_id, $value);
} }
} }
if($segment_ids !== false) {
SubscriberSegment::setSubscriptions($subscriber, $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;

View File

@ -12,22 +12,27 @@ class SubscriberSegment extends Model {
parent::__construct(); parent::__construct();
} }
function subscriber() {
return $this->has_one(__NAMESPACE__.'\Subscriber', 'id', 'subscriber_id');
}
static function setSubscriptions($subscriber, $segment_ids = array()) { static function setSubscriptions($subscriber, $segment_ids = array()) {
if($subscriber->id > 0) { if($subscriber->id > 0) {
// unsubscribe from current subscriptions // unsubscribe from current subscriptions
SubscriberSegment::where('subscriber_id', $subscriber->id) SubscriberSegment::where('subscriber_id', $subscriber->id)
->whereNotIn('segment_id', $segment_ids)
->findResultSet() ->findResultSet()
->set('status', Subscriber::STATUS_UNSUBSCRIBED) ->set('status', Subscriber::STATUS_UNSUBSCRIBED)
->save(); ->save();
// subscribe to segments // subscribe to segments
foreach($segment_ids as $segment_id) { foreach($segment_ids as $segment_id) {
self::createOrUpdate(array( if((int)$segment_id > 0) {
'subscriber_id' => $subscriber->id, self::createOrUpdate(array(
'segment_id' => $segment_id, 'subscriber_id' => $subscriber->id,
'status' => Subscriber::STATUS_SUBSCRIBED 'segment_id' => $segment_id,
)); 'status' => Subscriber::STATUS_SUBSCRIBED
));
}
} }
} }

View File

@ -169,6 +169,6 @@ class PostTransformer {
$alignment = (in_array($this->args['titleAlignment'], array('left', 'right', 'center'))) ? $this->args['titleAlignment'] : 'left'; $alignment = (in_array($this->args['titleAlignment'], array('left', 'right', 'center'))) ? $this->args['titleAlignment'] : 'left';
return '<' . $tag . ' style="text-align: ' . $alignment . '">' . $title . '</' . $tag . '>'; return '<' . $tag . ' data-post-id="' . $post->ID . '" style="text-align: ' . $alignment . '">' . $title . '</' . $tag . '>';
} }
} }

View File

@ -9,18 +9,18 @@ class Button {
$element['styles']['block']['width'] = self::calculateWidth($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_bottom mailpoet_padded_side" valign="top">
<div> <div>
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;"> <table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
<tr> <tr>
<td class="mailpoet_button-container" style="padding:8px 0;text-align:' . $element['styles']['block']['textAlign'] . ';"><!--[if mso]> <td class="mailpoet_button-container" style="text-align:' . $element['styles']['block']['textAlign'] . ';"><!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
href="' . $element['url'] . '" href="' . $element['url'] . '"
style="height:' . $element['styles']['block']['lineHeight'] . '; style="height:' . $element['styles']['block']['lineHeight'] . ';
width:' . $element['styles']['block']['width'] . '; width:' . $element['styles']['block']['width'] . ';
v-text-anchor:middle;" v-text-anchor:middle;"
arcsize="' . round($element['styles']['block']['borderRadius'] / $element['styles']['block']['lineHeight'] * 100) . '%" arcsize="' . round($element['styles']['block']['borderRadius'] / $element['styles']['block']['lineHeight'] * 100) . '%"
strokeweight="1px" strokeweight="' . $element['styles']['block']['borderWidth'] . '"
strokecolor="' . $element['styles']['block']['borderColor'] . '" strokecolor="' . $element['styles']['block']['borderColor'] . '"
fillcolor="' . $element['styles']['block']['backgroundColor'] . '"> fillcolor="' . $element['styles']['block']['backgroundColor'] . '">
<w:anchorlock/> <w:anchorlock/>
@ -43,9 +43,12 @@ class Button {
static function calculateWidth($element, $column_count) { static function calculateWidth($element, $column_count) {
$column_width = ColumnsHelper::columnWidth($column_count); $column_width = ColumnsHelper::columnWidth($column_count);
$column_width = $column_width - (StylesHelper::$padding_width * 2); $column_width = $column_width - (StylesHelper::$padding_width * 2);
$column_width = ((int) $element['styles']['block']['width'] > $column_width) ? $border_width = (int) $element['styles']['block']['borderWidth'];
$column_width . 'px' : $button_width = (int) $element['styles']['block']['width'];
$element['styles']['block']['width']; $button_width = ($button_width > $column_width) ?
return $column_width; $column_width :
$button_width;
$button_width = $button_width - (2 * $border_width) . 'px';
return $button_width;
} }
} }

View File

@ -7,6 +7,9 @@ class Footer {
static function render($element) { static function render($element) {
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']); $element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']); $element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
$line_height = sprintf(
'%spx', StylesHelper::$line_height_multiplier * (int) $element['styles']['text']['fontSize']
);
$DOM_parser = new \pQuery(); $DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']); $DOM = $DOM_parser->parseStr($element['text']);
if(isset($element['styles']['link'])) { if(isset($element['styles']['link'])) {
@ -17,10 +20,15 @@ class Footer {
} }
} }
} }
$background_color = $element['styles']['block']['backgroundColor'];
$background_color = ($background_color !== 'transparent') ?
'bgcolor="' . $background_color . '"' :
false;
if(!$background_color) unset($element['styles']['block']['backgroundColor']);
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_header_footer_padded mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_footer" ' . $background_color . '
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="line-height: ' . $line_height . ';' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>
</tr>'; </tr>';

View File

@ -6,7 +6,10 @@ use MailPoet\Newsletter\Renderer\StylesHelper;
class Header { class Header {
static function render($element) { static function render($element) {
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']); $element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/', '', $element['text']); $element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
$line_height = sprintf(
'%spx', StylesHelper::$line_height_multiplier * (int) $element['styles']['text']['fontSize']
);
$DOM_parser = new \pQuery(); $DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']); $DOM = $DOM_parser->parseStr($element['text']);
if(isset($element['styles']['link'])) { if(isset($element['styles']['link'])) {
@ -17,11 +20,16 @@ class Header {
} }
} }
} }
$background_color = $element['styles']['block']['backgroundColor'];
$background_color = ($background_color !== 'transparent') ?
'bgcolor="' . $background_color . '"' :
false;
if(!$background_color) unset($element['styles']['block']['backgroundColor']);
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_header_footer_padded mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_header" ' . $background_color . '
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="line-height: ' . $line_height . ';' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>
</tr>'; </tr>';
return $template; return $template;

View File

@ -11,14 +11,14 @@ class Image {
$element = self::adjustImageDimensions($element, $column_count); $element = self::adjustImageDimensions($element, $column_count);
$image_template = ' $image_template = '
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '" <img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/> width="' . $element['width'] . '" alt="' . $element['alt'] . '"/>
'; ';
if(!empty($element['link'])) { if(!empty($element['link'])) {
$image_template = '<a href="' . $element['link'] . '">' . $image_template . '</a>'; $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_bottom mailpoet_padded_side' : 'mailpoet_padded_bottom') . '" align="center" valign="top">
' . $image_template . ' ' . $image_template . '
</td> </td>
</tr>'; </tr>';

View File

@ -1,10 +1,12 @@
<?php <?php
namespace MailPoet\Newsletter\Renderer\Blocks; namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Renderer { class Renderer {
function render($data, $column_count) { function render($data, $column_count) {
$block_content = ''; $block_content = '';
array_map(function ($block) use (&$block_content, &$columns, $column_count) { array_map(function($block) use (&$block_content, &$columns, $column_count) {
$block_content .= $this->createElementFromBlockType($block, $column_count); $block_content .= $this->createElementFromBlockType($block, $column_count);
if(isset($block['blocks'])) { if(isset($block['blocks'])) {
$block_content = $this->render($block, $column_count); $block_content = $this->render($block, $column_count);
@ -18,6 +20,7 @@ class Renderer {
} }
function createElementFromBlockType($block, $column_count) { function createElementFromBlockType($block, $column_count) {
$block = StylesHelper::setTextAlign($block);
$block_class = __NAMESPACE__ . '\\' . ucfirst($block['type']); $block_class = __NAMESPACE__ . '\\' . ucfirst($block['type']);
return (class_exists($block_class)) ? $block_class::render($block, $column_count) : ''; return (class_exists($block_class)) ? $block_class::render($block, $column_count) : '';
} }

View File

@ -13,7 +13,7 @@ class Social {
} }
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded" valign="top" align="center"> <td class="mailpoet_padded_side maipoet_padded_bottom" valign="top" align="center">
' . $icons_block . ' ' . $icons_block . '
</td> </td>
</tr>'; </tr>';

View File

@ -1,6 +1,8 @@
<?php <?php
namespace MailPoet\Newsletter\Renderer\Blocks; namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Text { class Text {
static function render($element) { static function render($element) {
$html = $element['text']; $html = $element['text'];
@ -8,10 +10,10 @@ class Text {
$html = self::convertParagraphsToTables($html); $html = self::convertParagraphsToTables($html);
$html = self::styleLists($html); $html = self::styleLists($html);
$html = self::styleHeadings($html); $html = self::styleHeadings($html);
$html = self::addLineBreakAfterTags($html); $html = self::removeLastLineBreak($html);
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_text mailpoet_padded" valign="top" style="word-break:break-word;word-wrap:break-word;"> <td class="mailpoet_text mailpoet_padded_bottom mailpoet_padded_side" valign="top" style="word-break:break-word;word-wrap:break-word;">
' . $html . ' ' . $html . '
</td> </td>
</tr>'; </tr>';
@ -54,6 +56,14 @@ class Text {
</tr> </tr>
</tbody>' </tbody>'
); );
$blockquote->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$blockquote->index() + 1
);
} }
return $DOM->__toString(); return $DOM->__toString();
} }
@ -70,16 +80,23 @@ class Text {
continue; continue;
} }
$style = $paragraph->style; $style = $paragraph->style;
if(!preg_match('/text-align/i', $style)) {
$style = 'text-align: left;' . $style;
}
$contents = $paragraph->html(); $contents = $paragraph->html();
$paragraph->setTag('table'); $paragraph->setTag('table');
$paragraph->style = 'border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;'; $paragraph->style = 'border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;';
$paragraph->width = '100%'; $paragraph->width = '100%';
$paragraph->cellpadding = 0; $paragraph->cellpadding = 0;
$next_element = $paragraph->getNextSibling();
// unless this is the last element in column, add double line breaks
$line_breaks = ($next_element && !empty($next_element->getInnerText())) ? '<br /><br />' : '';
// if this element is followed by a list, add single line break
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->getInnerText())) ? '<br />' : $line_breaks;
$paragraph->html(' $paragraph->html('
<tr> <tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '"> <td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '">
' . $contents . ' ' . $contents . $line_breaks . '
<br /><br />
</td> </td>
</tr>' </tr>'
); );
@ -94,11 +111,13 @@ class Text {
if(!$lists->count()) return $html; if(!$lists->count()) return $html;
foreach($lists as $list) { foreach($lists as $list) {
if($list->tag === 'li') { if($list->tag === 'li') {
$list->setInnertext($list->text());
$list->class = 'mailpoet_paragraph'; $list->class = 'mailpoet_paragraph';
} else { } else {
$list->class = 'mailpoet_paragraph'; $list->class = 'mailpoet_paragraph';
$list->style .= 'padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;'; $list->style .= 'padding-top:0;padding-bottom:0;margin-top:10px;';
} }
$list->style .= 'margin-bottom:10px;';
} }
return $DOM->__toString(); return $DOM->__toString();
} }
@ -109,27 +128,12 @@ class Text {
$headings = $DOM->query('h1, h2, h3, h4'); $headings = $DOM->query('h1, h2, h3, h4');
if(!$headings->count()) return $html; if(!$headings->count()) return $html;
foreach($headings as $heading) { foreach($headings as $heading) {
$heading->style .= 'margin:0;font-style:normal;font-weight:normal;'; $heading->style .= 'padding:0;font-style:normal;font-weight:normal;';
} }
return $DOM->__toString(); return $DOM->__toString();
} }
static function addLineBreakAfterTags($html) { static function removeLastLineBreak($html) {
$DOM_parser = new \pQuery(); return preg_replace('/(^)?(<br.*?\/?>)+$/i', '', $html);
$DOM = $DOM_parser->parseStr($html);
$tags = $DOM->query('ul, ol, h1, h2, h3, h4, table.mailpoet_blockquote');
if(!$tags->count()) return $html;
foreach($tags as $tag) {
$tag->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$tag->index() + 1
);
}
// remove last line break
return preg_replace('/(^)?(<br.*?\/?>)+$/i', '', $DOM->__toString());
} }
} }

View File

@ -22,6 +22,7 @@ class Renderer {
} }
function getOneColumnTemplate($styles, $class) { function getOneColumnTemplate($styles, $class) {
$background_color = $this->getBackgroundColor($styles);
$template['content_start'] = ' $template['content_start'] = '
<tr> <tr>
<td class="mailpoet_content" align="center" style="border-collapse:collapse"> <td class="mailpoet_content" align="center" style="border-collapse:collapse">
@ -29,7 +30,7 @@ class Renderer {
<tbody> <tbody>
<tr> <tr>
<td style="padding-left:0;padding-right:0"> <td style="padding-left:0;padding-right:0">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_' . $class . '" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;background-color:' . $styles['backgroundColor'] . '!important;" bgcolor="' . $styles['backgroundColor'] . '"> <table width="100%" border="0" cellpadding="0" cellspacing="0" class="mailpoet_' . $class . '" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;table-layout:fixed;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;' . $background_color . '">
<tbody>'; <tbody>';
$template['content_end'] = ' $template['content_end'] = '
</tbody> </tbody>
@ -44,9 +45,10 @@ class Renderer {
} }
function getMultipleColumnsTemplate($styles, $width, $alignment, $class) { function getMultipleColumnsTemplate($styles, $width, $alignment, $class) {
$background_color = $this->getBackgroundColor($styles);
$template['container_start'] = ' $template['container_start'] = '
<tr> <tr>
<td class="mailpoet_content-' . $class . '" align="left" style="border-collapse:collapse;background-color:' . $styles['backgroundColor'] . '!important;" bgcolor="' . $styles['backgroundColor'] . '"> <td class="mailpoet_content-' . $class . '" align="left" style="border-collapse:collapse;' . $background_color . '">
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0"> <table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tbody> <tbody>
<tr> <tr>
@ -78,6 +80,14 @@ class Renderer {
} }
function removePaddingFromLastElement($element) { function removePaddingFromLastElement($element) {
return preg_replace('/mailpoet_padded(?!.*mailpoet_padded)/ism', '', $element); return preg_replace('/mailpoet_padded_bottom(?!.*mailpoet_padded_bottom)/ism', '', $element);
}
function getBackgroundColor($styles) {
if(!isset($styles['backgroundColor'])) return false;
$background_color = $styles['backgroundColor'];
return ($background_color !== 'transparent') ?
sprintf('background-color:%s!important;" bgcolor="%s', $background_color, $background_color) :
false;
} }
} }

View File

@ -43,9 +43,12 @@ 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,
@ -59,41 +62,18 @@ class Renderer {
$css = ''; $css = '';
foreach($styles as $selector => $style) { foreach($styles as $selector => $style) {
switch($selector) { switch($selector) {
case 'h1': case 'text':
$selector = 'h1'; $selector = 'td.mailpoet_paragraph, td.mailpoet_blockquote, li.mailpoet_paragraph';
break; break;
case 'h2': case 'body':
$selector = 'h2'; $selector = 'body, .mailpoet-wrapper';
break; break;
case 'h3': case 'link':
$selector = 'h3'; $selector = '.mailpoet-wrapper a';
break; break;
case 'text': case 'wrapper':
$selector = '.mailpoet_paragraph, td.mailpoet_blockquote'; $selector = '.mailpoet_content-wrapper';
break; break;
case 'body':
$selector = 'body, .mailpoet-wrapper';
break;
case 'link':
$selector = '.mailpoet-wrapper a';
break;
case 'wrapper':
$selector = '.mailpoet_content-wrapper';
break;
}
if(isset($style['fontSize'])) {
$css .= StylesHelper::setFontAndLineHeight(
(int) $style['fontSize'],
$selector
);
unset($style['fontSize']);
}
if(isset($style['fontFamily'])) {
$css .= StylesHelper::setFontFamily(
$style['fontFamily'],
$selector
);
unset($style['fontFamily']);
} }
$css .= StylesHelper::setStyle($style, $selector); $css .= StylesHelper::setStyle($style, $selector);
} }
@ -101,7 +81,7 @@ class Renderer {
} }
function injectContentIntoTemplate($template, $data) { function injectContentIntoTemplate($template, $data) {
return preg_replace_callback('/{{\w+}}/', function ($matches) use (&$data) { return preg_replace_callback('/{{\w+}}/', function($matches) use (&$data) {
return array_shift($data); return array_shift($data);
}, $template); }, $template);
} }

View File

@ -1,8 +1,6 @@
<?php <?php
namespace MailPoet\Newsletter\Renderer; namespace MailPoet\Newsletter\Renderer;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
class StylesHelper { class StylesHelper {
static $css_attributes = array( static $css_attributes = array(
'backgroundColor' => 'background-color', 'backgroundColor' => 'background-color',
@ -29,174 +27,8 @@ class StylesHelper {
'Trebuchet MS' => "'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif", 'Trebuchet MS' => "'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif",
'Verdana' => 'Verdana, Geneva, sans-serif' 'Verdana' => 'Verdana, Geneva, sans-serif'
); );
static $font_size = array( static $line_height_multiplier = 1.6;
// font_size => array(columnCount => lineHeight); static $heading_margin_multiplier = 0.3;
8 => array(
1 => "20",
2 => "15",
3 => "13"
),
9 => array(
1 => "20",
2 => "16",
3 => "14"
),
10 => array(
1 => "20",
2 => "17",
3 => "15"
),
11 => array(
1 => "21",
2 => "18",
3 => "16"
),
12 => array(
1 => "22",
2 => "19",
3 => "17"
),
13 => array(
1 => "23",
2 => "20",
3 => "19"
),
14 => array(
1 => "24",
2 => "21",
3 => "20"
),
15 => array(
1 => "25",
2 => "22",
3 => "21"
),
16 => array(
1 => "26",
2 => "23",
3 => "22"
),
17 => array(
1 => "27",
2 => "24",
3 => "24"
),
18 => array(
1 => "28",
2 => "25",
3 => "25"
),
19 => array(
1 => "29",
2 => "27",
3 => "26"
),
20 => array(
1 => "30",
2 => "28",
3 => "27"
),
21 => array(
1 => "31",
2 => "29",
3 => "29"
),
22 => array(
1 => "32",
2 => "30",
3 => "30"
),
23 => array(
1 => "33",
2 => "32",
3 => "31"
),
24 => array(
1 => "34",
2 => "33",
3 => "32"
),
25 => array(
1 => "36",
2 => "34",
3 => "34"
),
26 => array(
1 => "37",
2 => "35",
3 => "35"
),
27 => array(
1 => "38",
2 => "37",
3 => "36"
),
28 => array(
1 => "39",
2 => "38",
3 => "37"
),
29 => array(
1 => "40",
2 => "39",
3 => "39"
),
30 => array(
1 => "42",
2 => "40",
3 => "40"
),
31 => array(
1 => "43",
2 => "42",
3 => "41"
),
32 => array(
1 => "44",
2 => "43",
3 => "43"
),
33 => array(
1 => "45",
2 => "44",
3 => "44"
),
34 => array(
1 => "47",
2 => "46",
3 => "45"
),
35 => array(
1 => "48",
2 => "47",
3 => "46"
),
36 => array(
1 => "49",
2 => "48",
3 => "48"
),
37 => array(
1 => "50",
2 => "49",
3 => "49"
),
38 => array(
1 => "52",
2 => "51",
3 => "50"
),
39 => array(
1 => "53",
2 => "52",
3 => "52"
),
40 => array(
1 => "54",
2 => "53",
3 => "53"
)
);
static $padding_width = 20; static $padding_width = 20;
static function getBlockStyles($element, $ignore_specific_styles = false) { static function getBlockStyles($element, $ignore_specific_styles = false) {
@ -207,7 +39,7 @@ class StylesHelper {
} }
static function getStyles($data, $type, $ignore_specific_styles = false) { static function getStyles($data, $type, $ignore_specific_styles = false) {
$styles = array_map(function ($attribute, $style) use ($ignore_specific_styles) { $styles = array_map(function($attribute, $style) use ($ignore_specific_styles) {
if(!$ignore_specific_styles || !in_array($attribute, $ignore_specific_styles)) { if(!$ignore_specific_styles || !in_array($attribute, $ignore_specific_styles)) {
return self::translateCSSAttribute($attribute) . ': ' . $style . ' !important;'; return self::translateCSSAttribute($attribute) . ': ' . $style . ' !important;';
} }
@ -221,33 +53,52 @@ class StylesHelper {
$attribute; $attribute;
} }
static function setFontFamily($font_family, $selector) {
$font_family = (isset(self::$font[$font_family])) ?
self::$font[$font_family] :
self::$font['Arial'];
$css = $selector . '{' . PHP_EOL;
$css .= 'font-family:' . $font_family . ';' . PHP_EOL;
$css .= '}' . PHP_EOL;
return $css;
}
static function setFontAndLineHeight($font_size, $selector) {
$css = '';
foreach(ColumnsHelper::columnClasses() as $column_count => $column_class) {
$css .= '.mailpoet_content-' . $column_class . ' ' . $selector . '{' . PHP_EOL;
$css .= 'font-size:' . $font_size . 'px;' . PHP_EOL;
$css .= 'line-height:' . StylesHelper::$font_size[$font_size][$column_count] . 'px;' . PHP_EOL;
$css .= '}' . PHP_EOL;
}
return $css;
}
static function setStyle($style, $selector) { static function setStyle($style, $selector) {
$css = $selector . '{' . PHP_EOL; $css = $selector . '{' . PHP_EOL;
$style = self::applyHeadingMargin($style, $selector);
$style = self::applyLineHeight($style, $selector);
foreach($style as $attribute => $individual_style) { foreach($style as $attribute => $individual_style) {
$individual_style = self::applyFontFamily($attribute, $individual_style);
$css .= self::translateCSSAttribute($attribute) . ':' . $individual_style . ';' . PHP_EOL; $css .= self::translateCSSAttribute($attribute) . ':' . $individual_style . ';' . PHP_EOL;
} }
$css .= '}' . PHP_EOL; $css .= '}' . PHP_EOL;
return $css; return $css;
} }
static function setTextAlign($block) {
$alignments = array(
'center',
'right',
'justify'
);
$text_alignment = isset($block['styles']['block']['textAlign']) ?
strtolower($block['styles']['block']['textAlign']) :
false;
if(in_array($text_alignment, $alignments)) {
return $block;
}
$block['styles']['block']['textAlign'] = 'left';
return $block;
}
static function applyFontFamily($attribute, $style) {
if($attribute !== 'fontFamily') return $style;
return (isset(self::$font[$style])) ?
self::$font[$style] :
self::$font['Arial'];
}
static function applyHeadingMargin($style, $selector) {
if (!preg_match('/h[1-4]/i', $selector)) return $style;
$font_size = (int) $style['fontSize'];
$style['margin'] = sprintf('0 0 %spx 0', self::$heading_margin_multiplier * $font_size);
return $style;
}
static function applyLineHeight($style, $selector) {
if (!preg_match('/mailpoet_paragraph|h[1-4]/i', $selector)) return $style;
$font_size = (int) $style['fontSize'];
$style['lineHeight'] = sprintf('%spx', self::$line_height_multiplier * $font_size);
return $style;
}
} }

View File

@ -35,22 +35,27 @@
outline: none; outline: none;
text-align: center; text-align: center;
} }
.mailpoet_padded { .mailpoet_padded_bottom {
padding-bottom: 20px;
}
.mailpoet_padded_side {
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
padding-bottom: 20px;
} }
.mailpoet_header_footer_padded { .mailpoet_header_footer_padded {
padding: 10px 20px; padding: 10px 20px;
} }
@media screen and (max-width: 480px) and (-webkit-min-device-pixel-ratio: 1) {
.mailpoet_button {width:100% !important;}
}
@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) {
.mailpoet_header { .mailpoet_header {
padding: 10px 20px; padding: 10px 20px;
} }
.mailpoet_button { .mailpoet_button {
width: 100% !important; width: 100% !important;
max-width: 100% !important;
padding: 5px 0 !important; padding: 5px 0 !important;
box-sizing:border-box !important;
} }
div, .mailpoet_cols-two, .mailpoet_cols-three { div, .mailpoet_cols-two, .mailpoet_cols-three {
max-width: 100% !important; max-width: 100% !important;

View File

@ -0,0 +1,131 @@
<?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':
$schedule = '* * * * *';
break;
case 'immediate':
case 'daily':
$schedule = sprintf('0 %s * * *', $hour);
break;
case 'weekly':
$schedule = sprintf('0 %s * * %s', $hour, $week_day);
break;
case 'monthly':
$schedule = sprintf('0 %s %s * *', $hour, $month_day);
break;
case 'nthWeekDay':
$schedule = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day);
break;
}
$option_field = NewsletterOptionField::where('name', 'schedule')
->findOne()
->asArray();
$relation = NewsletterOption::where('newsletter_id', $newsletter_id)
->where('option_field_id', $option_field['id'])
->findOne();
if(!$relation) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter['id'];
$relation->option_field_id = $option_field['id'];
}
$relation->value = $schedule;
$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, $old_user_data) {
$newsletters = self::getWelcomeNewsletters();
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'user') {
if($old_user_data) {
// do not schedule welcome newsletter if roles have not changed
$old_role = (array) $old_user_data->roles;
$new_role = (array) $wp_user->roles;
if($newsletter['role'] === 'mailpoet_all' ||
!array_diff($old_role, $new_role)
) {
continue;
}
}
if($newsletter['role'] === 'mailpoet_all' ||
in_array($newsletter['role'], $wp_user['roles'])
) {
self::createSendingQueueEntry($newsletter, $subscriber_id);
}
}
}
}
private static function getWelcomeNewsletters() {
return Newsletter::where('type', 'welcome')
->whereNull('deleted_at')
->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;
$current_time = Carbon::createFromTimestamp(current_time('timestamp'));
switch($after_time_type) {
case 'hours':
$scheduled_at = $current_time->addHours($after_time_number);
break;
case 'days':
$scheduled_at = $current_time->addDays($after_time_number);
break;
case 'weeks':
$scheduled_at = $current_time->addWeeks($after_time_number);
break;
default:
$scheduled_at = $current_time;
}
$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

@ -1,6 +1,8 @@
<?php <?php
namespace MailPoet\Newsletter\Shortcodes\Categories; namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\SendingQueue;
class Newsletter { class Newsletter {
/* /*
{ {
@ -18,26 +20,54 @@ 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, $subscriber = false, $text) {
if(is_object($newsletter)) { if(is_object($newsletter)) {
$newsletter = $newsletter->asArray(); $newsletter = $newsletter->asArray();
} }
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(); return substr_count($text, 'data-post-id');
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 if ($newsletter['type'] !== 'notification') return false;
return; $sent_newsletters = (int)
SendingQueue::where('newsletter_id', $newsletter['id'])->count();
return ++$sent_newsletters;
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

@ -2,29 +2,25 @@
namespace MailPoet\Newsletter\Shortcodes; namespace MailPoet\Newsletter\Shortcodes;
class Shortcodes { class Shortcodes {
public $rendered_newsletter;
public $newsletter; public $newsletter;
public $subscriber; public $subscriber;
function __construct( function __construct(
$rendered_newsletter,
$newsletter = false, $newsletter = false,
$subscriber = false) { $subscriber = false
$this->rendered_newsletter = $rendered_newsletter; ) {
$this->newsletter = $newsletter; $this->newsletter = $newsletter;
$this->subscriber = $subscriber; $this->subscriber = $subscriber;
} }
function extract() { function extract($text) {
preg_match_all('/\[(?:\w+):.*?\]/', $this->rendered_newsletter, $shortcodes); preg_match_all('/\[(?:\w+):.*?\]/', $text, $shortcodes);
return array_unique($shortcodes[0]); return array_unique($shortcodes[0]);
} }
function process($shortcodes) { function process($shortcodes, $text) {
$processed_shortcodes = array_map( $processed_shortcodes = array_map(
function ($shortcode) { function($shortcode) use($text) {
// 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,
@ -32,22 +28,25 @@ class Shortcodes {
); );
$shortcode_class = $shortcode_class =
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']); __NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
$shortcode_action = $shortcode_details['action'];
$shortcode_default_value = isset($shortcode_details['default'])
? $shortcode_details['default'] : false;
if(!class_exists($shortcode_class)) return false; if(!class_exists($shortcode_class)) return false;
return $shortcode_class::process( return $shortcode_class::process(
$shortcode_details['action'], $shortcode_action,
isset($shortcode_details['default']) $shortcode_default_value,
? $shortcode_details['default'] : false,
$this->newsletter, $this->newsletter,
$this->subscriber $this->subscriber,
$text
); );
}, $shortcodes); }, $shortcodes);
return array_filter($processed_shortcodes); return $processed_shortcodes;
} }
function replace() { function replace($text) {
$shortcodes = $this->extract($this->rendered_newsletter); $shortcodes = $this->extract($text);
$processed_shortcodes = $this->process($shortcodes); $processed_shortcodes = $this->process($shortcodes, $text);
$shortcodes = array_intersect_key($shortcodes, $processed_shortcodes); $shortcodes = array_intersect_key($shortcodes, $processed_shortcodes);
return str_replace($shortcodes, $processed_shortcodes, $this->rendered_newsletter); return str_replace($shortcodes, $processed_shortcodes, $text);
} }
} }

View File

@ -1,7 +1,6 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use Carbon\Carbon;
use MailPoet\Cron\CronHelper; use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Supervisor; use MailPoet\Cron\Supervisor;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
@ -30,26 +29,8 @@ class Cron {
function getStatus() { function getStatus() {
$daemon = Setting::where('name', 'cron_daemon') $daemon = Setting::where('name', 'cron_daemon')
->findOne(); ->findOne();
return ( return ($daemon) ?
($daemon) ? unserialize($daemon->value) :
array_merge( false;
array(
'timeSinceStart' =>
Carbon::createFromFormat(
'Y-m-d H:i:s',
$daemon->created_at,
'UTC'
)->diffForHumans(),
'timeSinceUpdate' =>
Carbon::createFromFormat(
'Y-m-d H:i:s',
$daemon->updated_at,
'UTC'
)->diffForHumans()
),
unserialize($daemon->value)
) :
"false"
);
} }
} }

View File

@ -14,6 +14,8 @@ 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;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -32,7 +34,7 @@ class Newsletters {
$newsletter['segments'] = array_map(function($segment) { $newsletter['segments'] = array_map(function($segment) {
return $segment['id']; return $segment['id'];
}, $segments); }, $segments);
$newsletter['options'] = $options; $newsletter['options'] = Helpers::arrayColumn($options, 'value', 'name');
return $newsletter; return $newsletter;
} }
} }
@ -138,11 +140,8 @@ class Newsletters {
} }
$renderer = new Renderer($data); $renderer = new Renderer($data);
$rendered_newsletter = $renderer->render(); $rendered_newsletter = $renderer->render();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes( $shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes($data);
$rendered_newsletter['html'], $rendered_newsletter = $shortcodes->replace($rendered_newsletter['html']);
$data
);
$rendered_newsletter = $shortcodes->replace();
return array('rendered_body' => $rendered_newsletter); return array('rendered_body' => $rendered_newsletter);
} }
@ -158,7 +157,7 @@ class Newsletters {
if(empty($data['subscriber'])) { if(empty($data['subscriber'])) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Please specify receiver information')) 'errors' => array(__('Please specify receiver information.'))
); );
} }
@ -166,20 +165,17 @@ class Newsletters {
$renderer = new Renderer($newsletter); $renderer = new Renderer($newsletter);
$rendered_newsletter = $renderer->render(); $rendered_newsletter = $renderer->render();
$divider = '***MailPoet***';
$data_for_shortcodes =
array_merge(array($newsletter['subject']), $rendered_newsletter);
$body = implode($divider, $data_for_shortcodes);
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes( $shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$rendered_newsletter['html'],
$newsletter $newsletter
); );
$processed_newsletter['html'] = $shortcodes->replace(); list($newsletter['subject'],
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes( $newsletter['body']['html'],
$rendered_newsletter['text'], $newsletter['body']['text']
$newsletter ) = explode($divider, $shortcodes->replace($body));
);
$processed_newsletter['text'] = $shortcodes->replace();
$newsletter['body'] = array(
'html' => $processed_newsletter['html'],
'text' => $processed_newsletter['text'],
);
try { try {
$mailer = new \MailPoet\Mailer\Mailer( $mailer = new \MailPoet\Mailer\Mailer(
@ -265,6 +261,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

@ -2,14 +2,17 @@
namespace MailPoet\Router; namespace MailPoet\Router;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
use Cron\CronExpression as Cron;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendingQueue { class SendingQueue {
function add($data) { function add($data) {
// check if mailer is properly configured
try { try {
new Mailer(false); new Mailer(false);
} catch(\Exception $e) { } catch(\Exception $e) {
@ -19,45 +22,81 @@ class SendingQueue {
); );
} }
$queue = \MailPoet\Models\SendingQueue::whereNull('status') $newsletter = Newsletter::filter('filterWithOptions')
->where('newsletter_id', $data['newsletter_id']) ->findOne($data['newsletter_id']);
->findArray(); if(!$newsletter) {
return array(
'result' => false,
'errors' => array(__('Newsletter does not exist.'))
);
}
if($newsletter->type === 'welcome') {
return array(
'result' => true
);
}
$queue = \MailPoet\Models\SendingQueue::whereNull('status')
->where('newsletter_id', $newsletter->id)
->findOne();
if(!empty($queue)) { if(!empty($queue)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Send operation is already in progress.')) 'errors' => array(__('Send operation is already in progress.'))
); );
} }
$queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) { if($newsletter->type === 'notification') {
$subscriber_ids = array_merge( $option_field = NewsletterOptionField::where('name', 'segments')
$subscriber_ids, ->where('newsletter_type', 'notification')
Helpers::arrayColumn( ->findOne();
$segment->subscribers()->findArray(), 'id' $relation = NewsletterOption::where('option_field_id', $option_field->id)
) ->findOne();
if(!$relation) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter->id;
$relation->option_field_id = $option_field->id;
}
$relation->value = serialize($data['segments']);
$relation->save();
$queue = \MailPoet\Models\SendingQueue::where('status', 'scheduled')
->where('newsletter_id', $newsletter->id)
->findOne();
if(!$queue) {
$queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $newsletter->id;
}
$schedule = Cron::factory($newsletter->schedule);
$queue->scheduled_at =
$schedule->getNextRunDate(current_time('mysql'))->format('Y-m-d H:i:s');
$queue->status = 'scheduled';
$queue->save();
return array(
'result' => true,
'data' => array(__('Newsletter was scheduled for sending.'))
); );
} }
if(empty($subscriber_ids)) { $queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $newsletter->id;
$subscribers = SubscriberSegment::whereIn('segment_id', $data['segments'])
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
if(!count($subscribers)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('There are no subscribers.')) 'errors' => array(__('There are no subscribers.'))
); );
} }
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = serialize( $queue->subscribers = serialize(
array( array(
'to_process' => $subscriber_ids 'to_process' => $subscribers
) )
); );
$queue->count_total = $queue->count_to_process = count($subscriber_ids); $queue->count_total = $queue->count_to_process = count($subscribers);
$queue->save(); $queue->save();
$errors = $queue->getErrors(); $errors = $queue->getErrors();
if(!empty($errors)) { if(!empty($errors)) {

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, $old_wp_user_data = false) {
$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':
@ -20,23 +18,22 @@ class WP {
if($subscriber !== false && $subscriber->id()) { if($subscriber !== false && $subscriber->id()) {
$subscriber->delete(); $subscriber->delete();
} }
break; break;
case 'user_register':
case 'added_existing_user':
case 'profile_update': case 'profile_update':
case 'user_register':
$schedule_welcome_newsletter = true;
case 'added_existing_user':
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,13 +43,19 @@ 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->getErrors() === false && $subscriber->id > 0) {
if($segment !== false) { if($segment !== false) {
$segment->addSubscriber($subscriber->id); $segment->addSubscriber($subscriber->id);
} }
if(isset($schedule_welcome_newsletter)) {
Scheduler::welcomeForNewWPUser(
$subscriber->id,
(array) $wp_user,
$old_wp_user_data
);
}
} }
break; break;
} }
} }

View File

@ -18,23 +18,6 @@ class Hosts {
'EU (Ireland)' => 'eu-west-1' 'EU (Ireland)' => 'eu-west-1'
) )
), ),
'ElasticEmail' => array(
'name' => 'ElasticEmail',
'emails' => 100,
'interval' => 5,
'fields' => array(
'api_key'
)
),
'MailGun' => array(
'name' => 'MailGun',
'emails' => 100,
'interval' => 5,
'fields' => array(
'domain',
'api_key'
)
),
'SendGrid' => array( 'SendGrid' => array(
'name' => 'SendGrid', 'name' => 'SendGrid',
'emails' => 100, 'emails' => 100,

View File

@ -66,9 +66,8 @@ class Pages {
'id' => $page->ID, 'id' => $page->ID,
'title' => $page->post_title, 'title' => $page->post_title,
'preview_url' => add_query_arg(array( 'preview_url' => add_query_arg(array(
'preview' => 1 'mailpoet_preview' => 1
), get_permalink($page->ID)), ), get_permalink($page->ID))
'edit_url' => get_edit_post_link($page->ID)
); );
} }
} }

View File

@ -226,7 +226,7 @@ class Export {
function getExportFileURL($file) { function getExportFileURL($file) {
return sprintf( return sprintf(
'%s/%s', '%s/%s',
Env::$temp_URL, Env::$temp_url,
basename($file) basename($file)
); );
} }

View File

@ -8,6 +8,7 @@ use \MailPoet\Models\Setting;
use \MailPoet\Models\Segment; use \MailPoet\Models\Segment;
use \MailPoet\Util\Helpers; use \MailPoet\Util\Helpers;
use \MailPoet\Util\Url; use \MailPoet\Util\Url;
use \MailPoet\Subscription;
class Pages { class Pages {
const DEMO_EMAIL = 'demo@mailpoet.com'; const DEMO_EMAIL = 'demo@mailpoet.com';
@ -16,10 +17,12 @@ class Pages {
} }
function init() { function init() {
if(isset($_GET['mailpoet_page'])) { $action = $this->getAction();
add_filter('wp_title', array($this,'setWindowTitle')); if($action !== null) {
add_filter('the_title', array($this,'setPageTitle')); add_filter('wp_title', array($this,'setWindowTitle'), 10, 3);
add_filter('the_content', array($this,'setPageContent')); add_filter('document_title_parts', array($this,'setWindowTitleParts'), 10, 1);
add_filter('the_title', array($this,'setPageTitle'), 10, 1);
add_filter('the_content', array($this,'setPageContent'), 10, 1);
} }
add_action( add_action(
'admin_post_mailpoet_subscriber_save', 'admin_post_mailpoet_subscriber_save',
@ -42,49 +45,94 @@ class Pages {
$_POST, $_POST,
array_flip($reserved_keywords) array_flip($reserved_keywords)
); );
if(isset($subscriber_data['email'])) {
$subscriber = Subscriber::createOrUpdate($subscriber_data); if($subscriber_data['email'] !== self::DEMO_EMAIL) {
$errors = $subscriber->getErrors(); $subscriber = Subscriber::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
}
}
// TBD: success/error messages (not present in MP2) // TBD: success/error messages (not present in MP2)
Url::redirectBack(); Url::redirectBack();
} }
function isPreview() { function isPreview() {
return (array_key_exists('preview', $_GET)); return (array_key_exists('mailpoet_preview', $_GET));
} }
function setWindowTitle() { function setWindowTitle($title, $separator, $separator_location = 'right') {
// TODO $title_parts = explode(" $separator ", $title);
} if($separator_location === 'right') {
// first part
function setPageTitle($title = null) { $title_parts[0] = $this->setPageTitle($title_parts[0]);
$subscriber = $this->getSubscriber(); } else {
// last part
switch($this->getAction()) { $last_index = count($title_parts) - 1;
case 'confirm': $title_parts[$last_index] = $this->setPageTitle($title_parts[$last_index]);
return $this->getConfirmTitle($subscriber);
break;
case 'edit':
return $this->getEditTitle($subscriber);
break;
case 'unsubscribe':
if($subscriber !== false) {
if($subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$subscriber->save();
}
}
return $this->getUnsubscribeTitle($subscriber);
break;
} }
return $title; return implode(" $separator ", $title_parts);
}
function setWindowTitleParts($meta = array()) {
$meta['title'] = $this->setPageTitle($meta['title']);
return $meta;
}
function isMailPoetPage($page_id = null) {
$mailpoet_page_ids = array_unique(array_values(
Setting::getValue('subscription', array())
));
return (in_array($page_id, $mailpoet_page_ids));
}
function setPageTitle($page_title = '') {
global $post;
if($post->post_title !== __('MailPoet Page')) return $page_title;
if(
($this->isMailPoetPage($post->ID) === false)
||
($page_title !== single_post_title('', false))
) {
return $page_title;
} else {
$subscriber = $this->getSubscriber();
switch($this->getAction()) {
case 'confirm':
return $this->getConfirmTitle($subscriber);
break;
case 'manage':
return $this->getManageTitle($subscriber);
break;
case 'unsubscribe':
if($subscriber !== false) {
if($subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$subscriber->save();
}
}
return $this->getUnsubscribeTitle($subscriber);
break;
}
}
return $page_title;
} }
function setPageContent($page_content = '[mailpoet_page]') { function setPageContent($page_content = '[mailpoet_page]') {
global $post;
if(
($this->isPreview() === false)
&&
($this->isMailPoetPage($post->ID) === false)
) {
return $page_content;
}
$content = ''; $content = '';
$subscriber = $this->getSubscriber(); $subscriber = $this->getSubscriber();
@ -92,8 +140,8 @@ class Pages {
case 'confirm': case 'confirm':
$content = $this->getConfirmContent($subscriber); $content = $this->getConfirmContent($subscriber);
break; break;
case 'edit': case 'manage':
$content = $this->getEditContent($subscriber); $content = $this->getManageContent($subscriber);
break; break;
case 'unsubscribe': case 'unsubscribe':
$content = $this->getUnsubscribeContent($subscriber); $content = $this->getUnsubscribeContent($subscriber);
@ -132,7 +180,7 @@ class Pages {
return $title; return $title;
} }
private function getEditTitle($subscriber) { private function getManageTitle($subscriber) {
if($this->isPreview()) { if($this->isPreview()) {
return sprintf( return sprintf(
__('Edit your subscriber profile: %s'), __('Edit your subscriber profile: %s'),
@ -159,7 +207,7 @@ class Pages {
} }
} }
private function getEditContent($subscriber) { private function getManageContent($subscriber) {
if($this->isPreview()) { if($this->isPreview()) {
$subscriber = Subscriber::create(); $subscriber = Subscriber::create();
$subscriber->hydrate(array( $subscriber->hydrate(array(
@ -213,7 +261,8 @@ class Pages {
'params' => array( 'params' => array(
'label' => __('Email'), 'label' => __('Email'),
'required' => true, 'required' => true,
'value' => $subscriber->email 'value' => $subscriber->email,
'readonly' => true
) )
), ),
array( array(
@ -253,14 +302,6 @@ class Pages {
'is_checked' => ( 'is_checked' => (
$subscriber->status === Subscriber::STATUS_UNSUBSCRIBED $subscriber->status === Subscriber::STATUS_UNSUBSCRIBED
) )
),
array(
'value' => array(
Subscriber::STATUS_UNCONFIRMED => __('Unconfirmed')
),
'is_checked' => (
$subscriber->status === Subscriber::STATUS_UNCONFIRMED
)
) )
) )
) )
@ -310,7 +351,7 @@ class Pages {
$content .= '<p><strong>'. $content .= '<p><strong>'.
str_replace( str_replace(
array('[link]', '[/link]'), array('[link]', '[/link]'),
array('<a href="'.$subscriber->getConfirmationUrl().'">', '</a>'), array('<a href="'.Subscription\Url::getConfirmationUrl($subscriber).'">', '</a>'),
__('You made a mistake? [link]Undo unsubscribe.[/link]') __('You made a mistake? [link]Undo unsubscribe.[/link]')
). ).
'</strong></p>'; '</strong></p>';

57
lib/Subscription/Url.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\Setting;
class Url {
static function getConfirmationUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.confirmation_page'));
return self::getSubscriptionUrl($post, 'confirm', $subscriber);
}
static function getManageUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.manage_page'));
return self::getSubscriptionUrl($post, 'manage', $subscriber);
}
static function getUnsubscribeUrl($subscriber = false) {
$post = get_post(Setting::getValue('subscription.unsubscribe_page'));
return self::getSubscriptionUrl($post, 'unsubscribe', $subscriber);
}
private static function getSubscriptionUrl(
$post = null, $action = null, $subscriber = false
) {
if($post === null || $action === null) return;
$url = get_permalink($post);
if($subscriber !== false) {
if(is_object($subscriber)) {
$subscriber = $subscriber->asArray();
}
$params = array(
'mailpoet_action='.$action,
'mailpoet_token='.Subscriber::generateToken($subscriber['email']),
'mailpoet_email='.$subscriber['email']
);
} else {
$params = array(
'mailpoet_action='.$action,
'mailpoet_preview=1'
);
}
// add parameters
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?').join('&', $params);
$url_params = parse_url($url);
if(empty($url_params['scheme'])) {
$url = get_bloginfo('url').$url;
}
return $url;
}
}

View File

@ -42,10 +42,9 @@ class i18n extends \Twig_Extension {
$output = array(); $output = array();
$output[] = '<script type="text/javascript">'; $output[] = '<script type="text/javascript">';
$output[] = ' var MailPoetI18n = MailPoetI18n || {}';
foreach($translations as $key => $translation) { foreach($translations as $key => $translation) {
$output[] = $output[] =
'MailPoetI18n["'.$key.'"] = "'. str_replace('"', '\"', $translation) . '";'; 'MailPoet.I18n.add("'.$key.'", "'. str_replace('"', '\"', $translation) . '");';
} }
$output[] = '</script>'; $output[] = '</script>';
return join("\n", $output); return join("\n", $output);

View File

@ -116,6 +116,7 @@ class CSS {
public function parseCSS($text) public function parseCSS($text)
{ {
$css = new csstidy(); $css = new csstidy();
$css->settings['compress_colors'] = false;
$css->parse($text); $css->parse($text);
$rules = array(); $rules = array();

View File

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

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