Compare commits

...

141 Commits

Author SHA1 Message Date
5b2ede8b83 Bump up release version to 0.0.45 2016-09-20 12:53:33 +03:00
bff6aecd0d Merge pull request #617 from mailpoet/copy-edit
Updated text
2016-09-19 17:08:19 +03:00
a121583c2d Updated text 2016-09-19 10:13:34 +01:00
8c436180d9 Merge pull request #615 from mailpoet/unit_tests
Adds unit tests for cron scheduler worker
2016-09-15 13:21:34 +03:00
b834a6af4d - Updates cron scheduler worker
- Adds unit tests
2016-09-14 19:00:08 -04:00
1bd8aed192 Bump up release version to 0.0.44, add changelog 2016-09-13 15:59:18 +03:00
cfdf72867e Merge pull request #612 from mailpoet/subscribers_limit
Subscribers limit
2016-09-13 08:49:06 -04:00
5888620fc1 - Updates link to support area 2016-09-13 08:41:37 -04:00
55ba605eb0 fixed typo in test function name 2016-09-13 11:45:33 +02:00
7a73ca7d1a Merge pull request #608 from mailpoet/api_uniform_h
Listing method update for Newsletters/Forms/Segments/Subscribers
2016-09-12 10:12:18 -04:00
1918894c5c fix status code in API + PHP 5.3 errors on object instantiation 2016-09-12 15:59:30 +02:00
0c5589a3e3 Listing method update for Newsletters/Forms/Segments/Subscribers
- updated unit tests
2016-09-12 15:57:16 +02:00
d6eaa4ac8a Merge pull request #614 from mailpoet/links_to_support_articles
Updates links to KB articles
2016-09-12 14:37:32 +02:00
b0571b97f5 added missing KB link in Send with 3rd party 2016-09-12 14:32:59 +02:00
6d51ca8011 Merge pull request #613 from mailpoet/class_instance_creation_update
Class instance creation fix for PHP 5.3
2016-09-12 14:07:38 +02:00
d3289dfb84 Merge pull request #609 from mailpoet/unit_tests
Cron unit tests (batch 1 of 2)
2016-09-12 14:30:29 +03:00
bfffdd7274 - Updates links to KB articles
- Closes #571
2016-09-10 21:12:43 -04:00
c5b8b2aef0 - Fixes "class not found" error in Daemon
- Moves ignore_user_abort() to the run() method
- Updates unit tests
2016-09-10 18:06:48 -04:00
d7bcf1b817 - Updates the way errorResponse class is instantiated 2016-09-10 10:49:20 -04:00
f30ed153ce - Updates the way template classes are instantiated 2016-09-10 10:44:24 -04:00
f436088a16 Subscribers limit
- added "limit.html" template
- subscribers_limit set in Env class
2016-09-09 16:12:59 +02:00
db8cb7499d Merge pull request #611 from mailpoet/copy-editing
Update September 9 2016
2016-09-09 16:49:42 +03:00
49c4adc754 Increase the width of subject and preheader inputs 2016-09-09 16:45:19 +03:00
405e743171 update September 9 2016 2016-09-09 15:04:00 +02:00
3508ac36b4 - Removes daemon status logic and updates tests
- Refactors daemon
- Adds daemon unit tests
2016-09-08 19:22:42 -04:00
f17c8228cd - Adds units tests for cron triggers 2016-09-07 18:21:31 -04:00
3dd5ac0536 Merge pull request #607 from mailpoet/api_uniform_g
Forms / Newsletters / Segments / Subscribers
2016-09-07 14:52:21 -04:00
4ebdff49e0 make Model::setTimestamp() public 2016-09-07 10:26:08 +02:00
4a72995bf4 fix bulk actions and messages 2016-09-06 17:21:15 +02:00
ef27ac0b84 Update changelog for 0.0.43 2016-09-06 12:27:58 +03:00
2b4adef6c2 Bump up release version to 0.0.43 2016-09-06 12:13:21 +03:00
f650455a90 forms & newsletters endpoints 2016-09-05 11:55:01 +02:00
afbe25e215 Segments & Subscribers endpoints
- unit tests
2016-09-05 11:51:58 +02:00
d93249f077 form & listing jsx update + Segment endpoint conversion 2016-09-05 11:51:58 +02:00
6223ef77d9 Merge pull request #605 from mailpoet/editor_fixes
Editor fixes
2016-09-02 08:30:59 -04:00
a423123b66 Add 3 new sample templates Becs prepared 2016-09-02 14:12:19 +03:00
1b3d3082b0 Fix text in template selection boxes to not overflow 2016-09-02 12:48:44 +03:00
fa117cc7dd Add an animation to display tools 2016-09-02 12:48:44 +03:00
acd407c1f1 Clarify label of preheader field 2016-09-02 12:48:44 +03:00
9baf4b068f Swapped block tool default and hover colors. Dark normally, light on
hover
2016-09-02 12:48:44 +03:00
18d852e147 Changed Trash and Move SVG icons to new ones for newsletter editor 2016-09-02 12:48:44 +03:00
b8dc306741 Merge pull request #604 from mailpoet/unit_tests
Unit tests (Cron Supervisor and Trigger)
2016-09-02 12:46:39 +03:00
6ea056c042 - Removes references to cron from webpack's configuration 2016-09-01 19:19:47 -04:00
bcf1b37c6a - Adds unit tests for Cron Trigger class 2016-09-01 19:19:47 -04:00
2986cdba85 - Removes Cron status from MailPoet's WP admin panel 2016-09-01 19:19:47 -04:00
53a8ae74e2 - Adds unit tests for Supervisor class
- Updates execution limit condition check in Supervisor
2016-09-01 19:19:47 -04:00
8bab01506c - Updates CircleCI configuration to run Apache 2016-09-01 19:18:47 -04:00
c664045444 Merge pull request #603 from mailpoet/welcome_page
Welcome and Update pages
2016-09-01 14:02:20 +03:00
6b8149210d Remove installed_at default value from default settings 2016-08-31 16:59:50 +03:00
f31d30b318 Merge pull request #602 from mailpoet/cron_update
Cron update
2016-08-31 16:48:05 +03:00
d9fbbdc02d - Updates code comments 2016-08-31 09:23:12 -04:00
8136ee2d9b Add a timestamp to log when the plugin was installed 2016-08-31 14:42:43 +03:00
f7cf6e2131 Welcome and Update pages 2016-08-31 13:51:33 +03:00
f2d1787bd5 - Updates site URL detection logic
- Adds unit test for Cron Helper class
2016-08-30 12:37:30 -04:00
fb51765d3f Bump up release version to 0.0.42 2016-08-30 13:04:08 +03:00
088ad5fb42 Merge pull request #597 from mailpoet/editor_fixes
Editor fixes
2016-08-29 15:43:47 -04:00
2f5b3c0c0a Merge pull request #600 from mailpoet/circle_ci
Adds CircleCI support
2016-08-29 15:15:39 -04:00
04ac4d896c Remove the build testing error 2016-08-29 21:38:26 +03:00
f3b96af863 Attempt to fix command status codes for Robo commands 2016-08-29 21:32:57 +03:00
eb42b0b98d Merge branch 'circle_ci' of github.com:mailpoet/mailpoet into circle_ci 2016-08-29 21:32:14 +03:00
304667eb49 - Testing commit e-mail 2016-08-29 14:27:40 -04:00
dad1082cd7 - Fixes JS unit test 2016-08-29 14:26:44 -04:00
37cf0f3d29 - Fixes JS unit test
:
2016-08-29 13:58:20 -04:00
d61c6dff58 - Fixes PHP unit test
- Fails JS unit test to check if CircleCI will detect it
2016-08-29 13:52:17 -04:00
3734ac578d - Fails test to check if CircleCI will detect it 2016-08-29 13:48:33 -04:00
b3f56c9d8e Merge pull request #599 from mailpoet/router_unit_tests
Router refactoring and unit tests
2016-08-25 17:33:44 +03:00
3603eeee77 - Updates remaining router endpoints to use constructor and new constants 2016-08-25 10:03:52 -04:00
59d30cc139 - Renames router URL query parameter and router class
- Updates other classes to use the new name
- Updates unit tests
2016-08-25 09:57:14 -04:00
6ff3bbbb72 - Fixes type in method name 2016-08-24 23:35:45 -04:00
a561e10156 - Updates tests for view in browser and statistics tracking 2016-08-24 23:35:34 -04:00
99f2cf6702 - Adds unit tests for front router 2016-08-24 23:27:12 -04:00
c6b72e729b - Refactors front router and endpoints to use dynamic methods 2016-08-24 23:26:13 -04:00
c5bc0f36a4 Disable running PHP coverage reports 2016-08-25 00:46:29 +03:00
efc5c34bf9 Add running PHP unit test coverage reports in CircleCI 2016-08-25 00:34:58 +03:00
3929efbdd9 Enable running JS tests 2016-08-25 00:09:34 +03:00
0e0c41882e Generate XML report for unit tests and add it to CircleCI tracking 2016-08-25 00:05:41 +03:00
79cc708fc6 Set UTC timezone for CircleCI PHP version 2016-08-24 23:50:59 +03:00
8fa98879b8 Enable debugging when running tests 2016-08-24 23:46:22 +03:00
331ba385e9 Switch MySQL host to 127.0.0.1 instead of localhost 2016-08-24 23:30:19 +03:00
71ce46d78d Add running PHP tests 2016-08-24 23:13:06 +03:00
c493de6569 Try to output JS test results in jUnit format for CircleCI 2016-08-24 21:44:22 +03:00
ff2c2ace86 Add running JS tests in CircleCI 2016-08-24 21:29:58 +03:00
7fa789cfd1 Merge pull request #598 from mailpoet/view_in_browser_update
View in browser update
2016-08-24 19:02:15 +03:00
ae6269eb63 - Restricts router access to explicitly defined endpoint actions 2016-08-24 11:23:12 -04:00
a8f4779bfe - Updates code formatting 2016-08-24 10:22:10 -04:00
6868142e35 - Extracts view in browser response to the endpoint
- Updates unit tests
2016-08-24 10:20:35 -04:00
133d123919 - Updates front router and endpoints to use dynamic methods 2016-08-24 10:20:10 -04:00
05c128d12d - Fixes errors thrown when there are no shortcodes in the newsletter body 2016-08-24 09:29:17 -04:00
bdab0c12fa Fix debouncing for ALC refresh to not update multiple times immediately 2016-08-24 16:15:53 +03:00
75b94690e2 - Adds unit tests 2016-08-23 23:42:56 -04:00
80fddd6c58 - Refactors view in browser 2016-08-23 23:42:26 -04:00
c807ead5fd - Prepares newsletter renderer for conversion to using modal objects
instead of arrays
2016-08-23 12:48:38 -04:00
f004bb5368 - Set default preview email to be current user's email;
- Change "Preview in browser" form to autocomplete used emails.
2016-08-23 19:32:10 +03:00
1d756e95a7 Bump up release version to 0.0.41 2016-08-23 12:27:14 +03:00
1fb0da9fda Merge pull request #587 from mailpoet/open_stats_fix
Prevents tracking opens/clicks from subscribers who the newsletter was not sent to
2016-08-23 12:17:51 +03:00
a0017b91ee Merge pull request #592 from mailpoet/editor_fixes
Editor fixes
2016-08-22 20:43:24 -04:00
444ab17342 - Updates statistics tracking unit tests 2016-08-22 16:24:33 -04:00
44f3058326 - Adds unit test for tracker router endpoint 2016-08-22 16:24:13 -04:00
ec09fbcb78 - Converts static classes to dynamic 2016-08-22 16:22:33 -04:00
ed352bb1d3 - Passes wp_user_preview parameter to custom link shortcode filter 2016-08-22 09:26:25 -04:00
375bbd2759 - Calls an open tracking class when tracking clicks 2016-08-22 09:26:25 -04:00
9fb9d25132 - Uses model method to get queue 2016-08-22 09:26:25 -04:00
30f79aa589 - Updates unsubscribe tracking logic 2016-08-22 09:26:25 -04:00
69f8daac95 - Updates wp user & preview check condition 2016-08-22 09:26:25 -04:00
03f3a6080c - Returns empty response or an image in all cases 2016-08-22 09:26:25 -04:00
44f84c6cdb - Updates method names 2016-08-22 09:26:25 -04:00
31008a6895 - Updates queue and subscriber check condition 2016-08-22 09:26:25 -04:00
2490d8c919 - Refactors browser preview 2016-08-22 09:26:25 -04:00
5886dbfd25 - Refactors statistics tracking 2016-08-22 09:26:25 -04:00
e48d55f0b1 - Adds new model methods 2016-08-22 09:26:25 -04:00
42339927cf - Extends ORM's isNew() method to work on saved models 2016-08-22 09:26:25 -04:00
b492bcecc0 - Removes requirement of passing newsletter id when tracking clicks
- Extracts common tracking data processing/validation code into the Track
  class
- Refactors Clicks, Opens and View in Browser classes to enforce
  subscriber id and token check
- Allows admin users to preview newsletters without tracking statistics
2016-08-22 09:26:25 -04:00
6ab7debb7b - Fixes code formatting 2016-08-22 09:26:25 -04:00
b76ce6c26f - Adds verification of newsletter-to-queue and subscriber-to-queue
- Prevents tracking open/click rates for subscribers who the newsletter
  was not sent to
2016-08-22 09:26:25 -04:00
6fbc7b1593 Add "View in browser" to happen on the same page, instead of opening new
window
2016-08-19 18:48:29 +03:00
69c8670b01 Add an option to open settings by clicking on the block for image,
button, social, divider and spacer blocks
2016-08-19 14:57:14 +03:00
da44a87415 Change onkeyup to oninput events to correctly detect pasting into
inputs
2016-08-18 16:55:53 +03:00
9fb17d4a6b Fix "Preview in browser" notice to display a proper success message 2016-08-18 15:07:20 +03:00
16dd286f9d Merge pull request #591 from mailpoet/missing_response
missing response in fail and prevent next on MC import
2016-08-18 12:07:05 +03:00
5025f10f9f missing response in fail and prevent next on MC import 2016-08-17 16:15:54 +02:00
1278d9648c Merge pull request #590 from mailpoet/api_uniform_c
Custom fields & Import & Export
2016-08-17 16:18:53 +03:00
289811a595 Updated Import & MailChimp tests 2016-08-17 13:32:29 +02:00
916ae97f73 Updated export unit test 2016-08-17 12:51:50 +02:00
876e386966 converted export 2016-08-17 12:23:15 +02:00
9582e58dda converted import 2016-08-17 12:16:58 +02:00
213bca8050 fixed rendering of date block + fixed validation for dates 2016-08-16 14:41:53 +02:00
dc97d3115e updated Custom Field endpoint + Unit tests + form editor update 2016-08-16 12:40:10 +02:00
90eb443965 Merge pull request #582 from mailpoet/custom_field_fix
Custom field fix
2016-08-16 12:00:28 +02:00
c5a02c6136 - Allows setting empty value for date custom fields 2016-08-14 13:00:28 -04:00
7f091d7188 - Fixes rebase screwup 2016-08-12 10:38:15 -04:00
81c277ca93 - Update import to autodetect dates (UI) and convert them to datetime
format (backend)
- Fixes unit test
- Fixes code formatting in Date class
2016-08-12 00:29:57 -04:00
f8fea75130 - Updates date conversion method
- Uses Moment to parse dates in UI
- Updates Custom Field model to utilize date conversion method
- Adds unit test
2016-08-11 21:11:57 -04:00
46b0fcf37b - Adds subscriber data validation; specifically for custom fields with
date type
2016-08-08 18:23:11 -04:00
b07c4d0e6e - Adds date validation based on date format 2016-08-08 18:23:11 -04:00
eb107799a7 - Fixes varable name typo
- Declares a new variable during date validate
2016-08-08 18:23:11 -04:00
4eec0a42f9 - Forces date validation to use custom custom field parameter 2016-08-08 18:23:11 -04:00
9a5a3a08c6 - Fixes default date format not being set 2016-08-08 18:23:11 -04:00
151683c632 - Updates Import to use the existing custom field creation code
- Closes #499
2016-08-08 18:23:11 -04:00
fd2103d1aa - Extracts custom field specific templates/code into a separate template
file
2016-08-08 18:23:11 -04:00
7696b6ec5d - Replaces depreciated Notice .error() method with .fail() 2016-08-08 18:23:11 -04:00
d972b96255 - Updates custom field editor to display errors inside the form 2016-08-08 18:23:11 -04:00
179 changed files with 6675 additions and 2856 deletions

View File

@ -0,0 +1,15 @@
Listen 8080
<VirtualHost *:8080>
UseCanonicalName Off
ServerName mailpoet.loc
DocumentRoot /home/ubuntu/mailpoet/wordpress
DirectoryIndex index.php
LogLevel notice
<Directory /home/ubuntu/mailpoet/wordpress>
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

View File

@ -102,31 +102,51 @@ class RoboFile extends \Robo\Tasks {
);
}
function testUnit($file = null) {
function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
$command = 'vendor/bin/codecept run unit -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testCoverage($file = null) {
function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array(
$command = join(' ', array(
'vendor/bin/codecept run',
(($file) ? $file : ''),
(($opts['file']) ? $opts['file'] : ''),
'--coverage',
'--coverage-html'
)));
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
));
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
}
function testJavascript() {
function testJavascript($xml_output_file = null) {
$this->compileJs();
$this->_exec(join(' ', array(
$command = join(' ', array(
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js'
)));
));
if(!empty($xml_output_file)) {
$command .= sprintf(
' --reporter xunit --reporter-options output="%s"',
$xml_output_file
);
}
return $this->_exec($command);
}
function testDebug() {

View File

@ -60,15 +60,20 @@
.mailpoet_boxes .mailpoet_description
float:left
width: 245px
max-height: calc(110px - 2em)
max-height: calc(115px - 2em)
padding-bottom: 2em
overflow: hidden
.mailpoet_boxes .mailpoet_description h3
margin: 0 0 0.7em 0
overflow: hidden
max-width: 210px
line-height: 1.4em
h3
margin: 0 0 0.7em 0
overflow: hidden
max-width: 210px
line-height: 1.4em
p
font-size: 13px
line-height: 1.5
margin: 1em 0
.mailpoet_boxes .mailpoet_actions
position: absolute

View File

@ -1,6 +1,6 @@
$tool-inactive-color = #bbbbbb
$tool-inactive-color = #333333
$tool-inactive-secondary-color = #ffffff
$tool-hover-color = #333333
$tool-hover-color = #bbbbbb
$tool-hover-secondary-color = #ffffff
$tool-active-color = #d2d2d4
$tool-active-secondary-color = #ffffff
@ -12,21 +12,42 @@ $master-column-tool-width = 24px
position: absolute
top: 0
right: 0
display: none
z-index: 20
padding: 2px
text-align: right
overflow: hidden
.mailpoet_tool_slider
position: relative
right: -100%
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000)
opacity: 0
&.mailpoet_display_tools
.mailpoet_tool_slider
right: 0
opacity: 1
a
vertical-align: top
.mailpoet_container_horizontal + &
left: 100%
right: initial
padding-left: 5px
.mailpoet_tool_slider
left: -100%
right: initial
&.mailpoet_display_tools
.mailpoet_tool_slider
left: 0
.mailpoet_tool
width: $master-column-tool-width
height: $master-column-tool-width
display: block
.mailpoet_tool_icon
width: $master-column-tool-width

View File

@ -4,7 +4,7 @@
.mailpoet_input_title,
.mailpoet_input_preheader
width: 400px
width: 500px
padding: 3px
line-height: normal

View File

@ -5,8 +5,8 @@ $active-social-icon-set-border-color = #adadad
$active-social-icon-set-background-color = #daebf2
$social-icon-set-hover-border-color = $primary-active-color
$tool-inactive-color = #bbbbbb
$tool-hover-color = #333333
$tool-inactive-color = #333333
$tool-hover-color = #bbbbbb
$tool-active-color = #d2d2d4
$tool-width = 16px

View File

@ -1,12 +1,12 @@
animation-slide-open-downwards()
animation-slide-open-downwards($max-height = 2000px)
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
max-height: 2000px
max-height: $max-height
opacity: 1
overflow-y: hidden
&.mailpoet_closed
max-height: 0
max-height: 0px
opacity: 0
overflow-y: hidden
animation-background-color()
transition: background 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,72 +0,0 @@
define(
[
'react',
'react-dom',
'mailpoet'
],
function(
React,
ReactDOM,
MailPoet
) {
var CronControl = React.createClass({
getInitialState: function() {
return {
status: 'loading'
};
},
getCronData: function() {
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'getStatus'
}).done((response) => {
this.setState({
status: response.data.status
});
}).fail((response) => {
this.setState({
status: false
});
});
},
componentDidMount: function() {
if(this.isMounted()) {
this.getCronData();
setInterval(this.getCronData, 5000);
}
},
render: function() {
let status;
switch(this.state.status) {
case false:
case 'stopping':
case 'stopped':
status = MailPoet.I18n.t('cronDaemonIsNotRunning');
break;
case 'starting':
case 'started':
status = MailPoet.I18n.t('cronDaemonIsRunning');
break;
case 'loading':
status = MailPoet.I18n.t('loadingDaemonStatus');
break;
}
return (
<div>
{ status }
</div>
);
}
});
const container = document.getElementById('cron_container');
if(container) {
ReactDOM.render(
<CronControl />,
container
);
}
});

View File

@ -127,37 +127,12 @@ define([
return;
}
const dateType = this.props.field.params.date_type;
const dateParts = value.split('-');
let year = '';
let month = '';
let day = '';
switch(dateType) {
case 'year_month_day':
year = ~~(dateParts[0]);
month = ~~(dateParts[1]);
day = ~~(dateParts[2]);
break;
case 'year_month':
year = ~~(dateParts[0]);
month = ~~(dateParts[1]);
break;
case 'month':
month = ~~(dateParts[0]);
break;
case 'year':
year = ~~(dateParts[0]);
break;
}
const dateTime = Moment(value);
this.setState({
year: year,
month: month,
day: day
year: dateTime.format('YYYY'),
month: dateTime.format('M'),
day: dateTime.format('D')
});
}
formatValue() {
@ -228,7 +203,7 @@ define([
const fields = dateSelects.map(type => {
switch(type) {
case 'yyyy':
case 'YYYY':
return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) }
ref={ 'year' }
@ -239,7 +214,7 @@ define([
/>);
break;
case 'mm':
case 'MM':
return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) }
ref={ 'month' }
@ -251,7 +226,7 @@ define([
/>);
break;
case 'dd':
case 'DD':
return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) }
ref={ 'day' }

View File

@ -66,22 +66,22 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'get',
data: id
}).done(function(response) {
if(response === false) {
this.setState({
loading: false,
item: {}
}, function() {
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
loading: false,
item: response
});
data: {
id: id
}
}.bind(this));
}).done((response) => {
this.setState({
loading: false,
item: response.data
});
}).fail((response) => {
this.setState({
loading: false,
item: {}
}, function() {
this.context.router.push('/new');
});
});
},
handleSubmit: function(e) {
e.preventDefault();
@ -115,29 +115,25 @@ define(
endpoint: this.props.endpoint,
action: 'save',
data: item
}).done(function(response) {
}).always(() => {
this.setState({ loading: false });
if(response.result === true) {
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
this.context.router.push('/');
}
if(this.props.params.id !== undefined) {
this.props.messages.onUpdate();
} else {
this.props.messages.onCreate();
}
}).done((response) => {
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
if(response.result === false) {
if(response.errors.length > 0) {
this.setState({ errors: response.errors });
}
}
this.context.router.push('/');
}
}.bind(this));
if(this.props.params.id !== undefined) {
this.props.messages.onUpdate();
} else {
this.props.messages.onCreate();
}
}).fail((response) => {
if(response.errors.length > 0) {
this.setState({ errors: response.errors });
}
});
},
handleValueChange: function(e) {
if (this.props.onChange) {
@ -159,7 +155,7 @@ define(
var errors = this.getErrors().map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
{ error.message }
</p>
);
});

View File

@ -27,48 +27,48 @@ const columns = [
];
const messages = {
onTrash: function(response) {
var count = ~~response;
var message = null;
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneFormTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsTrashed')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
var count = ~~response;
var message = null;
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneFormDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsDeleted')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
var count = ~~response;
var message = null;
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneFormRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsRestored')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
@ -99,14 +99,21 @@ const item_actions = [
return MailPoet.Ajax.post({
endpoint: 'forms',
action: 'duplicate',
data: item.id
}).done(function(response) {
if (response !== false && response['name'] !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
data: {
id: item.id
}
}).done((response) => {
MailPoet.Notice.success(
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.data.name)
);
refresh();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
refresh();
});
}
},
@ -120,9 +127,14 @@ const FormList = React.createClass({
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response.result && response.form_id) {
window.location = mailpoet_form_edit_url + response.form_id;
}).done((response) => {
window.location = mailpoet_form_edit_url + response.data.id;
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
},

View File

@ -53,7 +53,10 @@ function(
}
if(data.action) {
this.props.onBulkAction(selected_ids, data).then(onSuccess);
const promise = this.props.onBulkAction(selected_ids, data);
if (promise !== false) {
promise.then(onSuccess);
};
}
this.setState({

View File

@ -456,22 +456,29 @@ const Listing = React.createClass({
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
}).always(() => {
this.setState({ loading: false });
}).done((response) => {
this.setState({
items: response.items || [],
filters: response.filters || {},
groups: response.groups || [],
count: response.count || 0,
loading: false
}, function() {
if (this.props['onGetItems'] !== undefined) {
const count = (response.groups[0] !== undefined)
? ~~(response.groups[0].count)
: 0;
this.props.onGetItems(count);
items: response.data || [],
filters: response.meta.filters || {},
groups: response.meta.groups || [],
count: response.meta.count || 0
}, () => {
// if viewing an empty trash
if (this.state.group === 'trash' && response.meta.count === 0) {
// redirect to default group
this.handleGroup('all');
}
}.bind(this));
}.bind(this));
});
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
}
},
handleRestoreItem: function(id) {
@ -483,8 +490,10 @@ const Listing = React.createClass({
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'restore',
data: id
}).done(function(response) {
data: {
id: id
}
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onRestore'] !== undefined
@ -492,7 +501,12 @@ const Listing = React.createClass({
this.props.messages.onRestore(response);
}
this.getItems();
}.bind(this));
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
});
},
handleTrashItem: function(id) {
this.setState({
@ -503,8 +517,10 @@ const Listing = React.createClass({
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'trash',
data: id
}).done(function(response) {
data: {
id: id
}
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
@ -512,7 +528,12 @@ const Listing = React.createClass({
this.props.messages.onTrash(response);
}
this.getItems();
}.bind(this));
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
});
},
handleDeleteItem: function(id) {
this.setState({
@ -523,8 +544,10 @@ const Listing = React.createClass({
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'delete',
data: id
}).done(function(response) {
data: {
id: id
}
}).done((response) => {
if (
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
@ -532,22 +555,29 @@ const Listing = React.createClass({
this.props.messages.onDelete(response);
}
this.getItems();
}.bind(this));
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
});
},
handleEmptyTrash: function() {
return this.handleBulkAction('all', {
action: 'delete',
group: 'trash'
}).then(function(response) {
if (~~(response) > 0) {
MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
);
}
}).done((response) => {
MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response.meta.count)
);
// redirect to default group
this.handleGroup('all');
}.bind(this));
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
});
},
handleBulkAction: function(selected_ids, params) {
if (
@ -555,7 +585,7 @@ const Listing = React.createClass({
&& this.state.selected_ids.length === 0
&& selected_ids !== 'all'
) {
return;
return false;
}
this.setState({ loading: true });

View File

@ -0,0 +1,31 @@
/**
* Show Settings Behavior
*
* Opens up settings of a BlockView if contents are clicked upon
*/
define([
'backbone.marionette',
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup',
], function(Marionette, jQuery, BehaviorsLookup) {
BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({
defaults: {
ignoreFrom: '', // selector
},
events: {
'click .mailpoet_content': 'showSettings',
},
showSettings: function(event) {
if(!this.isIgnoredElement(event.target)) {
this.view.triggerMethod('showSettings');
}
},
isIgnoredElement: function(element) {
return this.options.ignoreFrom
&& this.options.ignoreFrom.length > 0
&& jQuery(element).is(this.options.ignoreFrom);
},
});
});

View File

@ -35,7 +35,12 @@ define([
Module.ALCSupervisor = SuperModel.extend({
initialize: function() {
this.listenTo(App.getChannel(), 'automatedLatestContentRefresh', this.refresh);
var DELAY_REFRESH_FOR_MS = 500;
this.listenTo(
App.getChannel(),
'automatedLatestContentRefresh',
_.debounce(this.refresh, DELAY_REFRESH_FOR_MS)
);
},
refresh: function() {
var models = App.findModels(function(model) {
@ -107,9 +112,7 @@ define([
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
this.on('add remove update reset', function(model, collection, options) {
App.getChannel().trigger('automatedLatestContentRefresh');
});
this.on('add remove update reset', this._scheduleFetchPosts);
this.on('refreshPosts', this.updatePosts, this);
},
updatePosts: function(posts) {
@ -120,16 +123,7 @@ define([
* ALC posts on each model change
*/
_scheduleFetchPosts: function() {
var TIMEOUT = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
}
this._fetchPostsTimer = setTimeout(function() {
//that.fetchPosts();
App.getChannel().trigger('automatedLatestContentRefresh');
that._fetchPostsTimer = undefined;
}, TIMEOUT);
App.getChannel().trigger('automatedLatestContentRefresh');
},
});
@ -180,17 +174,17 @@ define([
"change .mailpoet_automated_latest_content_title_format": 'changeTitleFormat',
"change .mailpoet_automated_latest_content_title_as_links": _.partial(this.changeBoolField, 'titleIsLink'),
"change .mailpoet_automated_latest_content_show_divider": _.partial(this.changeBoolField, 'showDivider'),
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"input .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"input .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
"keyup .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"keyup .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"),
"input .mailpoet_automated_latest_content_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_automated_latest_content_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_automated_latest_content_sort_by": _.partial(this.changeField, "sortBy"),
"click .mailpoet_done_editing": "close",
};

View File

@ -99,12 +99,12 @@ define([
},
showTools: function(_event) {
if (!this.showingToolsDisabled) {
this.$('> .mailpoet_tools').show();
this.$('> .mailpoet_tools').addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools');
}
},
hideTools: function(e) {
this.$('> .mailpoet_tools').hide();
this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools');
},
enableShowingTools: function() {

View File

@ -44,6 +44,9 @@ define([
className: "mailpoet_block mailpoet_button_block mailpoet_droppable_block",
getTemplate: function() { return templates.buttonBlock; },
onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {},
}),
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
@ -65,8 +68,8 @@ define([
getTemplate: function() { return templates.buttonBlockSettings; },
events: function() {
return {
"keyup .mailpoet_field_button_text": _.partial(this.changeField, "text"),
"keyup .mailpoet_field_button_url": _.partial(this.changeField, "url"),
"input .mailpoet_field_button_text": _.partial(this.changeField, "text"),
"input .mailpoet_field_button_url": _.partial(this.changeField, "url"),
"change .mailpoet_field_button_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"change .mailpoet_field_button_font_color": _.partial(this.changeColorField, "styles.block.fontColor"),
"change .mailpoet_field_button_font_family": _.partial(this.changeField, "styles.block.fontFamily"),
@ -77,23 +80,19 @@ define([
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"keyup .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_button_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_button_border_radius": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"change .mailpoet_field_button_border_radius": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius_input', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"change .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"keyup .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"input .mailpoet_field_button_border_radius_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_radius', _.partial(this.changePixelField, "styles.block.borderRadius").bind(this)),
"input .mailpoet_field_button_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"change .mailpoet_field_button_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width_input', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"change .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"keyup .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"input .mailpoet_field_button_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_width', _.partial(this.changePixelField, "styles.block.width").bind(this)),
"input .mailpoet_field_button_line_height": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"change .mailpoet_field_button_line_height": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height_input', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"change .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"keyup .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"input .mailpoet_field_button_line_height_input": _.partial(this.updateValueAndCall, '.mailpoet_field_button_line_height', _.partial(this.changePixelField, "styles.block.lineHeight").bind(this)),
"click .mailpoet_field_button_replace_all_styles": "applyToAll",
"click .mailpoet_done_editing": "close",

View File

@ -186,13 +186,13 @@ define([
},
showTools: function() {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).show();
this.$(this.ui.tools).addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools');
}
},
hideTools: function() {
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
this.$(this.ui.tools).hide();
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools');
}
},

View File

@ -43,6 +43,9 @@ define([
minLength: 0, // TODO: Move this number to editor configuration
modelField: 'styles.block.padding',
},
ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle'
},
}, base.BlockView.prototype.behaviors),
onDragSubstituteBy: function() { return Module.DividerWidgetView; },
initialize: function() {
@ -88,8 +91,7 @@ define([
"input .mailpoet_field_divider_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"keyup .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"input .mailpoet_field_divider_border_width_input": _.partial(this.updateValueAndCall, '.mailpoet_field_divider_border_width', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_divider_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
"change .mailpoet_field_divider_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),

View File

@ -41,6 +41,9 @@ define([
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
}, base.BlockView.prototype.templateHelpers.apply(this));
},
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
ShowSettingsBehavior: {},
}),
onRender: function() {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView);
@ -61,9 +64,9 @@ define([
getTemplate: function() { return templates.imageBlockSettings; },
events: function() {
return {
"keyup .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"keyup .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"keyup .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"input .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"input .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"input .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager",

View File

@ -273,7 +273,7 @@ define([
return {
'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
'change .mailpoet_posts_post_status': _.partial(this.changeField, 'postStatus'),
'keyup .mailpoet_posts_search_term': _.partial(this.changeField, 'search'),
'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search'),
};
},
constructor: function() {
@ -414,17 +414,17 @@ define([
"change .mailpoet_posts_title_format": 'changeTitleFormat',
"change .mailpoet_posts_title_as_links": _.partial(this.changeBoolField, 'titleIsLink'),
"change .mailpoet_posts_show_divider": _.partial(this.changeBoolField, 'showDivider'),
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"input .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"input .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
"keyup .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"keyup .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"),
"input .mailpoet_posts_categories": _.partial(this.changeField, "categoriesPrecededBy"),
"input .mailpoet_posts_read_more_text": _.partial(this.changeField, "readMoreText"),
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"),
};
},

View File

@ -141,6 +141,7 @@ define([
},
},
HighlightEditingBehavior: {},
ShowSettingsBehavior: {},
},
onDragSubstituteBy: function() { return Module.SocialWidgetView; },
constructor: function() {
@ -149,6 +150,7 @@ define([
Marionette.CompositeView.apply(this, arguments);
},
initialize: function() {
this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
@ -169,13 +171,16 @@ define([
this.regionManager.destroy();
},
showTools: function(_event) {
this.$(this.ui.tools).show();
this.$(this.ui.tools).addClass('mailpoet_display_tools');
_event.stopPropagation();
},
hideTools: function(_event) {
this.$(this.ui.tools).hide();
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
_event.stopPropagation();
},
showSettings: function(options) {
this.toolsView.triggerMethod('showSettings', options);
},
getDropFunc: function() {
return function() {
return this.model.clone();
@ -275,9 +280,9 @@ define([
return {
"click .mailpoet_delete_block": "deleteIcon",
"change .mailpoet_social_icon_field_type": _.partial(this.changeField, "iconType"),
"keyup .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"),
"keyup .mailpoet_social_icon_field_link": this.changeLink,
"keyup .mailpoet_social_icon_field_text": _.partial(this.changeField, "text"),
"input .mailpoet_social_icon_field_image": _.partial(this.changeField, "image"),
"input .mailpoet_social_icon_field_link": this.changeLink,
"input .mailpoet_social_icon_field_text": _.partial(this.changeField, "text"),
};
},
modelEvents: {

View File

@ -36,6 +36,9 @@ define([
minLength: 20, // TODO: Move this number to editor configuration
modelField: 'styles.block.height',
},
ShowSettingsBehavior: {
ignoreFrom: '.mailpoet_resize_handle'
},
}, base.BlockView.prototype.behaviors),
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'),
onDragSubstituteBy: function() { return Module.SpacerWidgetView; },

View File

@ -11,7 +11,7 @@ define([
return MailPoet.Ajax.post({
endpoint: 'automatedLatestContent',
action: args.action,
data: args.options || {},
data: args.options || {}
});
};
Module._cachedQuery = _.memoize(Module._query, JSON.stringify);
@ -19,14 +19,14 @@ define([
Module.getNewsletter = function(options) {
return Module._query({
action: 'get',
options: options,
options: options
});
};
Module.getPostTypes = function() {
return Module._cachedQuery({
action: 'getPostTypes',
options: {},
options: {}
}).then(function(response) {
return _.values(response.data);
});
@ -83,7 +83,7 @@ define([
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: options || {},
data: options || {}
});
};
@ -91,7 +91,7 @@ define([
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'sendPreview',
data: options || {},
data: options || {}
});
};

View File

@ -232,6 +232,12 @@ define([
'click .mailpoet_show_preview': 'showPreview',
'click #mailpoet_send_preview': 'sendPreview',
},
onBeforeDestroy: function() {
if (this.previewView) {
this.previewView.destroy();
this.previewView = null;
}
},
showPreview: function() {
var json = App.toJSON();
@ -246,18 +252,31 @@ define([
endpoint: 'newsletters',
action: 'showPreview',
data: json,
}).done(function(response){
}).always(function() {
MailPoet.Modal.loading(false);
}).done(function(response) {
this.previewView = new Module.NewsletterPreviewView({
previewUrl: response.meta.preview_url
});
if (response.result === true) {
window.open(response.data.url, '_blank')
var view = this.previewView.render();
MailPoet.Modal.popup({
template: '',
element: this.previewView.$el,
title: MailPoet.I18n.t('newsletterPreview'),
onCancel: function() {
this.previewView.destroy();
this.previewView = null;
}.bind(this)
});
}.bind(this)).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
});
},
sendPreview: function() {
@ -282,32 +301,39 @@ define([
// send test email
MailPoet.Modal.loading(true);
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
} else {
if (_.isArray(response.errors)) {
response.errors.map(function(error) {
MailPoet.Notice.error(error, { scroll: true });
});
} else {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,
}
);
}
}
CommunicationComponent.previewNewsletter(data).always(function() {
MailPoet.Modal.loading(false);
}).done(function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterPreviewSent'),
{ scroll: true });
}).fail(function(response) {
// an error occurred
MailPoet.Modal.loading(false);
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true, static: true }
);
}
});
},
});
Module.NewsletterPreviewView = Marionette.ItemView.extend({
getTemplate: function() { return templates.newsletterPreview; },
initialize: function(options) {
this.previewUrl = options.previewUrl;
this.width = App.getConfig().get('newsletterPreview.width');
this.height = App.getConfig().get('newsletterPreview.height')
},
templateHelpers: function() {
return {
previewUrl: this.previewUrl,
width: this.width,
height: this.height,
};
}
});
App.on('before:start', function(options) {
App.registerWidget = Module.registerWidget;
App.getWidgets = Module.getWidgets;

View File

@ -17,8 +17,8 @@ import {
} from 'newsletters/scheduling/common.jsx'
const messages = {
onTrash(response) {
const count = ~~response;
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -28,12 +28,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -43,12 +43,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -58,7 +58,7 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
@ -127,16 +127,23 @@ const newsletter_actions = [
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
data: {
id: newsletter.id
}
}).done((response) => {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.data.subject
)
);
refresh();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
refresh();
});
}
},

View File

@ -12,8 +12,8 @@ import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = {
onTrash(response) {
const count = ~~response;
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -23,12 +23,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -38,12 +38,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -53,7 +53,7 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
@ -122,16 +122,23 @@ const newsletter_actions = [
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
data: {
id: newsletter.id
}
}).done((response) => {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.data.subject
)
);
refresh();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
refresh();
});
}
},

View File

@ -15,8 +15,8 @@ const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = {
onTrash(response) {
const count = ~~response;
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -26,12 +26,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete(response) {
const count = ~~response;
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -41,12 +41,12 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore(response) {
const count = ~~response;
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if (count === 1) {
@ -56,7 +56,7 @@ const messages = {
} else {
message = (
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
@ -125,16 +125,23 @@ const newsletter_actions = [
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'duplicate',
data: newsletter.id
}).done(function(response) {
if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.subject
)
data: {
id: newsletter.id
}
}).done((response) => {
MailPoet.Notice.success(
(MailPoet.I18n.t('newsletterDuplicated')).replace(
'%$1s', response.data.subject
)
);
refresh();
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
refresh();
});
}
},

View File

@ -66,22 +66,22 @@ define(
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'get',
data: id
}).done((response) => {
if(response === false) {
this.setState({
loading: false,
item: {},
}, function() {
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
loading: false,
item: response,
fields: this.getFieldsByNewsletter(response),
});
data: {
id: id
}
}).done((response) => {
this.setState({
loading: false,
item: response.data,
fields: this.getFieldsByNewsletter(response.data)
});
}).fail((response) => {
this.setState({
loading: false,
item: {}
}, () => {
this.context.router.push('/new');
});
});
},
handleSend: function(e) {

View File

@ -28,17 +28,16 @@ define(
type: type,
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.context.router.push(`/template/${response.newsletter.id}`);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}).done((response) => {
this.context.router.push(`/template/${response.data.id}`);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
});
},
render: function() {
return (

View File

@ -50,17 +50,16 @@ define(
type: 'notification',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}).done((response) => {
this.showTemplateSelection(response.data.id);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
});
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${newsletterId}`);

View File

@ -25,19 +25,18 @@ define(
endpoint: 'newsletters',
action: 'create',
data: {
type: 'standard',
type: 'standard'
}
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}).done((response) => {
this.showTemplateSelection(response.data.id);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
});
},
render: function() {
return (

View File

@ -113,17 +113,16 @@ const WelcomeScheduling = React.createClass({
type: 'welcome',
options: this.state
}
}).done(function(response) {
if (response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
}).done((response) => {
this.showTemplateSelection(response.data.id);
}).fail((response) => {
if (response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}
}.bind(this));
});
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${ newsletterId }`);

View File

@ -57,18 +57,17 @@ define(
data: _.extend({}, this.state, {
type: 'welcome',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
})
}).done((response) => {
this.showTemplateSelection(response.data.id);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
}.bind(this));
});
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${newsletterId}`);

View File

@ -36,48 +36,48 @@ var columns = [
];
const messages = {
onTrash: function(response) {
var count = ~~response;
var message = null;
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneSegmentTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsTrashed')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
var count = ~~response;
var message = null;
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneSegmentDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsDeleted')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
var count = ~~response;
var message = null;
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if(count === 1) {
if (count === 1) {
message = (
MailPoet.I18n.t('oneSegmentRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsRestored')
).replace('%$1d', count);
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
@ -106,16 +106,23 @@ const item_actions = [
{
name: 'duplicate_segment',
label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) {
onClick: (item, refresh) => {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function(response) {
data: {
id: item.id
}
}).done((response) => {
MailPoet.Notice.success(
(MailPoet.I18n.t('listDuplicated')).replace('%$1s', response.name)
MailPoet.I18n.t('listDuplicated').replace('%$1s', response.data.name)
);
refresh();
}).fail((response) => {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
});
},
display: function(segment) {

View File

@ -133,8 +133,7 @@ define(
return;
}
MailPoet.Modal.loading(true);
MailPoet.Ajax
.post({
MailPoet.Ajax.post({
endpoint: 'ImportExport',
action: 'processExport',
data: JSON.stringify({
@ -144,25 +143,22 @@ define(
'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
'subscriber_fields': subscriberFieldsContainerElement.val()
})
})
.done(function (response) {
}).always(function(response) {
MailPoet.Modal.loading(false);
if (response.result === false) {
MailPoet.Notice.error(response.errors);
} else {
resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + parseInt(response.data.totalExported).toLocaleString() + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
jQuery('#export_result_notice').html('<p>' + resultMessage + '</p>').show();
window.location.href = response.data.exportFileURL;
}).done(function(response) {
resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + parseInt(response.data.totalExported).toLocaleString() + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
jQuery('#export_result_notice').html('<p>' + resultMessage + '</p>').show();
window.location.href = response.data.exportFileURL;
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
})
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
});
});

View File

@ -51,6 +51,12 @@ define(
* STEP 1 (upload or copy/paste)
*/
router.on('route:step1', function () {
// set or reset temporary validation rule on all columns
mailpoetColumns = jQuery.map(mailpoetColumns, function (column, columnIndex) {
column.validation_rule = false;
return column;
});
if (typeof (importData.step1) !== 'undefined') {
showCurrentStep();
return;
@ -185,68 +191,59 @@ define(
mailChimpKeyVerifyButtonElement.click(function () {
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'ImportExport',
endpoint: 'importExport',
action: 'getMailChimpLists',
data: {api_key: mailChimpKeyInputElement.val()}
}).done(function (response) {
if (response.result === false) {
MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors);
jQuery('.mailpoet_mailchimp-key-status')
.removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-error');
data: {
api_key: mailChimpKeyInputElement.val()
}
}).always(function() {
MailPoet.Modal.loading(false);
}).done(function(response) {
jQuery('.mailpoet_mailchimp-key-status')
.html('')
.removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok');
if (response.data.length === 0) {
jQuery('.mailpoet_mailchimp-key-status').html(MailPoet.I18n.t('noMailChimpLists'));
mailChimpListsContainerElement.hide();
toggleNextStepButton(mailChimpProcessButtonElement, 'off');
} else {
jQuery('.mailpoet_mailchimp-key-status')
.html('')
.removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok');
if (!response.data) {
jQuery('.mailpoet_mailchimp-key-status').html(MailPoet.I18n.t('noMailChimpLists'));
mailChimpListsContainerElement.hide();
toggleNextStepButton(mailChimpProcessButtonElement, 'off');
} else {
displayMailChimpLists(response.data);
}
displayMailChimpLists(response.data);
}
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
MailPoet.Modal.loading(false);
}).error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
MailPoet.Modal.loading(false);
});
mailChimpProcessButtonElement.click(function () {
if (mailChimpProcessButtonElement.closest('table a').hasClass('disabled')) {
if (mailChimpProcessButtonElement.closest('table a').hasClass('button-disabled')) {
return;
}
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'ImportExport',
endpoint: 'importExport',
action: 'getMailChimpSubscribers',
data: {
api_key: mailChimpKeyInputElement.val(),
lists: mailChimpListsContainerElement.find('select').val()
}
}).done(function (response) {
if (response.result === true) {
importData.step1 = response.data;
router.navigate('step2', {trigger: true});
}
else {
MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors);
}
}).always(function(response) {
MailPoet.Modal.loading(false);
}).error(function () {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + result.statusText.toLowerCase() + '.'
);
}).done(function(response) {
importData.step1 = response.data;
router.navigate('step2', {trigger: true});
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
});
@ -455,7 +452,7 @@ define(
null,
new Array(subscribers.subscribers[0].length)
).map(String.prototype.valueOf, filler),
fillterPosition;
fillerPosition;
showCurrentStep();
@ -565,79 +562,54 @@ define(
})
}
jQuery('.mailpoet_create_segment').click(function () {
jQuery('.mailpoet_create_segment').click(function() {
MailPoet.Modal.popup({
title: MailPoet.I18n.t('addNewList'),
template: jQuery('#new_segment_template').html()
})
jQuery('#new_segment_name').keypress(function (e) {
jQuery('#new_segment_name').keypress(function(e) {
if (e.which == 13) {
jQuery('#new_segment_process').click();
}
});
jQuery('#new_segment_process').click(function () {
var segmentName = jQuery('#new_segment_name').val().trim(),
segmentDescription = jQuery('#new_segment_description').val().trim(),
isDuplicateListName = ( jQuery.map(mailpoetSegments, function (el) {
if (el.name.toLowerCase() === segmentName.toLowerCase()) {
return true;
}
}).length && segmentName) ? true : false;
if (segmentName === '') {
jQuery('.mailpoet_validation_error[data-error="segment_name_required"]:hidden').show();
} else {
jQuery('.mailpoet_validation_error[data-error="segment_name_required"]:visible').hide();
}
if (isDuplicateListName) {
jQuery('.mailpoet_validation_error[data-error="segment_name_not_unique"]:hidden').show();
} else {
jQuery('.mailpoet_validation_error[data-error="segment_name_not_unique"]:visible').hide();
}
if (segmentName && !isDuplicateListName) {
jQuery('.mailpoet_validation_error[data-error="segment_name_required"]:visible').hide();
MailPoet.Ajax
.post({
endpoint: 'ImportExport',
action: 'addSegment',
data: {
name: segmentName,
description: segmentDescription
}
})
.done(function (response) {
if (response.result === true) {
mailpoetSegments.push({
'id': response.segment.id,
'name': response.segment.name
});
var segmentName = jQuery('#new_segment_name').val().trim();
var segmentDescription = jQuery('#new_segment_description').val().trim();
var selected_values = segmentSelectElement.val();
if (selected_values === null) {
selected_values = [response.segment.id]
} else {
selected_values.push(response.segment.id);
}
MailPoet.Ajax.post({
endpoint: 'ImportExport',
action: 'addSegment',
data: {
name: segmentName,
description: segmentDescription
}
}).done(function(response) {
mailpoetSegments.push({
'id': response.data.id,
'name': response.data.name
});
enableSegmentSelection(mailpoetSegments);
segmentSelectElement.val(selected_values).trigger('change');
jQuery('.mailpoet_segments:hidden').show();
jQuery('.mailpoet_no_segments:visible').hide();
MailPoet.Modal.close();
}
else {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoet.I18n.t('segmentCreateError') + response.message + '.'
);
}
})
.error(function (error) {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
}
var selected_values = segmentSelectElement.val();
if (selected_values === null) {
selected_values = [response.data.id]
} else {
selected_values.push(response.data.id);
}
enableSegmentSelection(mailpoetSegments);
segmentSelectElement.val(selected_values).trigger('change');
jQuery('.mailpoet_segments:hidden').show();
jQuery('.mailpoet_no_segments:visible').hide();
MailPoet.Modal.close();
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.hide();
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ positionAfter: '#new_segment_name' }
);
}
});
});
jQuery('#new_segment_cancel').click(function () {
MailPoet.Modal.close();
@ -707,7 +679,7 @@ define(
// display filler data (e.g., ellipsis) if we've reached the maximum number of rows and
// subscribers count is greater than the maximum number of rows we're displaying
if (index === maxRowsToShow && subscribers.subscribersCount > (maxRowsToShow + 1)) {
fillterPosition = index;
fillerPosition = index;
return filler;
}
// if we're on the last line, show the total count of subscribers data
@ -745,114 +717,70 @@ define(
.on('select2:selecting', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.args.data.id;
// CREATE CUSTOM FIELD
if (selectedOptionId === 'create') {
selectEvent.preventDefault();
jQuery(selectElement).select2('close');
MailPoet.Modal.popup({
title: MailPoet.I18n.t('addNewColumn'),
template: jQuery('#new_column_template').html()
title: MailPoet.I18n.t('addNewField'),
template: jQuery('#form_template_field_form').html()
});
jQuery('#new_column_name').keypress(function (e) {
if (e.which == 13) {
jQuery('#new_column_process').click();
}
});
jQuery('#new_column_process').click(function () {
var name = jQuery('#new_column_name').val().trim(),
type = jQuery('#new_column_type').val().trim(),
columnNames = mailpoetColumns.map(function (el) {
return el.name.toLowerCase();
jQuery('#form_field_new').parsley().on('form:submit', function(parsley) {
// get data
var data = jQuery(this.$element).serializeObject();
// save custom field
MailPoet.Ajax.post({
endpoint: 'customFields',
action: 'save',
data: data
}).done(function(response) {
var new_column_data = {
'id': response.data.id,
'name': response.data.name,
'type': response.data.type,
'params': response.data.params,
'custom': true
};
// if this is the first custom column, create an "optgroup"
if (mailpoetColumnsSelect2.length === 2) {
mailpoetColumnsSelect2.push({
'name': MailPoet.I18n.t('userColumns'),
'children': []
});
isDuplicateColumnName =
(name && columnNames.indexOf(name.toLowerCase()) > -1)
? true
: false;
if (name === '') {
jQuery('.mailpoet_validation_error[data-error="name_required"]')
.show();
} else {
jQuery('.mailpoet_validation_error[data-error="name_required"]')
.hide();
}
if (type === '') {
jQuery('.mailpoet_validation_error[data-error="type_required"]')
.show();
} else {
jQuery('.mailpoet_validation_error[data-error="type_required"]')
.hide();
}
if (isDuplicateColumnName) {
jQuery('.mailpoet_validation_error[data-error="name_not_unique"]')
.show();
} else {
jQuery('.mailpoet_validation_error[data-error="name_not_unique"]')
.hide();
}
// create new field
if (name && type && !isDuplicateColumnName) {
MailPoet.Modal
.close()
.loading(true);
MailPoet.Ajax
.post({
endpoint: 'ImportExport',
action: 'addCustomField',
data: {
name: name,
type: type
}
})
.done(function (response) {
if (response.result === true) {
var new_column_data = {
'id': response.customField.id,
'name': name,
'type': type,
'custom': true,
};
// if this is the first custom column, create an "optgroup"
if (mailpoetColumnsSelect2.length === 2) {
mailpoetColumnsSelect2.push({
'name': MailPoet.I18n.t('userColumns'),
'children': []
});
}
mailpoetColumnsSelect2[2].children.push(new_column_data);
mailpoetColumns.push(new_column_data);
jQuery('select.mailpoet_subscribers_column_data_match')
.each(function () {
jQuery(this)
.html('')
.select2('destroy')
.select2({
data: mailpoetColumnsSelect2,
width: '15em',
templateResult: function (item) {
return item.name;
},
templateSelection: function (item) {
return item.name;
}
mailpoetColumnsSelect2[2].children.push(new_column_data);
mailpoetColumns.push(new_column_data);
jQuery('select.mailpoet_subscribers_column_data_match')
.each(function () {
jQuery(this)
.html('')
.select2('destroy')
.select2({
data: mailpoetColumnsSelect2,
width: '15em',
templateResult: function (item) {
return item.name;
},
templateSelection: function (item) {
return item.name;
}
})
});
jQuery(selectElement).data('column-id', new_column_data.id);
filterSubscribers();
}
else {
MailPoet.Notice.error(MailPoet.I18n.t('customFieldCreateError'));
}
MailPoet.Modal.loading(false);
})
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
}
});
jQuery('#new_column_cancel').click(function () {
MailPoet.Modal.close();
})
});
jQuery(selectElement).data('column-id', new_column_data.id);
jQuery(selectElement).data('validation-rule', false);
filterSubscribers();
// close popup
MailPoet.Modal.close();
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ positionAfter: '#field_name' }
);
}
});
return false;
});
}
// CHANGE COLUMN
@ -890,22 +818,22 @@ define(
.remove();
var subscribersClone = jQuery.extend(true, {}, subscribers),
preventNextStep = false,
displayedColumnsIds = jQuery.map(
jQuery('.mailpoet_subscribers_column_data_match'), function (data) {
var columnId = jQuery(data).data('column-id');
jQuery(data).val(columnId).trigger('change');
return columnId;
displayedColumns = jQuery.map(
jQuery('.mailpoet_subscribers_column_data_match'), function (element, elementIndex) {
var columnId = jQuery(element).data('column-id');
var validationRule = jQuery(element).data('validation-rule');
jQuery(element).val(columnId).trigger('change');
return { id: columnId, index: elementIndex, validationRule: validationRule, element: element };
});
// iterate through the object of mailpoet columns
jQuery.map(mailpoetColumns, function (column) {
jQuery.map(mailpoetColumns, function (column, columnIndex) {
// check if the column id matches the selected id of one of the
// subscriber's data columns
var matchedColumn = jQuery.inArray(column.id, displayedColumnsIds);
// EMAIL filter: if the last value in the column doesn't have a valid
var matchedColumn = _.find(displayedColumns, function(data) { return data.id === column.id; });
// EMAIL filter: if the first value in the column doesn't have a valid
// email, hide the next button
if (column.id === "email") {
if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn])) {
if (column.id === 'email') {
if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn.index])) {
preventNextStep = true;
if (!jQuery('[data-id="notice_invalidEmail"]').length) {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidElement'), {
@ -921,35 +849,63 @@ define(
}
}
// DATE filter: if column type is date, check if we can recognize it
if (column.type === 'date' && matchedColumn !== -1) {
jQuery.map(subscribersClone.subscribers, function (data, position) {
var rowData = data[matchedColumn];
if (position !== fillterPosition) {
// check if date exists
if (rowData.trim() === '') {
data[matchedColumn] =
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('emptyDate')
+ '</span>';
preventNextStep = true;
return;
}
// check if date is valid and is before today
if (Moment(rowData).isValid() && Moment(rowData).isBefore(Moment())) {
data[matchedColumn] +=
'<span class="mailpoet_data_match" title="'
+ MailPoet.I18n.t('verifyDateMatch') + '">'
+ MailPoet.Date.format(rowData) + '</span>';
}
else {
data[matchedColumn] +=
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('dateMatchError') + '</span>';
preventNextStep = true;
if (column.type === 'date' && matchedColumn) {
var allowedDateFormats = [
Moment.ISO_8601,
'YYYY/MM/DD',
'MM/DD/YYYY',
'DD/MM/YYYY',
'YYYY/MM/DD',
'YYYY/DD/MM',
'MM/YYYY',
'YYYY/MM',
'YYYY'
];
var firstRowData = subscribersClone.subscribers[0][matchedColumn.index];
var validationRule = false;
// check if date exists
if (firstRowData.trim() === '') {
subscribersClone.subscribers[0][matchedColumn.index] =
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('emptyFirstRowDate')
+ '</span>';
preventNextStep = true;
}
else {
for (var format in allowedDateFormats) {
var testedFormat = allowedDateFormats[format]
if (Moment(firstRowData, testedFormat, true).isValid()) {
var validationRule = (typeof(testedFormat) === 'function') ?
'datetime' :
testedFormat
// set validation on the column element
jQuery(matchedColumn.element).data('validation-rule', validationRule);
break;
}
if (validationRule === 'datetime') validationRule = Moment.ISO_8601;
}
}
jQuery.map(subscribersClone.subscribers, function (data, index) {
var rowData = data[matchedColumn.index];
if (index === fillerPosition || rowData.trim() === '') return;
var date = Moment(rowData, testedFormat, true);
// validate date
if (date.isValid()) {
data[matchedColumn.index] +=
'<span class="mailpoet_data_match" title="'
+ MailPoet.I18n.t('verifyDateMatch') + '">'
+ MailPoet.Date.format(date)
+ '</span>';
}
else {
data[matchedColumn.index] +=
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('dateMatchError')
+ '</span>';
preventNextStep = true;
};
});
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidDate'), {
@ -983,11 +939,11 @@ define(
nextStepButton.addClass(disabled);
}
previousStepButton.off().click(function () {
router.navigate('step1', {trigger: true});
previousStepButton.off().on('click', function () {
router.navigate('step1', { trigger: true });
});
nextStepButton.off().click(function () {
nextStepButton.off().on('click', function () {
if (jQuery(this).hasClass('button-disabled')) {
return;
}
@ -1019,44 +975,41 @@ define(
_.each(jQuery('select.mailpoet_subscribers_column_data_match'),
function (column, columnIndex) {
var columnId = jQuery(column).data('column-id');
var validationRule = jQuery(column).data('validation-rule');
if (columnId === 'ignore') {
return;
}
columns[columnId] = columnIndex;
columns[columnId] = { index: columnIndex, validation_rule: validationRule };
});
_.each(subscribers, function () {
queue.add(function (queue) {
queue.add(function(queue) {
queue.pause();
MailPoet.Ajax
.post({
endpoint: 'ImportExport',
action: 'processImport',
data: JSON.stringify({
columns: columns,
subscribers: subscribers[batchNumber],
timestamp: timestamp,
segments: segmentSelectElement.val(),
updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false
})
MailPoet.Ajax.post({
endpoint: 'ImportExport',
action: 'processImport',
data: JSON.stringify({
columns: columns,
subscribers: subscribers[batchNumber],
timestamp: timestamp,
segments: segmentSelectElement.val(),
updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false
})
.done(function (response) {
if (response.result === false) {
importResults.errors.push(response.errors);
} else {
importResults.created = response.data.created;
importResults.updated = response.data.updated;
importResults.segments = response.data.segments;
importResults.added_to_segment_with_welcome_notification = response.data.added_to_segment_with_welcome_notification;
}
queue.run();
})
.error(function (error) {
importResults.errors.push(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
}).done(function(response) {
importResults.created = response.data.created;
importResults.updated = response.data.updated;
importResults.segments = response.data.segments;
importResults.added_to_segment_with_welcome_notification = response.data.added_to_segment_with_welcome_notification;
queue.run();
}).fail(function(response) {
MailPoet.Modal.loading(false);
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
queue.run();
});
}
});
batchNumber++;
})
});
@ -1146,4 +1099,4 @@ define(
Backbone.history.start();
}
});
});
});

View File

@ -37,59 +37,50 @@ const columns = [
];
const messages = {
onTrash: function(response) {
if (response) {
var message = null;
if (~~response === 1) {
message = (
MailPoet.I18n.t('oneSubscriberTrashed')
);
} else if (~~response > 1) {
message = (
MailPoet.I18n.t('multipleSubscribersTrashed')
).replace('%$1d', (~~response).toLocaleString());
}
onTrash: (response) => {
const count = ~~response.meta.count;
let message = null;
if (message !== null) {
MailPoet.Notice.success(message);
}
if (count === 1) {
message = (
MailPoet.I18n.t('oneSubscriberTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleSubscribersTrashed')
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
if (response) {
var message = null;
if (~~response === 1) {
message = (
MailPoet.I18n.t('oneSubscriberDeleted')
);
} else if (~~response > 1) {
message = (
MailPoet.I18n.t('multipleSubscribersDeleted')
).replace('%$1d', ~~response);
}
onDelete: (response) => {
const count = ~~response.meta.count;
let message = null;
if (message !== null) {
MailPoet.Notice.success(message);
}
if (count === 1) {
message = (
MailPoet.I18n.t('oneSubscriberDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleSubscribersDeleted')
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
if (response) {
var message = null;
if (~~response === 1) {
message = (
MailPoet.I18n.t('oneSubscriberRestored')
);
} else if (~~response > 1) {
message = (
MailPoet.I18n.t('multipleSubscribersRestored')
).replace('%$1d', (~~response).toLocaleString());
}
onRestore: (response) => {
const count = ~~response.meta.count;
let message = null;
if (message !== null) {
MailPoet.Notice.success(message);
}
if (count === 1) {
message = (
MailPoet.I18n.t('oneSubscriberRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleSubscribersRestored')
).replace('%$1d', count.toLocaleString());
}
MailPoet.Notice.success(message);
}
};
@ -120,8 +111,8 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleSubscribersMovedToList')
.replace('%$1d', (~~(response.subscribers)).toLocaleString())
.replace('%$2s', response.segment)
.replace('%$1d', (~~(response.meta.count)).toLocaleString())
.replace('%$2s', response.meta.segment)
);
}
},
@ -151,8 +142,8 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleSubscribersAddedToList')
.replace('%$1d', (~~response.subscribers).toLocaleString())
.replace('%$2s', response.segment)
.replace('%$1d', (~~response.meta.count).toLocaleString())
.replace('%$2s', response.meta.segment)
);
}
},
@ -182,8 +173,8 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleSubscribersRemovedFromList')
.replace('%$1d', (~~response.subscribers).toLocaleString())
.replace('%$2s', response.segment)
.replace('%$1d', (~~response.meta.count).toLocaleString())
.replace('%$2s', response.meta.segment)
);
}
},
@ -193,7 +184,7 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleSubscribersRemovedFromAllLists')
.replace('%$1d', (~~response).toLocaleString())
.replace('%$1d', (~~response.meta.count).toLocaleString())
);
}
},
@ -203,7 +194,7 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleConfirmationEmailsSent')
.replace('%$1d', (~~response).toLocaleString())
.replace('%$1d', (~~response.meta.count).toLocaleString())
);
}
},
@ -330,16 +321,23 @@ const SubscriberList = React.createClass({
</div>
);
},
onGetItems: function(count) {
jQuery('#mailpoet_export_button')[(count > 0) ? 'show' : 'hide']();
},
render: function() {
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
<a className="page-title-action" href="?page=mailpoet-import#step1">{MailPoet.I18n.t('import')}</a>
<a id="mailpoet_export_button" className="page-title-action" href="?page=mailpoet-export">{MailPoet.I18n.t('export')}</a>
{MailPoet.I18n.t('pageTitle')} <Link
className="page-title-action"
to="/new"
>{MailPoet.I18n.t('new')}</Link>
<a
className="page-title-action"
href="?page=mailpoet-import#step1"
>{MailPoet.I18n.t('import')}</a>
<a
id="mailpoet_export_button"
className="page-title-action"
href="?page=mailpoet-export"
>{MailPoet.I18n.t('export')}</a>
</h1>
<Listing
@ -352,7 +350,6 @@ const SubscriberList = React.createClass({
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages }
onGetItems={ this.onGetItems }
sort_by={ 'created_at' }
sort_order={ 'desc' }
/>

61
circle.yml Normal file
View File

@ -0,0 +1,61 @@
machine:
timezone:
UTC
hosts:
mailpoet.loc: 127.0.0.1
## Customize dependencies
dependencies:
pre:
# install PHP dependencies for WordPress
- sudo apt-get update
- sudo apt-get --assume-yes install php5-mysql
# configure Apache
- sudo cp ./.circle_ci/apache/mailpoet.loc.conf /etc/apache2/sites-available
- sudo a2ensite mailpoet.loc
- sudo a2enmod rewrite
- sudo service apache2 restart
# install Phoenix dependencies
- composer install
- ./do install
# Set up Wordpress
# No password is required for the MySQL user `ubuntu`
- mysql -u ubuntu -e "create database wordpress"
# Use cURL to fetch WP-CLI
- curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# Make sure WP-CLI is executable
- chmod +x wp-cli.phar
# Download WordPress into `wordpress` directory
- ./wp-cli.phar core download --allow-root --path=wordpress
# Generate `wp-config.php` file
- echo "define(\"WP_DEBUG\", true);" | ./wp-cli.phar core config --allow-root --dbname=wordpress --dbuser=ubuntu --dbhost=127.0.0.1:3306 --path=wordpress --extra-php
# Install WordPress
- ./wp-cli.phar core install --allow-root --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc:8080 --title=WordPress --path=wordpress
# Softlink MailPoet to plugin path
- ln -s ../../.. wordpress/wp-content/plugins/mailpoet
# Activate MailPoet
- ./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation
- echo "WP_TEST_PATH=\"/home/ubuntu/mailpoet/wordpress\"" > .env
# Enable XDebug for coverage reports.
# Comment out if not running PHP coverage reports, for performance
#- sed -i 's/^;//' /opt/circleci/php/$(phpenv global)/etc/conf.d/xdebug.ini
## tests override
test:
override:
# Run JS tests
- mkdir $CIRCLE_TEST_REPORTS/mocha
- ./do t:j $CIRCLE_TEST_REPORTS/mocha/junit.xml
# Run PHP tests
- ./do t:u --xml
# Uncomment to run coverage tests instead
#- ./do t:c --xml
# Copy the report
- mkdir $CIRCLE_TEST_REPORTS/codeception
- cp tests/_output/report.xml $CIRCLE_TEST_REPORTS/codeception/report.xml
# Uncomment to copy PHP coverage report
#- cp tests/_output/coverage.xml $CIRCLE_TEST_REPORTS/codeception/coverage.xml

View File

@ -10,6 +10,7 @@ settings:
colors: true
memory_limit: 1024M
log: true
strict_xml: true
extensions:
enabled:
- Codeception\Extension\RunFailed

View File

@ -36,23 +36,25 @@ class API {
function setupAdmin() {
if($this->checkToken() === false) {
(new ErrorResponse(
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('You need to specify a valid API token.')
),
array(),
Response::STATUS_UNAUTHORIZED
))->send();
);
$error_response->send();
}
if($this->checkPermissions() === false) {
(new ErrorResponse(
$error_response = new ErrorResponse(
array(
Error::FORBIDDEN => __('You do not have the required permissions.')
),
array(),
Response::STATUS_FORBIDDEN
))->send();
);
$error_response->send();
}
$this->processRoute();
@ -60,13 +62,14 @@ class API {
function setupPublic() {
if($this->checkToken() === false) {
(new ErrorResponse(
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('You need to specify a valid API token.')
),
array(),
Response::STATUS_UNAUTHORIZED
))->send();
);
$error_response->send();
}
$this->processRoute();
@ -99,18 +102,12 @@ class API {
try {
$endpoint = new $endpoint();
$response = $endpoint->$method($data);
// TODO: remove this condition once the API unification is complete
if(is_object($response)) {
$response->send();
} else {
// LEGACY API
wp_send_json($response);
}
$response->send();
} catch(\Exception $e) {
(new ErrorResponse(
$error_response = new ErrorResponse(
array($e->getCode() => $e->getMessage())
))->send();
);
$error_response->send();
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Cron extends APIEndpoint {
function getStatus() {
$daemon = Setting::getValue(CronHelper::DAEMON_SETTING, false);
if($daemon === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('Cron daemon is not running.')
));
} else {
return $this->successResponse($daemon);
}
}
}

View File

@ -1,34 +1,32 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\Models\CustomField;
if(!defined('ABSPATH')) exit;
class CustomFields {
function __construct() {
}
class CustomFields extends APIEndpoint {
function getAll() {
$collection = CustomField::findMany();
$custom_fields = array_map(function($custom_field) {
return $custom_field->asArray();
}, $collection);
return $custom_fields;
return $this->successResponse($custom_fields);
}
function delete($id) {
function delete($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : null);
$custom_field = CustomField::findOne($id);
if($custom_field === false or !$custom_field->id()) {
return array('result' => false);
if($custom_field === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This custom field does not exist.')
));
} else {
$custom_field->delete();
return array(
'result' => true,
'field' => $custom_field->asArray()
);
return $this->successResponse($custom_field->asArray());
}
}
@ -37,24 +35,23 @@ class CustomFields {
$errors = $custom_field->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
return $this->badRequest($errors);
} else {
return array(
'result' => true,
'field' => $custom_field->asArray()
return $this->successResponse(
CustomField::findOne($custom_field->id)->asArray()
);
}
}
function get($id) {
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : null);
$custom_field = CustomField::findOne($id);
if($custom_field === false) {
return false;
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This custom field does not exist.')
));
} else {
return $custom_field->asArray();
return $this->successResponse($custom_field->asArray());
}
}
}

View File

@ -1,5 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use \MailPoet\Models\Form;
use \MailPoet\Models\StatisticsForms;
@ -9,16 +11,17 @@ use \MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Forms {
function __construct() {
}
function get($id = false) {
class Forms extends APIEndpoint {
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
$form = $form->asArray();
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
return $this->successResponse($form->asArray());
}
return $form;
}
function listing($data = array()) {
@ -29,8 +32,8 @@ class Forms {
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as $key => $form) {
$data = array();
foreach($listing_data['items'] as $form) {
$form = $form->asArray();
$form['signups'] = StatisticsForms::getTotalSignups($form['id']);
@ -40,10 +43,15 @@ class Forms {
? $form['settings']['segments']
: array()
);
$listing_data['items'][$key] = $form;
$data[] = $form;
}
return $listing_data;
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
'groups' => $listing_data['groups']
));
}
function create() {
@ -87,14 +95,10 @@ class Forms {
$errors = $form->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
return $this->badRequest($errors);
} else {
return array(
'result' => true,
'form_id' => $form->id()
return $this->successResponse(
Form::findOne($form->id)->asArray()
);
}
}
@ -109,76 +113,74 @@ class Forms {
// styles
$css = new Util\Styles(FormRenderer::getStyles($data));
return array(
return $this->successResponse(array(
'html' => $html,
'css' => $css->render()
);
));
}
function exportsEditor($id) {
$exports = false;
function exportsEditor($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
$exports = Util\Export::getAll($form->asArray());
return $this->successResponse($exports);
}
return $exports;
}
function saveEditor($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form_id = (isset($data['id']) ? (int)$data['id'] : 0);
$name = (isset($data['name']) ? $data['name'] : __('New form'));
$body = (isset($data['body']) ? $data['body'] : array());
$settings = (isset($data['settings']) ? $data['settings'] : array());
$styles = (isset($data['styles']) ? $data['styles'] : '');
if(empty($body) || empty($settings)) {
// error
return false;
} else {
// check if the form is used as a widget
$is_widget = false;
$widgets = get_option('widget_mailpoet_form');
if(!empty($widgets)) {
foreach($widgets as $widget) {
if(isset($widget['form']) && (int)$widget['form'] === $form_id) {
$is_widget = true;
break;
}
}
}
// check if the user gets to pick his own lists
// or if it's selected by the admin
$has_segment_selection = false;
foreach($body as $i => $block) {
if($block['type'] === 'segment') {
$has_segment_selection = true;
if(!empty($block['params']['values'])) {
$list_selection = array_filter(
array_map(function($segment) {
return (isset($segment['id'])
? (int)$segment['id']
: null
);
}, $block['params']['values'])
);
}
// check if the form is used as a widget
$is_widget = false;
$widgets = get_option('widget_mailpoet_form');
if(!empty($widgets)) {
foreach($widgets as $widget) {
if(isset($widget['form']) && (int)$widget['form'] === $form_id) {
$is_widget = true;
break;
}
}
}
// check list selection
if($has_segment_selection === true) {
$settings['segments_selected_by'] = 'user';
} else {
$settings['segments_selected_by'] = 'admin';
// check if the user gets to pick his own lists
// or if it's selected by the admin
$has_segment_selection = false;
foreach($body as $i => $block) {
if($block['type'] === 'segment') {
$has_segment_selection = true;
if(!empty($block['params']['values'])) {
$list_selection = array_filter(
array_map(function($segment) {
return (isset($segment['id'])
? (int)$segment['id']
: null
);
}, $block['params']['values'])
);
}
break;
}
}
// check list selection
if($has_segment_selection === true) {
$settings['segments_selected_by'] = 'user';
} else {
$settings['segments_selected_by'] = 'admin';
}
$form = Form::createOrUpdate(array(
'id' => $form_id,
'name' => $name,
@ -187,64 +189,101 @@ class Forms {
'styles' => $styles
));
if($form->getErrors() === false) {
return array(
'result' => true,
'is_widget' => $is_widget
);
$errors = $form->getErrors();
if(!empty($errors)) {
return $this->badRequest($errors);
} else {
return array(
'result' => false,
'errors' => $form->getErrors()
return $this->successResponse(
Form::findOne($form->id)->asArray(),
array('is_widget' => $is_widget)
);
}
}
function restore($id) {
function restore($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
$form->restore();
return $this->successResponse(
Form::findOne($form->id)->asArray(),
array('count' => 1)
);
}
return ($form->getErrors() === false);
}
function trash($id) {
function trash($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
$form->trash();
return $this->successResponse(
Form::findOne($form->id)->asArray(),
array('count' => 1)
);
}
return ($form->getErrors() === false);
}
function delete($id) {
function delete($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
$form->delete();
return 1;
return $this->successResponse(null, array('count' => 1));
}
return false;
}
function duplicate($id) {
$result = false;
function duplicate($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$form = Form::findOne($id);
if($form !== false) {
if($form === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This form does not exist.')
));
} else {
$data = array(
'name' => sprintf(__('Copy of %s'), $form->name)
);
$result = $form->duplicate($data)->asArray();
}
$duplicate = $form->duplicate($data);
$errors = $duplicate->getErrors();
return $result;
if(!empty($errors)) {
return $this->errorResponse($errors);
} else {
return $this->successResponse(
Form::findOne($duplicate->id)->asArray(),
array('count' => 1)
);
}
}
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Form',
$data
);
return $bulk_action->apply();
try {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Form',
$data
);
$meta = $bulk_action->apply();
return $this->successResponse(null, $meta);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
}

View File

@ -1,5 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
use MailPoet\Models\CustomField;
@ -7,58 +9,69 @@ use MailPoet\Models\Segment;
if(!defined('ABSPATH')) exit;
class ImportExport {
class ImportExport extends APIEndpoint {
function getMailChimpLists($data) {
$mailChimp = new MailChimp($data['api_key']);
return $mailChimp->getLists();
try {
$mailChimp = new MailChimp($data['api_key']);
$lists = $mailChimp->getLists();
return $this->successResponse($lists);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
function getMailChimpSubscribers($data) {
$mailChimp = new MailChimp($data['api_key']);
return $mailChimp->getSubscribers($data['lists']);
try {
$mailChimp = new MailChimp($data['api_key']);
$subscribers = $mailChimp->getSubscribers($data['lists']);
return $this->successResponse($subscribers);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
function addSegment($data) {
$segment = Segment::createOrUpdate($data);
return (
($segment->id) ?
array(
'result' => true,
'segment' => $segment->asArray()
) :
array(
'result' => false
)
);
}
$errors = $segment->getErrors();
function addCustomField($data) {
$customField = CustomField::create();
$customField->hydrate($data);
$result = $customField->save();
return (
($result) ?
array(
'result' => true,
'customField' => $customField->asArray()
) :
array(
'result' => false
)
);
if(!empty($errors)) {
return $this->errorResponse($errors);
} else {
return $this->successResponse(
Segment::findOne($segment->id)->asArray()
);
}
}
function processImport($data) {
$import = new \MailPoet\Subscribers\ImportExport\Import\Import(
json_decode($data, true)
);
return $import->process();
try {
$import = new \MailPoet\Subscribers\ImportExport\Import\Import(
json_decode($data, true)
);
$process = $import->process();
return $this->successResponse($process);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
function processExport($data) {
$export = new \MailPoet\Subscribers\ImportExport\Export\Export(
json_decode($data, true)
);
return $export->process();
try {
$export = new \MailPoet\Subscribers\ImportExport\Export\Export(
json_decode($data, true)
);
$process = $export->process();
return $this->successResponse($process);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
}

View File

@ -5,6 +5,7 @@ use MailPoet\API\Error as APIError;
use MailPoet\Listing;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\NewsletterSegment;
@ -21,19 +22,20 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Newsletters extends APIEndpoint {
function get($id = false) {
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return false;
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$segments = $newsletter->segments()->findArray();
$options = $newsletter->options()->findArray();
$newsletter = $newsletter->asArray();
$newsletter['segments'] = array_map(function($segment) {
return $segment['id'];
}, $segments);
$newsletter['options'] = Helpers::arrayColumn($options, 'value', 'name');
return $newsletter;
return $this->successResponse(
$newsletter
->withSegments()
->withOptions()
->asArray()
);
}
}
@ -95,8 +97,6 @@ class Newsletters extends APIEndpoint {
}
function setStatus($data = array()) {
$id = (isset($data['id'])) ? (int)$data['id'] : false;
$newsletter = Newsletter::findOne($id);
$status = (isset($data['status']) ? $data['status'] : null);
if(!$status) {
@ -105,6 +105,9 @@ class Newsletters extends APIEndpoint {
));
}
$id = (isset($data['id'])) ? (int)$data['id'] : false;
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
@ -118,142 +121,179 @@ class Newsletters extends APIEndpoint {
return $this->errorResponse($errors);
} else {
return $this->successResponse(
$newsletter->asArray()
Newsletter::findOne($newsletter->id)->asArray()
);
}
}
function restore($id) {
function restore($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->restore();
}
return ($newsletter->getErrors() === false);
}
function trash($id) {
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->trash();
}
return ($newsletter->getErrors() === false);
}
function delete($id) {
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->delete();
return 1;
}
return false;
}
function duplicate($id = false) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$duplicate = $newsletter->duplicate(array(
'subject' => sprintf(__('Copy of %s'), $newsletter->subject)
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$newsletter->restore();
return $this->successResponse(
Newsletter::findOne($newsletter->id)->asArray(),
array('count' => 1)
);
}
}
if($duplicate !== false && $duplicate->getErrors() === false) {
$result = $duplicate->asArray();
function trash($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$newsletter->trash();
return $this->successResponse(
Newsletter::findOne($newsletter->id)->asArray(),
array('count' => 1)
);
}
}
function delete($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$newsletter->delete();
return $this->successResponse(null, array('count' => 1));
}
}
function duplicate($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$data = array(
'subject' => sprintf(__('Copy of %s'), $newsletter->subject)
);
$duplicate = $newsletter->duplicate($data);
$errors = $duplicate->getErrors();
if(!empty($errors)) {
return $this->errorResponse($errors);
} else {
return $this->successResponse(
Newsletter::findOne($duplicate->id)->asArray(),
array('count' => 1)
);
}
}
return $result;
}
function showPreview($data = array()) {
if(!isset($data['body'])) {
return array(
'result' => false,
'errors' => array(__('Newsletter data is missing'))
);
if(empty($data['body'])) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Newsletter data is missing.')
));
}
$newsletter_id = (isset($data['id'])) ? (int)$data['id'] : null;
$newsletter = Newsletter::findOne($newsletter_id);
if(!$newsletter) {
return array(
'result' => false,
'errors' => array(__('Newsletter could not be read'))
);
}
$newsletter->body = $data['body'];
$newsletter->save();
$subscriber = Subscriber::getCurrentWPUser();
$preview_url = NewsletterUrl::getViewInBrowserUrl(
$data, $subscriber, $queue = false, $preview = true
);
return array(
'result' => true,
'data' => array('url' => $preview_url)
);
}
function sendPreview($data = array()) {
$id = (isset($data['id'])) ? (int)$data['id'] : false;
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return array(
'result' => false
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$newsletter->body = $data['body'];
$newsletter->save();
$subscriber = Subscriber::getCurrentWPUser();
$preview_url = NewsletterUrl::getViewInBrowserUrl(
$data, $subscriber, $queue = false, $preview = true
);
}
if(empty($data['subscriber'])) {
return array(
'result' => false,
'errors' => array(__('Please specify receiver information.'))
);
}
$newsletter = $newsletter->asArray();
$renderer = new Renderer($newsletter, $preview = true);
$rendered_newsletter = $renderer->render();
$divider = '***MailPoet***';
$data_for_shortcodes =
array_merge(array($newsletter['subject']), $rendered_newsletter);
$body = implode($divider, $data_for_shortcodes);
$subscriber = Subscriber::getCurrentWPUser();
$subscriber = ($subscriber) ? $subscriber->asArray() : false;
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$newsletter,
$subscriber
);
list($newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = explode($divider, $shortcodes->replace($body));
try {
$mailer = new \MailPoet\Mailer\Mailer(
$mailer = false,
$sender = false,
$reply_to = false
);
$result = $mailer->send($newsletter, $data['subscriber']);
return array('result' => $result);
} catch(\Exception $e) {
return array(
'result' => false,
'errors' => array($e->getMessage()),
return $this->successResponse(
Newsletter::findOne($newsletter->id)->asArray(),
array('preview_url' => $preview_url)
);
}
}
function listing($data = array()) {
function sendPreview($data = array()) {
if(empty($data['subscriber'])) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please specify receiver information.')
));
}
$id = (isset($data['id'])) ? (int)$data['id'] : false;
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This newsletter does not exist.')
));
} else {
$newsletter = $newsletter->asArray();
$renderer = new Renderer($newsletter, $preview = true);
$rendered_newsletter = $renderer->render();
$divider = '***MailPoet***';
$data_for_shortcodes = array_merge(
array($newsletter['subject']),
$rendered_newsletter
);
$body = implode($divider, $data_for_shortcodes);
$subscriber = Subscriber::getCurrentWPUser();
$subscriber = ($subscriber) ? $subscriber->asArray() : false;
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$newsletter,
$subscriber
);
list(
$newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = explode($divider, $shortcodes->replace($body));
try {
$mailer = new \MailPoet\Mailer\Mailer(
$mailer = false,
$sender = false,
$reply_to = false
);
$mailer->send($newsletter, $data['subscriber']);
return $this->successResponse(
Newsletter::findOne($id)->asArray()
);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
}
function listing($data = array()) {
$listing = new Listing\Handler(
'\MailPoet\Models\Newsletter',
$data
);
$listing_data = $listing->get();
foreach($listing_data['items'] as $key => $newsletter) {
$data = array();
foreach($listing_data['items'] as $newsletter) {
$queue = false;
if($newsletter->type === Newsletter::TYPE_STANDARD) {
$newsletter
@ -277,22 +317,40 @@ class Newsletters extends APIEndpoint {
->withStatistics();
}
// get preview url
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl($newsletter);
if($newsletter->status === Newsletter::STATUS_SENT ||
$newsletter->status === Newsletter::STATUS_SENDING
) {
$queue = $newsletter->getQueue();
}
// convert object to array
$listing_data['items'][$key] = $newsletter->asArray();
// get preview url
$subscriber = Subscriber::getCurrentWPUser();
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl(
$newsletter, $subscriber, $queue, $preview = true);
$data[] = $newsletter->asArray();
}
return $listing_data;
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
'groups' => $listing_data['groups']
));
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Newsletter',
$data
);
return $bulk_action->apply();
try {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Newsletter',
$data
);
$meta = $bulk_action->apply();
return $this->successResponse(null, $meta);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
function create($data = array()) {
@ -303,23 +361,25 @@ class Newsletters extends APIEndpoint {
}
$newsletter = Newsletter::createOrUpdate($data);
$errors = $newsletter->getErrors();
// try to load template data
$template_id = (!empty($data['template']) ? (int)$data['template'] : false);
$template = NewsletterTemplate::findOne($template_id);
if($template !== false) {
$newsletter->body = $template->body;
if(!empty($errors)) {
return $this->badRequest($errors);
} else {
$newsletter->body = array();
// try to load template data
$template_id = (isset($data['template']) ? (int)$data['template'] : false);
$template = NewsletterTemplate::findOne($template_id);
if($template === false) {
$newsletter->body = array();
} else {
$newsletter->body = $template->body;
}
}
$newsletter->save();
$errors = $newsletter->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' =>$errors
);
return $this->badRequest($errors);
} else {
if(!empty($options)) {
$option_fields = NewsletterOptionField::where(
@ -336,15 +396,19 @@ class Newsletters extends APIEndpoint {
}
}
}
if(!isset($data['id']) &&
isset($data['type']) &&
$data['type'] === 'notification'
if(
empty($data['id'])
&&
isset($data['type'])
&&
$data['type'] === Newsletter::TYPE_NOTIFICATION
) {
Scheduler::processPostNotificationSchedule($newsletter->id);
}
return array(
'result' => true,
'newsletter' => $newsletter->asArray()
return $this->successResponse(
Newsletter::findOne($newsletter->id)->asArray()
);
}
}

View File

@ -1,5 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\Models\Segment;
use \MailPoet\Models\SubscriberSegment;
@ -9,16 +11,16 @@ use \MailPoet\Segments\WP;
if(!defined('ABSPATH')) exit;
class Segments {
function __construct() {
}
function get($id = false) {
class Segments extends APIEndpoint {
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);
if($segment === false) {
return false;
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This list does not exist.')
));
} else {
return $segment->asArray();
return $this->successResponse($segment->asArray());
}
}
@ -30,18 +32,22 @@ class Segments {
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as $key => $segment) {
$data = array();
foreach($listing_data['items'] as $segment) {
$segment->subscribers_url = admin_url(
'admin.php?page=mailpoet-subscribers#/filter[segment='.$segment->id.']'
);
$listing_data['items'][$key] = $segment
$data[] = $segment
->withSubscribersCount()
->asArray();
}
return $listing_data;
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
'groups' => $listing_data['groups']
));
}
function save($data = array()) {
@ -49,68 +55,109 @@ class Segments {
$errors = $segment->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
return $this->badRequest($errors);
} else {
return array(
'result' => true
return $this->successResponse(
Segment::findOne($segment->id)->asArray()
);
}
}
function restore($id) {
function restore($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);
if($segment !== false) {
if($segment === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This list does not exist.')
));
} else {
$segment->restore();
return $this->successResponse(
Segment::findOne($segment->id)->asArray(),
array('count' => 1)
);
}
return ($segment->getErrors() === false);
}
function trash($id) {
function trash($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);
if($segment !== false) {
if($segment === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This list does not exist.')
));
} else {
$segment->trash();
return $this->successResponse(
Segment::findOne($segment->id)->asArray(),
array('count' => 1)
);
}
return ($segment->getErrors() === false);
}
function delete($id) {
function delete($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);
if($segment !== false) {
if($segment === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This list does not exist.')
));
} else {
$segment->delete();
return 1;
return $this->successResponse(null, array('count' => 1));
}
return false;
}
function duplicate($id) {
$result = false;
function duplicate($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$segment = Segment::findOne($id);
if($segment !== false) {
if($segment === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This list does not exist.')
));
} else {
$data = array(
'name' => sprintf(__('Copy of %s'), $segment->name)
);
$result = $segment->duplicate($data)->asArray();
}
$duplicate = $segment->duplicate($data);
$errors = $duplicate->getErrors();
return $result;
if(!empty($errors)) {
return $this->errorResponse($errors);
} else {
return $this->successResponse(
Segment::findOne($duplicate->id)->asArray(),
array('count' => 1)
);
}
}
}
function synchronize() {
$result = WP::synchronizeUsers();
try {
WP::synchronizeUsers();
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
return $result;
return $this->successResponse(null);
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Segment',
$data
);
return $bulk_action->apply();
try {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Segment',
$data
);
$meta = $bulk_action->apply();
return $this->successResponse(null, $meta);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
}

View File

@ -1,5 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
@ -13,19 +15,22 @@ use MailPoet\Util\Url;
if(!defined('ABSPATH')) exit;
class Subscribers {
function __construct() {
}
function get($id = null) {
class Subscribers extends APIEndpoint {
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$subscriber = $subscriber
->withCustomFields()
->withSubscriptions()
->asArray();
if($subscriber === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This subscriber does not exist.')
));
} else {
return $this->successResponse(
$subscriber
->withCustomFields()
->withSubscriptions()
->asArray()
);
}
return $subscriber;
}
function listing($data = array()) {
@ -36,30 +41,18 @@ class Subscribers {
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as $key => $subscriber) {
$listing_data['items'][$key] = $subscriber
$data = array();
foreach($listing_data['items'] as $subscriber) {
$data[] = $subscriber
->withSubscriptions()
->asArray();
}
return $listing_data;
}
function save($data = array()) {
$subscriber = Subscriber::createOrUpdate($data);
$errors = $subscriber->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
} else {
return array(
'result' => true
);
}
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
'groups' => $listing_data['groups']
));
}
function subscribe($data = array()) {
@ -148,37 +141,76 @@ class Subscribers {
}
}
function restore($id) {
function save($data = array()) {
$subscriber = Subscriber::createOrUpdate($data);
$errors = $subscriber->getErrors();
if(!empty($errors)) {
return $this->badRequest($errors);
} else {
return $this->successResponse(
Subscriber::findOne($subscriber->id)->asArray()
);
}
}
function restore($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
if($subscriber === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This subscriber does not exist.')
));
} else {
$subscriber->restore();
return $this->successResponse(
Subscriber::findOne($subscriber->id)->asArray(),
array('count' => 1)
);
}
return ($subscriber->getErrors() === false);
}
function trash($id) {
function trash($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
if($subscriber === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This subscriber does not exist.')
));
} else {
$subscriber->trash();
return $this->successResponse(
Subscriber::findOne($subscriber->id)->asArray(),
array('count' => 1)
);
}
return ($subscriber->getErrors() === false);
}
function delete($id) {
function delete($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
if($subscriber === false) {
return $this->errorResponse(array(
APIError::NOT_FOUND => __('This subscriber does not exist.')
));
} else {
$subscriber->delete();
return 1;
return $this->successResponse(null, array('count' => 1));
}
return false;
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Subscriber',
$data
);
return $bulk_action->apply();
try {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Subscriber',
$data
);
$meta = $bulk_action->apply();
return $this->successResponse(null, $meta);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
}
}

View File

@ -31,11 +31,11 @@ abstract class Response {
$response = array_merge($response, $data);
}
if(empty($response)) {
die();
} else {
wp_send_json($response);
if(!empty($response)) {
@header('Content-Type: application/json; charset='.get_option('blog_charset'));
echo wp_json_encode($response);
}
die();
}
abstract function getData();

View File

@ -27,6 +27,7 @@ class Env {
static $db_password;
static $db_charset;
static $db_timezone_offset;
static $subscribers_limit = 2000;
static function init($file, $version) {
global $wpdb;

View File

@ -1,7 +1,6 @@
<?php
namespace MailPoet\Config;
use MailPoet\Models;
use MailPoet\Cron\CronTrigger;
use MailPoet\Router;
use MailPoet\API;
@ -122,7 +121,7 @@ class Initializer {
try {
$this->setupAPI();
$this->setupFrontRouter();
$this->setupRouter();
$this->setupPages();
} catch(\Exception $e) {
$this->handleFailedInitialization($e);
@ -187,14 +186,17 @@ class Initializer {
$api->init();
}
function setupFrontRouter() {
$router = new Router\Front();
function setupRouter() {
$router = new Router\Router();
$router->init();
}
function setupCronTrigger() {
$cron_trigger = new CronTrigger();
$cron_trigger->init();
// setup cron trigger only outside of cli environment
if(php_sapi_name() !== 'cli') {
$cron_trigger = new CronTrigger();
$cron_trigger->init();
}
}
function setupImages() {

View File

@ -8,6 +8,7 @@ use MailPoet\Models\CustomField;
use MailPoet\Models\Form;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages;
@ -33,6 +34,16 @@ class Menu {
);
}
function checkSubscribersLimit() {
$subscribers_count = Subscriber::getTotalSubscribers();
if($subscribers_count > Env::$subscribers_limit) {
echo $this->renderer->render('limit.html', array(
'limit' => Env::$subscribers_limit
));
exit;
}
}
function setup() {
$main_page_slug = 'mailpoet-newsletters';
@ -185,15 +196,6 @@ class Menu {
'mailpoet-newsletter-editor',
array($this, 'newletterEditor')
);
add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Cron')),
__('Cron'),
'manage_options',
'mailpoet-cron',
array($this, 'cron')
);
}
function welcome() {
@ -250,6 +252,8 @@ class Menu {
}
function settings() {
$this->checkSubscribersLimit();
$settings = Setting::getAll();
$flags = $this->_getFlags();
@ -322,12 +326,16 @@ class Menu {
}
function segments() {
$this->checkSubscribersLimit();
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('segments');
echo $this->renderer->render('segments.html', $data);
}
function forms() {
$this->checkSubscribersLimit();
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('forms');
@ -337,6 +345,8 @@ class Menu {
}
function newsletters() {
$this->checkSubscribersLimit();
global $wp_roles;
$data = array();
@ -368,6 +378,7 @@ class Menu {
$data = array(
'shortcodes' => ShortcodesHelper::getShortcodes(),
'settings' => Setting::getAll(),
'current_wp_user' => Subscriber::getCurrentWPUser(),
'sub_menu' => 'mailpoet-newsletters'
);
wp_enqueue_media();
@ -379,7 +390,12 @@ class Menu {
function import() {
$import = new ImportExportFactory('import');
$data = $import->bootstrap();
$data['sub_menu'] = 'mailpoet-subscribers';
$data = array_merge($data, array(
'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats(),
'month_names' => Block\Date::getMonthNames(),
'sub_menu' => 'mailpoet-subscribers'
));
echo $this->renderer->render('subscribers/importExport/import.html', $data);
}
@ -411,10 +427,6 @@ class Menu {
echo $this->renderer->render('form/editor.html', $data);
}
function cron() {
echo $this->renderer->render('cron.html');
}
function setPageTitle($title) {
return sprintf(
'%s - %s',
@ -435,4 +447,4 @@ class Menu {
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
}
}

View File

@ -1,15 +1,6 @@
<?php
namespace MailPoet\Config;
use MailPoet\Config\PopulatorData\Templates\FranksRoastHouseTemplate;
use MailPoet\Config\PopulatorData\Templates\NewsletterBlank1Column;
use MailPoet\Config\PopulatorData\Templates\NewsletterBlank12Column;
use MailPoet\Config\PopulatorData\Templates\NewsletterBlank121Column;
use MailPoet\Config\PopulatorData\Templates\NewsletterBlank13Column;
use MailPoet\Config\PopulatorData\Templates\PostNotificationsBlank1Column;
use MailPoet\Config\PopulatorData\Templates\WelcomeBlank1Column;
use MailPoet\Config\PopulatorData\Templates\WelcomeBlank12Column;
use MailPoet\Config\PopulatorData\Templates\SimpleText;
use MailPoet\Cron\CronTrigger;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
@ -22,12 +13,31 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
class Populator {
public $prefix;
public $models;
public $templates;
const TEMPLATES_NAMESPACE = '\MailPoet\Config\PopulatorData\Templates\\';
function __construct() {
$this->prefix = Env::$db_prefix;
$this->models = array(
'newsletter_option_fields',
'newsletter_templates',
);
$this->templates = array(
"FranksRoastHouseTemplate",
"NewsletterBlank1Column",
"NewsletterBlank12Column",
"NewsletterBlank121Column",
"NewsletterBlank13Column",
"PostNotificationsBlank1Column",
"WelcomeBlank1Column",
"WelcomeBlank12Column",
"SimpleText",
"Restaurant",
"StoreDiscount",
"TravelEmail"
);
}
function up() {
@ -104,6 +114,10 @@ class Populator {
'reply_to' => $sender
));
}
if(!Setting::getValue('installed_at')) {
Setting::setValue('installed_at', date("Y-m-d H:i:s"));
}
}
private function createDefaultSegments() {
@ -184,17 +198,13 @@ class Populator {
}
private function newsletterTemplates() {
return array(
(new FranksRoastHouseTemplate(Env::$assets_url))->get(),
(new NewsletterBlank1Column(Env::$assets_url))->get(),
(new NewsletterBlank12Column(Env::$assets_url))->get(),
(new NewsletterBlank121Column(Env::$assets_url))->get(),
(new NewsletterBlank13Column(Env::$assets_url))->get(),
(new PostNotificationsBlank1Column(Env::$assets_url))->get(),
(new WelcomeBlank1Column(Env::$assets_url))->get(),
(new WelcomeBlank12Column(Env::$assets_url))->get(),
(new SimpleText(Env::$assets_url))->get(),
);
$templates = array();
foreach($this->templates as $template) {
$template = self::TEMPLATES_NAMESPACE . $template;
$template = new $template(Env::$assets_url);
$templates[] = $template->get();
}
return $templates;
}
private function populate($model) {

View File

@ -7,7 +7,7 @@ class FranksRoastHouseTemplate {
function __construct($assets_url) {
$this->assets_url = $assets_url;
$this->template_image_url = $this->assets_url . '/img/sample_template';
$this->template_image_url = $this->assets_url . '/img/sample_templates/coffee';
$this->social_icon_url = $this->assets_url . '/img/newsletter_editor/social-icons';
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
<?php
namespace MailPoet\Cron;
use Carbon\Carbon;
use MailPoet\Models\Setting;
class BootStrapMenu {
function __construct() {
$this->daemon = Setting::where('name', 'cron_daemon')
->findOne();
}
function bootStrap() {
return ($this->daemon) ?
array_merge(
array(
'timeSinceStart' => Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->created_at,
'UTC'
)->diffForHumans(),
'timeSinceUpdate' => Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at,
'UTC'
)->diffForHumans()
),
json_decode($this->daemon->value, true)
) :
"false";
}
}

View File

@ -3,7 +3,7 @@ namespace MailPoet\Cron;
use MailPoet\Models\Setting;
use MailPoet\Router\Endpoints\Queue as QueueEndpoint;
use MailPoet\Router\Front as FrontRouter;
use MailPoet\Router\Router;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -16,7 +16,6 @@ class CronHelper {
static function createDaemon($token) {
$daemon = array(
'status' => Daemon::STATUS_STARTING,
'token' => $token
);
self::saveDaemon($daemon);
@ -41,7 +40,6 @@ class CronHelper {
static function stopDaemon() {
$daemon = self::getDaemon();
$daemon['status'] = Daemon::STATUS_STOPPED;
return self::saveDaemon($daemon);
}
@ -55,11 +53,12 @@ class CronHelper {
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
$data = array('token' => $token);
$url = FrontRouter::buildRequest(
$url = Router::buildRequest(
QueueEndpoint::ENDPOINT,
QueueEndpoint::ACTION_RUN,
$data
);
$url = str_replace(home_url(), self::getSiteUrl(), $url);
$args = array(
'blocking' => false,
'sslverify' => false,
@ -70,20 +69,22 @@ class CronHelper {
return wp_remote_retrieve_body($result);
}
private static function getSiteUrl() {
// additional check for some sites running on a virtual machine or behind
static function getSiteUrl($site_url = false) {
// additional check for some sites running inside a virtual machine or behind
// proxy where there could be different ports (e.g., host:8080 => guest:80)
// if the site URL does not contain a port, return the URL
if(!preg_match('!^https?://.*?:\d+!', site_url())) return site_url();
preg_match('!://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// connect to the URL with port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return site_url();
// connect to the URL without port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connection attempts failed
$site_url = ($site_url) ? $site_url : home_url();
$parsed_url = parse_url($site_url);
// 1. if site URL does not contain a port, return the URL
if(empty($parsed_url['port'])) return $site_url;
// 2. if site URL contains valid port, try connecting to it
$fp = @fsockopen($parsed_url['host'], $parsed_url['port'], $errno, $errstr, 1);
if($fp) return $site_url;
// 3. if connection fails, attempt to connect the standard port derived from URL
// schema
$port = (strtolower($parsed_url['scheme']) === 'http') ? 80 : 443;
$fp = @fsockopen($parsed_url['host'], $port, $errno, $errstr, 1);
if($fp) return sprintf('%s://%s', $parsed_url['scheme'], $parsed_url['host']);
// 4. throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.'));
}

View File

@ -20,8 +20,6 @@ class CronTrigger {
function init() {
try {
// configure cron trigger only outside of cli environment
if(php_sapi_name() === 'cli') return;
$trigger_class = __NAMESPACE__ . '\Triggers\\' . $this->current_method;
return (class_exists($trigger_class)) ?
$trigger_class::run() :

View File

@ -10,77 +10,75 @@ if(!defined('ABSPATH')) exit;
class Daemon {
public $daemon;
public $data;
public $refreshed_token;
const STATUS_STOPPED = 'stopped';
const STATUS_STOPPING = 'stopping';
const STATUS_STARTED = 'started';
const STATUS_STARTING = 'starting';
public $request_data;
public $timer;
const REQUEST_TIMEOUT = 5;
private $timer;
function __construct($data) {
if(empty($data)) $this->abortWithError(__('Invalid or missing Cron data.'));
ignore_user_abort();
function __construct($request_data = false) {
$this->request_data = $request_data;
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
$this->data = $data;
$this->timer = microtime(true);
}
function run() {
ignore_user_abort(true);
if(!$this->request_data) {
$error = __('Invalid or missing request data.');
} else {
if(!$this->daemon) {
$error = __('Daemon does not exist.');
} else {
if(!isset($this->request_data['token']) ||
$this->request_data['token'] !== $this->daemon['token']
) {
$error = 'Invalid or missing token.';
}
}
}
if(!empty($error)) {
return $this->abortWithError($error);
}
$daemon = $this->daemon;
if(!$daemon) {
$this->abortWithError(__('Daemon does not exist.'));
}
if(!isset($this->data['token']) ||
$this->data['token'] !== $daemon['token']
) {
$this->abortWithError(__('Invalid or missing token.'));
}
$daemon['token'] = $this->token;
CronHelper::saveDaemon($daemon);
$this->abortIfStopped($daemon);
try {
$scheduler = new SchedulerWorker($this->timer);
$scheduler->process();
$queue = new SendingQueueWorker($this->timer);
$queue->process();
$this->executeScheduleWorker();
$this->executeQueueWorker();
} catch(\Exception $e) {
// continue processing, no need to handle errors
}
// if workers took less time to execute than the daemon execution limit,
// pause daemon execution to ensure that daemon runs only once every X seconds
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time < CronHelper::DAEMON_EXECUTION_LIMIT) {
sleep(CronHelper::DAEMON_EXECUTION_LIMIT - $elapsed_time);
$this->pauseExecution(CronHelper::DAEMON_EXECUTION_LIMIT - $elapsed_time);
}
// after each execution, re-read daemon data in case its status was changed
// its status has changed
// after each execution, re-read daemon data in case it changed
$daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['token'] !== $this->token) {
$this->terminateRequest();
return $this->terminateRequest();
}
$this->abortIfStopped($daemon);
if($daemon['status'] === self::STATUS_STARTING) {
$daemon['status'] = self::STATUS_STARTED;
}
CronHelper::saveDaemon($daemon);
$this->callSelf();
return $this->callSelf();
}
function abortIfStopped($daemon) {
if($daemon['status'] === self::STATUS_STOPPED) {
$this->terminateRequest();
}
if($daemon['status'] === self::STATUS_STOPPING) {
$daemon['status'] = self::STATUS_STOPPED;
CronHelper::saveDaemon($daemon);
$this->terminateRequest();
}
function pauseExecution($pause_time) {
return sleep($pause_time);
}
function executeScheduleWorker() {
$scheduler = new SchedulerWorker($this->timer);
return $scheduler->process();
}
function executeQueueWorker() {
$queue = new SendingQueueWorker($this->timer);
return $queue->process();
}
function callSelf() {
CronHelper::accessDaemon($this->token, self::REQUEST_TIMEOUT);
$this->terminateRequest();
return $this->terminateRequest();
}
function abortWithError($message) {

View File

@ -15,7 +15,7 @@ class Supervisor {
function checkDaemon() {
$daemon = $this->daemon;
$execution_timeout_exceeded =
(time() - (int)$daemon['updated_at']) > CronHelper::DAEMON_EXECUTION_TIMEOUT;
(time() - (int)$daemon['updated_at']) >= CronHelper::DAEMON_EXECUTION_TIMEOUT;
if($execution_timeout_exceeded) {
CronHelper::restartDaemon($this->token);
return $this->runDaemon();

View File

@ -26,7 +26,7 @@ class Scheduler {
function process() {
$scheduled_queues = self::getScheduledQueues();
if(!count($scheduled_queues)) return;
if(!count($scheduled_queues)) return false;
foreach($scheduled_queues as $i => $queue) {
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($queue->newsletter_id);
@ -47,30 +47,30 @@ class Scheduler {
$subscriber = unserialize($queue->subscribers);
if(empty($subscriber['to_process'][0])) {
$queue->delete();
return;
return false;
}
$subscriber_id = (int)$subscriber['to_process'][0];
if($newsletter->event === 'segment') {
if($this->verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
if($this->verifyMailpoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
return false;
}
} else {
if($newsletter->event === 'user') {
if($this->verifyWPSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
return false;
}
}
}
$queue->status = null;
$queue->save();
return true;
}
function processPostNotificationNewsletter($newsletter, $queue) {
// ensure that segments exist
$segments = $newsletter->segments()->findArray();
if(empty($segments)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
}
$segment_ids = array_map(function($segment) {
return (int)$segment['id'];
@ -83,13 +83,12 @@ class Scheduler {
$subscribers = array_unique($subscribers);
if(empty($subscribers)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
}
// create a duplicate newsletter that acts as a history record
$notification_history = $this->createNotificationHistory($newsletter->id);
if(!$notification_history) return;
if(!$notification_history) return false;
// queue newsletter for delivery
$queue->newsletter_id = $notification_history->id;
@ -101,6 +100,7 @@ class Scheduler {
$queue->count_total = $queue->count_to_process = count($subscribers);
$queue->status = null;
$queue->save();
return true;
}
function processScheduledStandardNewsletter($newsletter, $queue) {
@ -108,7 +108,6 @@ class Scheduler {
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
@ -123,9 +122,10 @@ class Scheduler {
$queue->count_total = $queue->count_to_process = count($subscribers);
$queue->status = null;
$queue->save();
return true;
}
function verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) {
function verifyMailpoetSubscriber($subscriber_id, $newsletter, $queue) {
$subscriber = Subscriber::findOne($subscriber_id);
// check if subscriber is in proper segment
$subscriber_in_segment =
@ -184,7 +184,7 @@ class Scheduler {
$notification_history :
false;
}
static function getScheduledQueues() {
return SendingQueue::where('status', 'scheduled')
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))

View File

@ -88,7 +88,6 @@ class Newsletter {
);
if($this->tracking_enabled) {
$prepared_newsletter = NewsletterLinks::replaceSubscriberData(
$newsletter['id'],
$subscriber['id'],
$queue['id'],
$prepared_newsletter

View File

@ -2,7 +2,7 @@
namespace MailPoet\Form\Block;
abstract class Base {
protected static function getInputValidation($block) {
protected static function getInputValidation($block, $extra_rules = array()) {
$rules = array();
if($block['id'] === 'email') {
@ -37,8 +37,15 @@ abstract class Base {
$rules['required-message'] = __('Please select at least one option');
}
if($block['type'] === 'date') {
$rules['group'] = 'custom_field_'.$block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id'];
}
$validation = array();
$rules = array_merge($rules, $extra_rules);
if(!empty($rules)) {
$rules = array_unique($rules);
foreach($rules as $rule => $value) {

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Form\Block;
use Carbon\Carbon;
class Date extends Base {
static function render($block) {
@ -17,7 +19,6 @@ class Date extends Base {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$date_formats = static::getDateFormats();
@ -65,27 +66,38 @@ class Date extends Base {
}
foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') {
if($date_selector === 'DD') {
$block['selected'] = $day;
$html .= '<select class="mailpoet_date_day" ';
$html .= static::getInputValidation($block, array(
'required-message' => __('Please select a day')
));
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block);
$html .= '</select>';
} else if($date_selector === 'mm') {
} else if($date_selector === 'MM') {
$block['selected'] = $month;
$html .= '<select class="mailpoet_date_month" ';
$html .= static::getInputValidation($block, array(
'required-message' => __('Please select a month')
));
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block);
$html .= '</select>';
} else if($date_selector === 'yyyy') {
} else if($date_selector === 'YYYY') {
$block['selected'] = $year;
$html .= '<select class="mailpoet_date_year" ';
$html .= static::getInputValidation($block, array(
'required-message' => __('Please select a year')
));
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block);
$html .= '</select>';
}
}
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
return $html;
}
@ -100,10 +112,10 @@ class Date extends Base {
static function getDateFormats() {
return array(
'year_month_day' => array('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy/mm/dd'),
'year_month' => array('mm/yyyy', 'yyyy/mm'),
'year' => array('yyyy'),
'month' => array('mm')
'year_month_day' => array('MM/DD/YYYY', 'DD/MM/YYYY', 'YYYY/MM/DD'),
'year_month' => array('MM/YYYY', 'YYYY/MM'),
'year' => array('YYYY'),
'month' => array('MM')
);
}
static function getMonthNames() {
@ -192,4 +204,85 @@ class Date extends Base {
return $html;
}
static function convertDateToDatetime($date, $date_format) {
$datetime = false;
if($date_format === 'datetime') {
$datetime = $date;
} else {
$parsed_date = explode('/', $date);
$parsed_date_format = explode('/', $date_format);
$year_position = array_search('YYYY', $parsed_date_format);
$month_position = array_search('MM', $parsed_date_format);
$day_position = array_search('DD', $parsed_date_format);
if(count($parsed_date) === 3) {
// create date from any combination of month, day and year
$parsed_date = array(
'year' => $parsed_date[$year_position],
'month' => $parsed_date[$month_position],
'day' => $parsed_date[$day_position]
);
} else if(count($parsed_date) === 2) {
// create date from any combination of month and year
$parsed_date = array(
'year' => $parsed_date[$year_position],
'month' => $parsed_date[$month_position],
'day' => '01'
);
} else if($date_format === 'MM' && count($parsed_date) === 1) {
// create date from month
if((int)$parsed_date[$month_position] === 0) {
$datetime = '';
$parsed_date = false;
} else {
$parsed_date = array(
'month' => $parsed_date[$month_position],
'day' => '01',
'year' => date('Y')
);
}
} else if($date_format === 'YYYY' && count($parsed_date) === 1) {
// create date from year
if((int)$parsed_date[$year_position] === 0) {
$datetime = '';
$parsed_date = false;
} else {
$parsed_date = array(
'year' => $parsed_date[$year_position],
'month' => '01',
'day' => '01'
);
}
} else {
$parsed_date = false;
}
if($parsed_date) {
$year = $parsed_date['year'];
$month = $parsed_date['month'];
$day = $parsed_date['day'];
// if all date parts are set to 0, date value is empty
if((int)$year === 0 && (int)$month === 0 && (int)$day === 0) {
$datetime = '';
} else {
if((int)$year === 0) $year = date('Y');
if((int)$month === 0) $month = date('m');
if((int)$day === 0) $day = date('d');
$datetime = sprintf(
'%s-%s-%s 00:00:00',
$year,
$month,
$day
);
}
}
}
if($datetime !== false && !empty($datetime)) {
try {
$datetime = Carbon::parse($datetime)->toDateTimeString();
} catch(\Exception $e) {
$datetime = false;
}
}
return $datetime;
}
}

View File

@ -26,7 +26,7 @@ class BulkAction {
if(!method_exists($this->model_class, $bulk_action_method)) {
throw new \Exception(
$this->model_class. ' has not method "'.$bulk_action_method.'"'
$this->model_class. ' has no method "'.$bulk_action_method.'"'
);
}

View File

@ -106,6 +106,7 @@ class Handler {
);
}
// get items and total count
if(method_exists($this->model_class, 'listingQuery')) {
$custom_query = call_user_func_array(
array($this->model_class, 'listingQuery'),
@ -135,7 +136,6 @@ class Handler {
->findMany();
}
return array(
'count' => $count,
'filters' => $filters,

View File

@ -12,7 +12,7 @@ class Mailer {
public $sender;
public $reply_to;
public $mailer_instance;
const MAILER_CONFIG = 'mta';
const MAILER_CONFIG_SETTING_NAME = 'mta';
const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
const METHOD_MAILPOET = 'MailPoet';
const METHOD_MAILGUN = 'MailGun';
@ -100,7 +100,7 @@ class Mailer {
static function getMailerConfig($mailer = false) {
if(!$mailer) {
$mailer = Setting::getValue(self::MAILER_CONFIG);
$mailer = Setting::getValue(self::MAILER_CONFIG_SETTING_NAME);
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured'));
}
if(empty($mailer['frequency'])) {

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Models;
use MailPoet\Form\Block\Date;
if(!defined('ABSPATH')) exit;
class CustomField extends Model {
@ -43,28 +45,27 @@ class CustomField extends Model {
// format custom field data depending on type
if(is_array($value) && $this->type === 'date' ) {
$custom_field_data = $this->asArray();
$date_format = $custom_field_data['params']['date_format'];
$date_type = (isset($custom_field_data['params']['date_type'])
? $custom_field_data['params']['date_type']
: 'year_month_day'
);
$date_parts = explode('_', $date_type);
switch($date_type) {
case 'year_month_day':
$value = sprintf(
'%04d-%02d-%02d',
$value['year'],
'%s/%s/%s',
$value['month'],
$value['day']
$value['day'],
$value['year']
);
break;
case 'year_month':
$value = sprintf(
'%04d-%02d',
$value['year'],
$value['month']
'%s/%s',
$value['month'],
$value['year']
);
break;
@ -73,12 +74,23 @@ class CustomField extends Model {
$value = '';
} else {
$value = sprintf(
'%02d',
'%s',
$value['month']
);
}
break;
case 'day':
if((int)$value['day'] === 0) {
$value = '';
} else {
$value = sprintf(
'%s',
$value['day']
);
}
break;
case 'year':
if((int)$value['year'] === 0) {
$value = '';
@ -90,6 +102,10 @@ class CustomField extends Model {
}
break;
}
if(!empty($value)) {
$value = Date::convertDateToDatetime($value, $date_format);
}
}
return $value;

View File

@ -5,6 +5,7 @@ if(!defined('ABSPATH')) exit;
class Model extends \Sudzy\ValidModel {
protected $_errors;
protected $_new_record;
function __construct() {
$this->_errors = array();
@ -36,6 +37,7 @@ class Model extends \Sudzy\ValidModel {
function save() {
$this->setTimestamp();
$this->_new_record = $this->isNew();
try {
parent::save();
} catch(\Sudzy\ValidationException $e) {
@ -63,13 +65,19 @@ class Model extends \Sudzy\ValidModel {
return $this;
}
function isNew() {
return (isset($this->_new_record)) ?
$this->_new_record :
parent::isNew();
}
function trash() {
return $this->set_expr('deleted_at', 'NOW()')->save();
}
static function bulkTrash($orm) {
$model = get_called_class();
return self::bulkAction($orm, function($ids) use($model) {
$count = self::bulkAction($orm, function($ids) use($model) {
self::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at` = NOW()',
@ -78,13 +86,17 @@ class Model extends \Sudzy\ValidModel {
$ids
);
});
return array('count' => $count);
}
static function bulkDelete($orm) {
$model = get_called_class();
return self::bulkAction($orm, function($ids) use($model) {
$count = self::bulkAction($orm, function($ids) use($model) {
$model::whereIn('id', $ids)->deleteMany();
});
return array('count' => $count);
}
function restore() {
@ -93,7 +105,7 @@ class Model extends \Sudzy\ValidModel {
static function bulkRestore($orm) {
$model = get_called_class();
return self::bulkAction($orm, function($ids) use($model) {
$count = self::bulkAction($orm, function($ids) use($model) {
self::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at` = NULL',
@ -102,6 +114,8 @@ class Model extends \Sudzy\ValidModel {
$ids
);
});
return array('count' => $count);
}
static function bulkAction($orm, $callback = false) {
@ -137,14 +151,11 @@ class Model extends \Sudzy\ValidModel {
$duplicate->set_expr('updated_at', 'NOW()');
$duplicate->set_expr('deleted_at', 'NULL');
if($duplicate->save()) {
return $duplicate;
} else {
return false;
}
$duplicate->save();
return $duplicate;
}
private function setTimestamp() {
function setTimestamp() {
if($this->created_at === null) {
$this->set_expr('created_at', 'NOW()');
}
@ -157,4 +168,4 @@ class Model extends \Sudzy\ValidModel {
static function getTrashed() {
return static::whereNotNull('deleted_at');
}
}
}

View File

@ -5,4 +5,9 @@ if(!defined('ABSPATH')) exit;
class NewsletterLink extends Model {
public static $_table = MP_NEWSLETTER_LINKS_TABLE;
static function getByHash($hash) {
return parent::where('hash', $hash)
->findOne();
}
}

View File

@ -212,7 +212,7 @@ class Segment extends Model {
}
static function bulkTrash($orm) {
return parent::bulkAction($orm, function($ids) {
$count = parent::bulkAction($orm, function($ids) {
parent::rawExecute(join(' ', array(
'UPDATE `'.self::$_table.'`',
'SET `deleted_at` = NOW()',
@ -220,14 +220,18 @@ class Segment extends Model {
'AND `type` = "default"'
)), $ids);
});
return array('count' => $count);
}
static function bulkDelete($orm) {
return parent::bulkAction($orm, function($ids) {
$count = parent::bulkAction($orm, function($ids) {
// delete segments (only default)
Segment::whereIn('id', $ids)
->where('type', 'default')
->deleteMany();
});
return array('count' => $count);
}
}

View File

@ -59,6 +59,15 @@ class SendingQueue extends Model {
return $subscribers;
}
function getRenderedNewsletterBody() {
return json_decode($this->newsletter_rendered_body, true);
}
function isSubscriberProcessed($subscriber_id) {
$subscribers = $this->getSubscribers();
return in_array($subscriber_id, $subscribers['processed']);
}
function asArray() {
$model = parent::asArray();
$model['subscribers'] = (is_serialized($this->subscribers))

View File

@ -50,6 +50,9 @@ class Setting extends Model {
),
'tracking' => array(
'enabled' => true
),
'analytics' => array(
'enabled' => false,
)
);
}
@ -167,4 +170,4 @@ class Setting extends Model {
$value = self::where('name', $value)->findOne();
return ($value) ? $value->delete() : false;
}
}
}

View File

@ -5,4 +5,23 @@ if(!defined('ABSPATH')) exit;
class StatisticsClicks extends Model {
public static $_table = MP_STATISTICS_CLICKS_TABLE;
static function createOrUpdateClickCount($link_id, $subscriber_id, $newsletter_id, $queue_id) {
$statistics = self::where('link_id', $link_id)
->where('subscriber_id', $subscriber_id)
->where('newsletter_id', $newsletter_id)
->where('queue_id', $queue_id)
->findOne();
if(!$statistics) {
$statistics = self::create();
$statistics->link_id = $link_id;
$statistics->subscriber_id = $subscriber_id;
$statistics->newsletter_id = $newsletter_id;
$statistics->queue_id = $queue_id;
$statistics->count = 1;
} else {
$statistics->count++;
}
return $statistics->save();
}
}

View File

@ -5,4 +5,19 @@ if(!defined('ABSPATH')) exit;
class StatisticsOpens extends Model {
public static $_table = MP_STATISTICS_OPENS_TABLE;
static function getOrCreate($subscriber_id, $newsletter_id, $queue_id) {
$statistics = self::where('subscriber_id', $subscriber_id)
->where('newsletter_id', $newsletter_id)
->where('queue_id', $queue_id)
->findOne();
if(!$statistics) {
$statistics = self::create();
$statistics->subscriber_id = $subscriber_id;
$statistics->newsletter_id = $newsletter_id;
$statistics->queue_id = $queue_id;
$statistics->save();
}
return $statistics;
}
}

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