Compare commits
346 Commits
Author | SHA1 | Date | |
---|---|---|---|
a1441dfde6 | |||
8e7336d352 | |||
e0e2933cdf | |||
c93ec629ea | |||
d613df6558 | |||
01c9096543 | |||
f120e839dd | |||
f0ab592c04 | |||
1a269d28b3 | |||
8d4a666bf0 | |||
4a96e483a6 | |||
263a66407f | |||
c4b728f4e1 | |||
9970ad7fb6 | |||
eb380499d9 | |||
4b528549f5 | |||
a903017bc2 | |||
afd25e9174 | |||
b1bbf1b3bc | |||
9e84e8df93 | |||
df775b5a07 | |||
c9c22b9b52 | |||
93d20688ea | |||
9ebcddfa2a | |||
85995bc8a6 | |||
a8f2959bc6 | |||
36242bd580 | |||
1e7dbc8449 | |||
12c159c627 | |||
82ed7e51c5 | |||
0b3aa0d12a | |||
4d788f69aa | |||
c721843c12 | |||
d2be407ccb | |||
e6337216cf | |||
f1c396f0b0 | |||
3622bc9fcb | |||
14fe333678 | |||
cf6466197a | |||
f56bee76f2 | |||
2ec6bc8c99 | |||
4aeccb1961 | |||
bd593b1ad4 | |||
bb7812bd5d | |||
73ed070a34 | |||
9e81c48bf8 | |||
f9028d28c0 | |||
da32b243ea | |||
5092c3d328 | |||
06ad4488bf | |||
a856800e6d | |||
dfc680f3a1 | |||
a9baecc504 | |||
11e15659ac | |||
ebe440a272 | |||
853794d459 | |||
cfea13bf81 | |||
7f291d80b9 | |||
9840b55de6 | |||
ca7322933f | |||
81569e5b81 | |||
1942972282 | |||
a23aac370c | |||
99d6f74d1b | |||
a883e1176c | |||
24b98a1154 | |||
8dbb6ab79f | |||
3e7d1690bd | |||
07d533a810 | |||
499936e3ab | |||
580ac989aa | |||
acf300160d | |||
983df4ee13 | |||
03c782d4ab | |||
0daf7e12c1 | |||
6a2e18a0e1 | |||
316d5ab183 | |||
eb6bba5961 | |||
b5864adf06 | |||
9bce50a633 | |||
365a53cf27 | |||
31a4575d43 | |||
1ae584c4e7 | |||
aac2cd6eb8 | |||
9874e1c371 | |||
16b1c0dc41 | |||
9223fbf478 | |||
eb27de36f4 | |||
636fa38ab6 | |||
9b1503dc7a | |||
2ac3b00af6 | |||
5fcdbfe826 | |||
67036ddb61 | |||
6c0f6a07cd | |||
8139a7dd0a | |||
97adfc14c0 | |||
4ed703a351 | |||
2aee853406 | |||
854736fac7 | |||
841c69af59 | |||
56b4688f93 | |||
e60bc7c387 | |||
6094a83f4b | |||
91ddb98f56 | |||
27d5972306 | |||
6088497433 | |||
0d894a6fef | |||
57f0b88299 | |||
5121dbe0c8 | |||
f874ffc19c | |||
e928a5c2bc | |||
d11badf3ce | |||
3006c982cb | |||
b29e31fdd6 | |||
bc42c8e280 | |||
409697ee64 | |||
cfb4265971 | |||
13d78aac05 | |||
6f176f4e6c | |||
9b584296a5 | |||
5ea87c5eed | |||
7522084ccb | |||
214aa60d0e | |||
7a049ce1b7 | |||
94d293deb7 | |||
bd2d38d757 | |||
abec524daa | |||
cac6beb4ac | |||
cac995e15b | |||
b26380fd10 | |||
6c6a4070be | |||
8b001d820b | |||
2ae3d8ebdf | |||
459ec21f9d | |||
9c7790d07e | |||
f9c5b99e46 | |||
95b0b39366 | |||
4b6fa0e760 | |||
67a3440ced | |||
0de372344a | |||
7a04eeb650 | |||
8dbfe82922 | |||
7322f2151c | |||
c43d2f240d | |||
bbcd267b6f | |||
bbc4acb2a4 | |||
c89cc5a919 | |||
33075940de | |||
51c09b8360 | |||
2fbf85f371 | |||
24cb614adb | |||
55f851208b | |||
990dac7727 | |||
233020ca20 | |||
0c419cde16 | |||
35f9530d8e | |||
992fe2a6e9 | |||
c2cb88f995 | |||
ba69d659ab | |||
397d988eb1 | |||
d85f2341ec | |||
2b3c288b5f | |||
8ec28a23a7 | |||
12c9623e2f | |||
8ba9fdccbc | |||
a2ef62302f | |||
24ecc879d3 | |||
a1104a7f90 | |||
aa959810e9 | |||
45b7a79277 | |||
41c8c0dae5 | |||
2aeab7aaff | |||
4fd0c4b484 | |||
d4623cf763 | |||
181c4fed08 | |||
7884dd8389 | |||
b577d33414 | |||
70de0a01bf | |||
3b7f77d9af | |||
21847ca875 | |||
6153316047 | |||
32f8f07602 | |||
70a04d9bf6 | |||
bb1cc997cc | |||
24f96d9d7d | |||
46c7332da2 | |||
2f42f643ab | |||
63c87f3746 | |||
d4d575cda4 | |||
2cf03ec0a3 | |||
72ad98a77f | |||
b5094f568c | |||
7d224274fc | |||
f3b9f7be92 | |||
6e74f82ace | |||
831eb6af44 | |||
1e8e5aecee | |||
c0f98c9ba6 | |||
746c19d6ed | |||
894a9e8c90 | |||
8fea917337 | |||
c60425afb2 | |||
0776e9ad73 | |||
91981cc324 | |||
1906fafacb | |||
c11d95b402 | |||
b4c8fe6f45 | |||
d0e770e0fc | |||
3d6d1a4282 | |||
dc3b47db00 | |||
900d6694e2 | |||
e8074a61a5 | |||
64501a914a | |||
de70e855ad | |||
03e3b5a94b | |||
3331bed31c | |||
8c5a33a0fe | |||
01eb6c7a98 | |||
f502e0b677 | |||
a6b64a1c5d | |||
5019131b21 | |||
da483fb88f | |||
788bed4622 | |||
3fbe5423d0 | |||
8357295be2 | |||
8072b162d4 | |||
3f2f0ec1a9 | |||
5a5a777b7d | |||
6cac7f3652 | |||
6a9313107c | |||
72c9d301b7 | |||
ad925de801 | |||
1da28b7299 | |||
e837ad7014 | |||
daec56191f | |||
7bd25660df | |||
3b9821fbe1 | |||
cabe2d61b7 | |||
a6d802e2fa | |||
1732c4f634 | |||
bb77134224 | |||
f1cb64b240 | |||
3689545589 | |||
9b67c56281 | |||
dc38b19667 | |||
a574733217 | |||
b90aaa629e | |||
8de186c0e6 | |||
e3719967f9 | |||
138a631ed7 | |||
07b7636a72 | |||
a63ce3cdac | |||
f5c7bb87af | |||
2c8d925971 | |||
0c5beb2511 | |||
9c0316a87d | |||
46c1b682fa | |||
7954346a3f | |||
d87ff67f50 | |||
6642bb3bfa | |||
2cb32e7a78 | |||
fcea9adbd9 | |||
bbdd0dbb6e | |||
1b2cf7bd16 | |||
b7cfa549d5 | |||
ffc1d0a61c | |||
d1b160def7 | |||
493fd01754 | |||
9ced4b1757 | |||
17010e5ba9 | |||
42ad7584d4 | |||
dbc0f9b238 | |||
e62e9a5892 | |||
bc25fa61b4 | |||
2590967183 | |||
86eafd3c17 | |||
90a6f160c2 | |||
c774aec6a2 | |||
8f2fd1d76e | |||
4df11163a1 | |||
82a736ffbb | |||
87052986e8 | |||
0c73c0fadc | |||
5c7e11076d | |||
d1df94c759 | |||
53cc39c6f5 | |||
4955c72ee1 | |||
16661af8c3 | |||
bc80f69e41 | |||
0192934e65 | |||
2793e74858 | |||
5996696cc9 | |||
7f6cf5bbf3 | |||
c0ef2254cd | |||
0dbe04c3f8 | |||
ef1805d9b5 | |||
514f539e83 | |||
50f072705e | |||
f8f7bc3d3d | |||
f1bf2bb097 | |||
bbe2f69a7f | |||
c844488b0b | |||
112fe0cd6e | |||
c9e6dce785 | |||
d1c09c015a | |||
8d61866b77 | |||
0831c748b1 | |||
b2a0bc3860 | |||
1f99345e7b | |||
89782bc94b | |||
155ff09280 | |||
132e6d3342 | |||
f02699158f | |||
be3462925d | |||
3d82230d10 | |||
c20c46fd86 | |||
6e63c72aa5 | |||
e059eec5ea | |||
a2d38c9076 | |||
8d507b2ee0 | |||
5e2979c283 | |||
84ec0de3cd | |||
ee07780833 | |||
ed104156a9 | |||
90f2e9ff9d | |||
98fb7aa65e | |||
a51eb59cf8 | |||
270023b89c | |||
7d77e075e9 | |||
9bbe36b3cb | |||
7a904ed093 | |||
e3c065b353 | |||
4ca2872e0e | |||
ce338f7fe2 | |||
fa28b0a955 | |||
a298650187 | |||
95772ef68a | |||
e1c94db516 | |||
7be1a11d1e | |||
c04b95c09a | |||
cdfeb8d512 | |||
3ef8067968 | |||
9fb04bc3c0 | |||
25727ea0ba | |||
4c8ac369b7 | |||
268dabdc9f |
16
.env.sample
16
.env.sample
@ -1,2 +1,16 @@
|
||||
WP_TEST_PATH="/var/www/wordpress"
|
||||
|
||||
WP_TEST_ENABLE_NETWORK_TESTS="true"
|
||||
WP_TEST_IMPORT_MAILCHIMP_API=""
|
||||
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
|
||||
WP_TEST_MAILER_ENABLE_SENDING="true"
|
||||
WP_TEST_MAILER_AMAZON_ACCESS=""
|
||||
WP_TEST_MAILER_AMAZON_SECRET=""
|
||||
WP_TEST_MAILER_AMAZON_REGION=""
|
||||
WP_TEST_MAILER_ELASTICEMAIL_API=""
|
||||
WP_TEST_MAILER_MAILGUN_API=""
|
||||
WP_TEST_MAILER_MAILGUN_DOMAIN=""
|
||||
WP_TEST_MAILER_MAILPOET_API=""
|
||||
WP_TEST_MAILER_SENDGRID_API=""
|
||||
WP_TEST_MAILER_SMTP_HOST=""
|
||||
WP_TEST_MAILER_SMTP_LOGIN=""
|
||||
WP_TEST_MAILER_SMTP_PASSWORD=""
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
.DS_Store
|
||||
TODO
|
||||
composer.phar
|
||||
vendor
|
||||
/vendor
|
||||
tests/_output/*
|
||||
tests/acceptance.suite.yml
|
||||
tests/_support/_generated/*
|
||||
|
11
README.md
11
README.md
@ -8,7 +8,6 @@ MailPoet done the right way.
|
||||
```
|
||||
php
|
||||
nodejs
|
||||
phantomjs
|
||||
wordpress
|
||||
```
|
||||
|
||||
@ -47,16 +46,6 @@ $ ./do compile:all
|
||||
$ ./do test:unit
|
||||
```
|
||||
|
||||
- Acceptance tests:
|
||||
```sh
|
||||
$ ./do test:acceptance
|
||||
```
|
||||
|
||||
- Run all tests:
|
||||
```sh
|
||||
$ ./do test:all
|
||||
```
|
||||
|
||||
- Debug tests:
|
||||
```sh
|
||||
$ ./do test:debug
|
||||
|
13
RoboFile.php
13
RoboFile.php
@ -105,7 +105,18 @@ class RoboFile extends \Robo\Tasks {
|
||||
function testUnit($file = null) {
|
||||
$this->loadEnv();
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
$this->_exec('vendor/bin/codecept run unit '.(($file) ? $file : ''));
|
||||
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
|
||||
}
|
||||
|
||||
function testCoverage($file = null) {
|
||||
$this->loadEnv();
|
||||
$this->_exec('vendor/bin/codecept build');
|
||||
$this->_exec(join(' ', array(
|
||||
'vendor/bin/codecept run',
|
||||
(($file) ? $file : ''),
|
||||
'--coverage',
|
||||
'--coverage-html'
|
||||
)));
|
||||
}
|
||||
|
||||
function testJavascript() {
|
||||
|
@ -5,13 +5,17 @@
|
||||
@require 'common'
|
||||
@require 'modal'
|
||||
@require 'notice'
|
||||
@require 'parsley'
|
||||
|
||||
@require 'form_editor'
|
||||
@require 'listing'
|
||||
@require 'box'
|
||||
@require 'breadcrumb'
|
||||
|
||||
@require 'form'
|
||||
@require 'parsley'
|
||||
@require 'form_validation'
|
||||
|
||||
@require 'settings'
|
||||
@require 'progress_bar'
|
||||
|
||||
@require 'subscribers'
|
File diff suppressed because one or more lines are too long
@ -19,6 +19,8 @@ a:focus
|
||||
|
||||
// select 2
|
||||
.select2-container
|
||||
width: 25em !important
|
||||
|
||||
// textareas
|
||||
textarea.regular-text
|
||||
width: 25em !important
|
||||
|
@ -4,6 +4,9 @@
|
||||
icons = '../img/form_editor_icons.png'
|
||||
handle_icon = '../img/handle.png'
|
||||
|
||||
#mailpoet_form_name
|
||||
font-size: 23px
|
||||
|
||||
#mailpoet_form_history
|
||||
display: none
|
||||
|
||||
@ -99,6 +102,7 @@ handle_icon = '../img/handle.png'
|
||||
/* MailPoet Form wrapper */
|
||||
#mailpoet_form_wrapper
|
||||
position: relative
|
||||
margin: 20px 0 0 0
|
||||
|
||||
/* MailPoet Form container */
|
||||
#mailpoet_form_container
|
||||
@ -121,6 +125,7 @@ handle_icon = '../img/handle.png'
|
||||
float: none
|
||||
|
||||
#mailpoet_form_toolbar
|
||||
z-index: 999
|
||||
position: absolute
|
||||
width: 400px
|
||||
|
||||
|
6
assets/css/src/form_validation.styl
Normal file
6
assets/css/src/form_validation.styl
Normal file
@ -0,0 +1,6 @@
|
||||
.parsley-errors-list
|
||||
margin-top: 8px
|
||||
|
||||
.parsley-required
|
||||
.parsley-custom-error-message
|
||||
color: #b94a48
|
@ -90,7 +90,7 @@ body.mailpoet_modal_opened
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 100%
|
||||
transition: margin 0.3s ease-out
|
||||
transition: margin 350ms ease-out
|
||||
|
||||
.mailpoet_panel_wrapper
|
||||
background-color: #f1f1f1
|
||||
|
@ -7,7 +7,6 @@ $tool-active-secondary-color = #ffffff
|
||||
|
||||
$tool-width = 20px
|
||||
$master-column-tool-width = 24px
|
||||
$layer-selector-width = 30px
|
||||
|
||||
.mailpoet_tools
|
||||
position: absolute
|
||||
@ -33,10 +32,35 @@ $layer-selector-width = 30px
|
||||
width: $master-column-tool-width
|
||||
height: $master-column-tool-width
|
||||
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
max-width: 100%
|
||||
max-height: $master-column-tool-width
|
||||
opacity: 1
|
||||
display: block
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
max-width: 100%
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
overflow: hidden
|
||||
display: block
|
||||
|
||||
.mailpoet_delete_block_activated
|
||||
width: auto
|
||||
height: auto
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
overflow: hidden
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
|
||||
.mailpoet_delete_block_confirm,
|
||||
.mailpoet_delete_block_cancel
|
||||
max-height: $master-column-tool-width*2
|
||||
opacity: 1
|
||||
|
||||
.mailpoet_tool
|
||||
display: inline-block
|
||||
width: $tool-width
|
||||
@ -82,7 +106,7 @@ $layer-selector-width = 30px
|
||||
padding: 0
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
max-width: 100%
|
||||
max-width: $tool-width
|
||||
display: inline-block
|
||||
opacity: 1
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
@ -96,12 +120,12 @@ $layer-selector-width = 30px
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
|
||||
.mailpoet_delete_block_activated
|
||||
height: auto
|
||||
width: auto
|
||||
border-radius(3px)
|
||||
background-color: $warning-background-color
|
||||
padding: 3px 5px
|
||||
line-height: 1.2em
|
||||
height: auto
|
||||
|
||||
.mailpoet_delete_block_activate
|
||||
overflow: hidden
|
||||
@ -113,6 +137,9 @@ $layer-selector-width = 30px
|
||||
max-width: 100%
|
||||
opacity: 1
|
||||
|
||||
.mailpoet_delete_block_cancel
|
||||
margin-left: 3px
|
||||
|
||||
.mailpoet_delete_block_confirm
|
||||
color: $warning-text-color
|
||||
|
||||
|
@ -52,7 +52,7 @@ $draggable-widget-z-index = 2
|
||||
padding: 0
|
||||
margin: 0
|
||||
z-index: $draggable-widget-z-index
|
||||
animation-fade-in-and-scale-up()
|
||||
animation-fade-in()
|
||||
|
||||
.mailpoet_widget_icon
|
||||
padding: 0
|
||||
|
@ -37,3 +37,6 @@
|
||||
|
||||
input[type=text]
|
||||
vertical-align: middle
|
||||
|
||||
.mailpoet_form_field_block
|
||||
display: block
|
||||
|
@ -38,9 +38,7 @@
|
||||
content: '\f142'
|
||||
|
||||
.mailpoet_save_show_options_icon
|
||||
width: auto
|
||||
height: auto
|
||||
line-height: auto
|
||||
vertical-align: middle
|
||||
|
||||
&::before
|
||||
content: '\f140'
|
||||
@ -48,7 +46,7 @@
|
||||
.mailpoet_save_as_template_container,
|
||||
.mailpoet_export_template_container
|
||||
border-radius(3px)
|
||||
float: left
|
||||
display: inline-block
|
||||
clear: both
|
||||
|
||||
margin-top: 5px
|
||||
|
@ -26,13 +26,9 @@ $widget-icon-width = 30px
|
||||
border-right: 0
|
||||
|
||||
&.closed .mailpoet_region_content
|
||||
max-height: 0px
|
||||
overflow: hidden
|
||||
margin-top: 0
|
||||
display: none
|
||||
|
||||
.mailpoet_region_content
|
||||
max-height: 2000px
|
||||
transition: max-height 300ms ease
|
||||
padding: 0 20px
|
||||
margin-top: 12px
|
||||
|
||||
|
@ -21,3 +21,9 @@
|
||||
|
||||
.mailpoet_automated_latest_content_display_options
|
||||
animation-slide-open-downwards()
|
||||
|
||||
.mailpoet_automated_latest_content_show_amount
|
||||
width: 25px
|
||||
|
||||
.mailpoet_automated_latest_content_content_type
|
||||
width: 180px
|
||||
|
@ -30,8 +30,8 @@ $block-hover-highlight-color = $primary-active-color
|
||||
|
||||
.mailpoet_content
|
||||
position: relative
|
||||
line-height: 1.61803398875
|
||||
|
||||
.mailpoet_block_transition_in
|
||||
animation-fade-in-and-scale-up()
|
||||
.mailpoet_block_transition_out
|
||||
animation-fade-out-and-scale-down()
|
||||
p, h1, h2, h3, h4, h5, h6
|
||||
line-height: 1.61803398875
|
||||
font-style: normal
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_footer_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
p
|
||||
margin: 0
|
||||
|
@ -1,6 +1,10 @@
|
||||
.mailpoet_header_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
.mailpoet_content
|
||||
padding: 5px 20px
|
||||
padding: 10px 20px
|
||||
|
||||
p
|
||||
margin: 0
|
||||
|
@ -11,9 +11,6 @@
|
||||
padding-right: 0
|
||||
margin-bottom: 0
|
||||
|
||||
img
|
||||
width: 100%
|
||||
|
||||
.mailpoet_content a:hover
|
||||
cursor: all-scroll
|
||||
|
||||
|
@ -1,16 +1,23 @@
|
||||
$text-vertical-padding = 3px
|
||||
|
||||
.mailpoet_text_block
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
& > .mailpoet_content
|
||||
overflow: hidden
|
||||
padding-top: 13px
|
||||
padding-bottom: 13px
|
||||
padding-top: 0
|
||||
padding-bottom: 0px
|
||||
padding-left: 20px
|
||||
padding-right: 20px
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
padding: 0
|
||||
margin: 0
|
||||
font-weight: normal
|
||||
|
||||
p
|
||||
margin-top: 0
|
||||
font-weight: normal
|
||||
|
||||
blockquote
|
||||
margin: 1em
|
||||
padding-left: 1em
|
||||
|
@ -129,3 +129,21 @@ body
|
||||
|
||||
#mailpoet_modal_close
|
||||
display: none
|
||||
|
||||
.wrap > .mailpoet_notice,
|
||||
.update-nag
|
||||
margin-left: 2px + 15px !important
|
||||
|
||||
/* Make a button group */
|
||||
.mailpoet_button_group
|
||||
|
||||
.button:first-child
|
||||
border-right: 0
|
||||
border-top-right-radius: 0
|
||||
border-bottom-right-radius: 0
|
||||
|
||||
.button:last-child
|
||||
border-left: 0
|
||||
border-top-left-radius: 0
|
||||
border-bottom-left-radius: 0
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
animation-slide-open-downwards()
|
||||
transition: all 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
max-height: 2000px
|
||||
opacity: 1
|
||||
|
||||
@ -9,45 +9,23 @@ animation-slide-open-downwards()
|
||||
overflow-y: hidden
|
||||
|
||||
animation-background-color()
|
||||
transition: background 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
transition: background 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
|
||||
animation-fade-in-and-scale-up()
|
||||
animation-name: fadeInAndScaleUp
|
||||
animation-duration: 500ms
|
||||
animation-fill-mode: forwards
|
||||
|
||||
animation-fade-out-and-scale-down()
|
||||
animation-name: fadeOutAndScaleDown
|
||||
animation-duration: 500ms
|
||||
animation-fade-in()
|
||||
animation-name: fadeIn
|
||||
animation-duration: 300ms
|
||||
animation-fill-mode: forwards
|
||||
animation-timing-function: ease-in
|
||||
|
||||
animation-fade-in-and-scale-horizontally()
|
||||
transition: all 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
|
||||
|
||||
@keyframes fadeInAndScaleUp {
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0.3
|
||||
max-height: 0
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1
|
||||
max-height: 5000px
|
||||
overflow: hidden
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutAndScaleDown {
|
||||
0% {
|
||||
opacity: 1
|
||||
max-height: 5000px
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.3
|
||||
max-height: 0
|
||||
overflow: hidden
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,14 @@
|
||||
@import 'nib'
|
||||
|
||||
@require 'parsley'
|
||||
@require 'form_validation'
|
||||
|
||||
/* labels */
|
||||
.mailpoet_text_label
|
||||
.mailpoet_textarea_label
|
||||
.mailpoet_select_label
|
||||
.mailpoet_radio_label
|
||||
.mailpoet_checkbox_label
|
||||
.mailpoet_list_label
|
||||
.mailpoet_date_label
|
||||
display:block
|
||||
|
3
assets/css/src/subscribers.styl
Normal file
3
assets/css/src/subscribers.styl
Normal file
@ -0,0 +1,3 @@
|
||||
#subscribers_container
|
||||
.mailpoet_segments_unsubscribed
|
||||
color: lighten(#555, 33)
|
BIN
assets/img/post_notifications_template/ALC-widget-icon.png
Executable file
BIN
assets/img/post_notifications_template/ALC-widget-icon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/img/post_notifications_template/settings-icon-highlight.png
Executable file
BIN
assets/img/post_notifications_template/settings-icon-highlight.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/img/welcome_template/logo-header.gif
Executable file
BIN
assets/img/welcome_template/logo-header.gif
Executable file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@ -1,98 +1,106 @@
|
||||
define(
|
||||
[
|
||||
'react',
|
||||
'react-dom',
|
||||
'mailpoet'
|
||||
],
|
||||
function (
|
||||
React,
|
||||
ReactDOM,
|
||||
MailPoet
|
||||
) {
|
||||
var CronControl = React.createClass({
|
||||
getInitialState: function () {
|
||||
return (cronDaemon) ? cronDaemon : null;
|
||||
},
|
||||
getDaemonData: function () {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'cron',
|
||||
action: 'getDaemonStatus'
|
||||
}).done(function (response) {
|
||||
jQuery('.button-primary').removeClass('disabled');
|
||||
if (!response) {
|
||||
this.replaceState();
|
||||
} else {
|
||||
this.setState(response);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
componentDidMount: function componentDidMount() {
|
||||
if (this.isMounted()) {
|
||||
this.getDaemonData;
|
||||
setInterval(this.getDaemonData, 5000);
|
||||
}
|
||||
},
|
||||
controlDaemon: function (action) {
|
||||
jQuery('.button-primary').addClass('disabled');
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'cron',
|
||||
action: 'controlDaemon',
|
||||
data: {'action': action}
|
||||
}).done(function (response) {
|
||||
if (!response) {
|
||||
this.replaceState();
|
||||
} else {
|
||||
this.setState(response);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
render: function () {
|
||||
if (!this.state) {
|
||||
return
|
||||
<div>
|
||||
Woops, daemon is not running ;\
|
||||
</div>
|
||||
}
|
||||
switch (this.state.status) {
|
||||
case 'started':
|
||||
return (
|
||||
[
|
||||
'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(function(response) {
|
||||
jQuery('.button-primary')
|
||||
.removeClass('disabled');
|
||||
if(response.status !== undefined) {
|
||||
this.setState(response);
|
||||
} else {
|
||||
this.replaceState();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.isMounted()) {
|
||||
this.getCronData();
|
||||
setInterval(this.getCronData, 5000);
|
||||
}
|
||||
},
|
||||
controlCron: function(action) {
|
||||
if(jQuery('.button-primary').hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
jQuery('.button-primary')
|
||||
.addClass('disabled');
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'cron',
|
||||
action: action,
|
||||
})
|
||||
.done(function(response) {
|
||||
if(!response.result) {
|
||||
MailPoet.Notice.error(MailPoetI18n.daemonControlError);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
render: function() {
|
||||
if(this.state.status === 'loading') {
|
||||
return(<div>Loading daemon status...</div>);
|
||||
}
|
||||
switch(this.state.status) {
|
||||
case 'started':
|
||||
return(
|
||||
<div>
|
||||
<div>
|
||||
Cron daemon is running.
|
||||
<br/>
|
||||
<br/>
|
||||
It was started
|
||||
<strong> {this.state.timeSinceStart} </strong> and last executed
|
||||
<strong> {this.state.timeSinceUpdate} </strong> for a total of
|
||||
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
|
||||
<br />
|
||||
<br />
|
||||
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'stop')}>Stop</a>
|
||||
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'pause')}>Pause</a>
|
||||
</div>
|
||||
Cron daemon is running.
|
||||
<br/>
|
||||
<br/>
|
||||
It was started
|
||||
<strong> {this.state.timeSinceStart} </strong> and last executed
|
||||
<strong> {this.state.timeSinceUpdate} </strong> for a total of
|
||||
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
|
||||
<br />
|
||||
<br />
|
||||
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'paused':
|
||||
case 'stopped':
|
||||
return (
|
||||
);
|
||||
break;
|
||||
case 'starting':
|
||||
case 'stopping':
|
||||
return(
|
||||
<div>
|
||||
Daemon is {this.state.status}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 'stopped':
|
||||
return(
|
||||
<div>
|
||||
Daemon is {this.state.status}
|
||||
<br />
|
||||
<br />
|
||||
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'start')}>Start</a>
|
||||
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a>
|
||||
</div>
|
||||
)
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
let container = document.getElementById('cron_container');
|
||||
if (container) {
|
||||
ReactDOM.render(
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const container = document.getElementById('cron_container');
|
||||
|
||||
if(container) {
|
||||
ReactDOM.render(
|
||||
<CronControl />,
|
||||
document.getElementById('cron_status')
|
||||
)
|
||||
}
|
||||
}
|
||||
);
|
||||
container
|
||||
);
|
||||
}
|
||||
});
|
136
assets/js/src/date.js
Normal file
136
assets/js/src/date.js
Normal file
@ -0,0 +1,136 @@
|
||||
define('date',
|
||||
[
|
||||
'mailpoet',
|
||||
'jquery',
|
||||
'moment'
|
||||
], function(
|
||||
MailPoet,
|
||||
jQuery,
|
||||
Moment
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
MailPoet.Date = {
|
||||
version: 0.1,
|
||||
options: {},
|
||||
defaults: {
|
||||
offset: 0,
|
||||
format: 'F, d Y H:i:s'
|
||||
},
|
||||
init: function(options) {
|
||||
options = options || {};
|
||||
|
||||
// set UTC offset
|
||||
if (
|
||||
options.offset === undefined
|
||||
&& window.mailpoet_date_offset !== undefined
|
||||
) {
|
||||
options.offset = window.mailpoet_date_offset;
|
||||
}
|
||||
// set date format
|
||||
if (
|
||||
options.format === undefined
|
||||
&& window.mailpoet_date_format !== undefined
|
||||
) {
|
||||
options.format = window.mailpoet_date_format;
|
||||
}
|
||||
// merge options
|
||||
this.options = jQuery.extend({}, this.defaults, options);
|
||||
|
||||
return this;
|
||||
},
|
||||
format: function(date, options) {
|
||||
this.init(options);
|
||||
return Moment.utc(date)
|
||||
.local()
|
||||
.format(this.convertFormat(this.options.format));
|
||||
},
|
||||
short: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y'
|
||||
});
|
||||
},
|
||||
full: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'F, j Y H:i:s'
|
||||
});
|
||||
},
|
||||
time: function(date) {
|
||||
return this.format(date, {
|
||||
format: 'H:i:s'
|
||||
});
|
||||
},
|
||||
convertFormat: function(format) {
|
||||
const format_mappings = {
|
||||
date: {
|
||||
D: 'ddd',
|
||||
l: 'dddd',
|
||||
d: 'DD',
|
||||
j: 'D',
|
||||
z: 'DDDD',
|
||||
N: 'E',
|
||||
S: '',
|
||||
M: 'MMM',
|
||||
F: 'MMMM',
|
||||
m: 'MM',
|
||||
n: '',
|
||||
t: '',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
H: 'HH',
|
||||
h: 'hh',
|
||||
g: 'h',
|
||||
A: 'A',
|
||||
i: 'mm',
|
||||
s: 'ss',
|
||||
T: 'z',
|
||||
O: 'ZZ',
|
||||
w: 'd',
|
||||
W: 'WW'
|
||||
},
|
||||
strftime: {
|
||||
a: 'ddd',
|
||||
A: 'dddd',
|
||||
b: 'MMM',
|
||||
B: 'MMMM',
|
||||
d: 'DD',
|
||||
e: 'D',
|
||||
F: 'YYYY-MM-DD',
|
||||
H: 'HH',
|
||||
I: 'hh',
|
||||
j: 'DDDD',
|
||||
k: 'H',
|
||||
l: 'h',
|
||||
m: 'MM',
|
||||
M: 'mm',
|
||||
p: 'A',
|
||||
S: 'ss',
|
||||
u: 'E',
|
||||
w: 'd',
|
||||
W: 'WW',
|
||||
y: 'YY',
|
||||
Y: 'YYYY',
|
||||
z: 'ZZ',
|
||||
Z: 'z'
|
||||
}
|
||||
};
|
||||
|
||||
const replacements = format_mappings['date'];
|
||||
|
||||
let outputFormat = '';
|
||||
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (format.indexOf(key) !== -1) {
|
||||
format = format.replace(key, '%'+key);
|
||||
}
|
||||
});
|
||||
outputFormat = format;
|
||||
Object.keys(replacements).forEach(function(key) {
|
||||
if (outputFormat.indexOf('%'+key) !== -1) {
|
||||
outputFormat = outputFormat.replace('%'+key, replacements[key]);
|
||||
}
|
||||
});
|
||||
return outputFormat;
|
||||
}
|
||||
};
|
||||
});
|
@ -1,62 +1,45 @@
|
||||
define([
|
||||
'react',
|
||||
'react-checkbox-group'
|
||||
'react'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
CheckboxGroup
|
||||
React
|
||||
) {
|
||||
var FormFieldCheckbox = React.createClass({
|
||||
const FormFieldCheckbox = React.createClass({
|
||||
onValueChange: function(e) {
|
||||
e.target.value = this.refs.checkbox.checked ? '1' : '';
|
||||
return this.props.onValueChange(e);
|
||||
},
|
||||
render: function() {
|
||||
var selected_values = this.props.item[this.props.field.name] || '';
|
||||
if(
|
||||
selected_values !== undefined
|
||||
&& selected_values.constructor !== Array
|
||||
) {
|
||||
selected_values = selected_values.split(';').map(function(value) {
|
||||
return value.trim();
|
||||
});
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
var count = Object.keys(this.props.field.values).length;
|
||||
|
||||
var options = Object.keys(this.props.field.values).map(
|
||||
function(value, index) {
|
||||
const isChecked = !!(this.props.item[this.props.field.name]);
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<p key={ 'checkbox-' + index }>
|
||||
<label>
|
||||
<input type="checkbox" value={ value } />
|
||||
{ this.props.field.values[value] }
|
||||
<input
|
||||
ref="checkbox"
|
||||
type="checkbox"
|
||||
value="1"
|
||||
checked={ isChecked }
|
||||
onChange={ this.onValueChange }
|
||||
name={ this.props.field.name }
|
||||
/>
|
||||
{ this.props.field.values[value] }
|
||||
</label>
|
||||
</p>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<CheckboxGroup
|
||||
name={ this.props.field.name }
|
||||
value={ selected_values }
|
||||
ref={ this.props.field.name }
|
||||
onChange={ this.handleValueChange }>
|
||||
<div>
|
||||
{ options }
|
||||
</CheckboxGroup>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleValueChange: function() {
|
||||
var field = this.props.field.name;
|
||||
var group = this.refs[field];
|
||||
var selected_values = [];
|
||||
|
||||
if(group !== undefined) {
|
||||
selected_values = group.getCheckedValues();
|
||||
}
|
||||
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: selected_values.join(';')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
196
assets/js/src/form/fields/date.jsx
Normal file
196
assets/js/src/form/fields/date.jsx
Normal file
@ -0,0 +1,196 @@
|
||||
define([
|
||||
'react',
|
||||
'moment',
|
||||
], function(
|
||||
React,
|
||||
Moment
|
||||
) {
|
||||
class FormFieldDateYear extends React.Component {
|
||||
render() {
|
||||
const yearsRange = 100;
|
||||
const years = [];
|
||||
const currentYear = Moment().year();
|
||||
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
|
||||
years.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ i }</option>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[year]' }
|
||||
value={ this.props.year }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ years }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDateMonth extends React.Component {
|
||||
render() {
|
||||
const months = [];
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
months.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ this.props.monthNames[i - 1] }</option>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[month]' }
|
||||
value={ this.props.month }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ months }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDateDay extends React.Component {
|
||||
render() {
|
||||
const days = [];
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
days.push((
|
||||
<option
|
||||
key={ i }
|
||||
value={ i }
|
||||
>{ i }</option>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<select
|
||||
name={ this.props.name + '[day]' }
|
||||
value={ this.props.day }
|
||||
onChange={ this.props.onValueChange }
|
||||
>
|
||||
{ days }
|
||||
</select>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FormFieldDate extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
year: Moment().year(),
|
||||
month: 1,
|
||||
day: 1
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
(this.props.item !== undefined && prevProps.item !== undefined)
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
this.extractTimeStamp();
|
||||
}
|
||||
}
|
||||
extractTimeStamp() {
|
||||
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
|
||||
|
||||
this.setState({
|
||||
year: Moment.unix(timeStamp).year(),
|
||||
// Moment returns the month as [0..11]
|
||||
// We increment it to match PHP's mktime() which expects [1..12]
|
||||
month: Moment.unix(timeStamp).month() + 1,
|
||||
day: Moment.unix(timeStamp).date()
|
||||
});
|
||||
}
|
||||
updateTimeStamp(field) {
|
||||
let newTimeStamp = Moment(
|
||||
`${this.state.month}/${this.state.day}/${this.state.year}`,
|
||||
'M/D/YYYY'
|
||||
).valueOf();
|
||||
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
|
||||
// convert milliseconds to seconds
|
||||
newTimeStamp /= 1000;
|
||||
return this.props.onValueChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: newTimeStamp
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
onValueChange(e) {
|
||||
// extract property from name
|
||||
const matches = e.target.name.match(/(.*?)\[(.*?)\]/);
|
||||
let field = null;
|
||||
let property = null;
|
||||
|
||||
if (matches !== null && matches.length === 3) {
|
||||
field = matches[1];
|
||||
property = matches[2];
|
||||
|
||||
let value = parseInt(e.target.value, 10);
|
||||
|
||||
this.setState({
|
||||
[`${property}`]: value
|
||||
}, () => {
|
||||
this.updateTimeStamp(field);
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const monthNames = window.mailpoet_month_names || [];
|
||||
|
||||
const dateType = this.props.field.params.date_type;
|
||||
|
||||
const dateSelects = dateType.split('_');
|
||||
|
||||
const fields = dateSelects.map(type => {
|
||||
switch(type) {
|
||||
case 'year':
|
||||
return (<FormFieldDateYear
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'year' }
|
||||
key={ 'year' }
|
||||
name={ this.props.field.name }
|
||||
year={ this.state.year }
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
return (<FormFieldDateMonth
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'month' }
|
||||
key={ 'month' }
|
||||
name={ this.props.field.name }
|
||||
month={ this.state.month }
|
||||
monthNames={ monthNames }
|
||||
/>);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
return (<FormFieldDateDay
|
||||
onValueChange={ this.onValueChange.bind(this) }
|
||||
ref={ 'day' }
|
||||
key={ 'day' }
|
||||
name={ this.props.field.name }
|
||||
day={ this.state.day }
|
||||
/>);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{fields}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return FormFieldDate;
|
||||
});
|
@ -5,7 +5,8 @@ define([
|
||||
'form/fields/select.jsx',
|
||||
'form/fields/radio.jsx',
|
||||
'form/fields/checkbox.jsx',
|
||||
'form/fields/selection.jsx'
|
||||
'form/fields/selection.jsx',
|
||||
'form/fields/date.jsx',
|
||||
],
|
||||
function(
|
||||
React,
|
||||
@ -14,7 +15,8 @@ function(
|
||||
FormFieldSelect,
|
||||
FormFieldRadio,
|
||||
FormFieldCheckbox,
|
||||
FormFieldSelection
|
||||
FormFieldSelection,
|
||||
FormFieldDate
|
||||
) {
|
||||
var FormField = React.createClass({
|
||||
renderField: function(data, inline = false) {
|
||||
@ -55,6 +57,10 @@ function(
|
||||
case 'selection':
|
||||
field = (<FormFieldSelection {...data} />);
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
field = (<FormFieldDate {...data} />);
|
||||
break;
|
||||
}
|
||||
|
||||
if(inline === true) {
|
||||
@ -66,10 +72,10 @@ function(
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<p key={ 'field-' + (data.index || 0) }>
|
||||
<div key={ 'field-' + (data.index || 0) }>
|
||||
{ field }
|
||||
{ description }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -4,13 +4,15 @@ define([
|
||||
function(
|
||||
React
|
||||
) {
|
||||
var FormFieldRadio = React.createClass({
|
||||
const FormFieldRadio = React.createClass({
|
||||
render: function() {
|
||||
var selected_value = this.props.item[this.props.field.name];
|
||||
var count = Object.keys(this.props.field.values).length;
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var options = Object.keys(this.props.field.values).map(
|
||||
function(value, index) {
|
||||
const selected_value = this.props.item[this.props.field.name];
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<p key={ 'radio-' + index }>
|
||||
<label>
|
||||
@ -20,11 +22,11 @@ function(
|
||||
value={ value }
|
||||
onChange={ this.props.onValueChange }
|
||||
name={ this.props.field.name } />
|
||||
{ this.props.field.values[value] }
|
||||
{ this.props.field.values[value] }
|
||||
</label>
|
||||
</p>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -4,10 +4,14 @@ define([
|
||||
function(
|
||||
React
|
||||
) {
|
||||
var FormFieldSelect = React.createClass({
|
||||
const FormFieldSelect = React.createClass({
|
||||
render: function() {
|
||||
var options =
|
||||
Object.keys(this.props.field.values).map(function(value, index) {
|
||||
if (this.props.field.values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = Object.keys(this.props.field.values).map(
|
||||
(value, index) => {
|
||||
return (
|
||||
<option
|
||||
key={ 'option-' + index }
|
||||
@ -15,7 +19,7 @@ function(
|
||||
{ this.props.field.values[value] }
|
||||
</option>
|
||||
);
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -26,7 +26,7 @@ function(
|
||||
&& (this.props.item.id !== prevProps.item.id)
|
||||
) {
|
||||
jQuery('#'+this.refs.select.id)
|
||||
.val(this.props.item[this.props.field.name])
|
||||
.val(this.getSelectedValues())
|
||||
.trigger('change');
|
||||
}
|
||||
},
|
||||
@ -45,7 +45,11 @@ function(
|
||||
if(item.element && item.element.selected) {
|
||||
return null;
|
||||
} else {
|
||||
return item.text;
|
||||
if(item.title) {
|
||||
return item.title;
|
||||
} else {
|
||||
return item.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -65,15 +69,25 @@ function(
|
||||
|
||||
select2.select2(
|
||||
'val',
|
||||
this.props.item[this.props.field.name]
|
||||
this.getSelectedValues()
|
||||
);
|
||||
|
||||
this.setState({ initialized: true });
|
||||
},
|
||||
getSelectedValues: function() {
|
||||
if(this.props.field['selected'] !== undefined) {
|
||||
return this.props.field['selected'](this.props.item);
|
||||
} else if(this.props.item !== undefined && this.props.field.name !== undefined) {
|
||||
return this.props.item[this.props.field.name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
loadCachedItems: function() {
|
||||
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
|
||||
var items = window['mailpoet_'+this.props.field.endpoint];
|
||||
|
||||
|
||||
if(this.props.field['filter'] !== undefined) {
|
||||
items = items.filter(this.props.field.filter);
|
||||
}
|
||||
@ -98,31 +112,48 @@ function(
|
||||
});
|
||||
}
|
||||
},
|
||||
getLabel: function(item) {
|
||||
if(this.props.field['getLabel'] !== undefined) {
|
||||
return this.props.field.getLabel(item, this.props.item);
|
||||
}
|
||||
return item.name;
|
||||
},
|
||||
getSearchLabel: function(item) {
|
||||
if(this.props.field['getSearchLabel'] !== undefined) {
|
||||
return this.props.field.getSearchLabel(item, this.props.item);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getValue: function(item) {
|
||||
if(this.props.field['getValue'] !== undefined) {
|
||||
return this.props.field.getValue(item, this.props.item);
|
||||
}
|
||||
return item.id;
|
||||
},
|
||||
render: function() {
|
||||
var options = this.state.items.map(function(item, index) {
|
||||
const options = this.state.items.map((item, index) => {
|
||||
let label = this.getLabel(item);
|
||||
let searchLabel = this.getSearchLabel(item);
|
||||
let value = this.getValue(item);
|
||||
|
||||
return (
|
||||
<option
|
||||
key={ item.id }
|
||||
value={ item.id }
|
||||
key={ 'option-'+index }
|
||||
value={ value }
|
||||
title={ searchLabel }
|
||||
>
|
||||
{ item.name }
|
||||
{ label }
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
var default_value = (
|
||||
(this.props.item !== undefined && this.props.field.name !== undefined)
|
||||
? this.props.item[this.props.field.name]
|
||||
: null
|
||||
);
|
||||
|
||||
return (
|
||||
<select
|
||||
id={ this.props.field.id || this.props.field.name }
|
||||
ref="select"
|
||||
placeholder={ this.props.field.placeholder }
|
||||
data-placeholder={ this.props.field.placeholder }
|
||||
multiple={ this.props.field.multiple }
|
||||
defaultValue={ default_value }
|
||||
defaultValue={ this.getSelectedValues() }
|
||||
{...this.props.field.validation}
|
||||
>{ options }</select>
|
||||
);
|
||||
|
@ -48,7 +48,7 @@ define(
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'get',
|
||||
data: { id: id }
|
||||
data: id
|
||||
}).done(function(response) {
|
||||
if(response === false) {
|
||||
this.setState({
|
||||
|
@ -231,7 +231,7 @@ var WysijaHistory = {
|
||||
|
||||
/* MailPoet Form */
|
||||
var WysijaForm = {
|
||||
version: '0.6',
|
||||
version: '0.7',
|
||||
options: {
|
||||
container: 'mailpoet_form_container',
|
||||
editor: 'mailpoet_form_editor',
|
||||
@ -317,6 +317,7 @@ var WysijaForm = {
|
||||
save: function() {
|
||||
var position = 1,
|
||||
data = {
|
||||
'name': $F('mailpoet_form_name'),
|
||||
'settings': $('mailpoet_form_settings').serialize(true),
|
||||
'body': [],
|
||||
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
|
||||
@ -616,6 +617,28 @@ var WysijaForm = {
|
||||
// this is a url, so do not encode the protocol
|
||||
return encodeURI(str).replace(/[!'()*]/g, escape);
|
||||
}
|
||||
},
|
||||
updateBlock: function(field) {
|
||||
var hasUpdated = false;
|
||||
WysijaForm.getBlocks().each(function(b) {
|
||||
if(b.block.getData().id === field.id) {
|
||||
hasUpdated = true;
|
||||
b.block.redraw(field);
|
||||
}
|
||||
});
|
||||
|
||||
return hasUpdated;
|
||||
},
|
||||
removeBlock: function(field, callback) {
|
||||
var hasRemoved = false;
|
||||
WysijaForm.getBlocks().each(function(b) {
|
||||
if(b.block.getData().id === field.id) {
|
||||
hasRemoved = true;
|
||||
b.block.removeBlock(callback);
|
||||
}
|
||||
});
|
||||
|
||||
return hasRemoved;
|
||||
}
|
||||
};
|
||||
|
||||
@ -824,10 +847,6 @@ WysijaForm.Block = Class.create({
|
||||
Effect.Fade(this.element.identify(), {
|
||||
duration: 0.2,
|
||||
afterFinish: function(effect) {
|
||||
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
|
||||
// show controls of next block to allow mass delete
|
||||
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
|
||||
}
|
||||
// remove placeholder
|
||||
if(effect.element.previous('.block_placeholder') !== undefined) {
|
||||
effect.element.previous('.block_placeholder').remove();
|
||||
|
@ -25,58 +25,49 @@ const columns = [
|
||||
|
||||
const messages = {
|
||||
onTrash: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form was moved to the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms were moved to the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 form was moved to the trash.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d forms were moved to the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form was permanently deleted.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms were permanently deleted.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 form was permanently deleted.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d forms were permanently deleted.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 form has been restored from the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d forms have been restored from the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 form has been restored from the trash.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d forms have been restored from the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -125,8 +116,8 @@ const FormList = React.createClass({
|
||||
endpoint: 'forms',
|
||||
action: 'create'
|
||||
}).done(function(response) {
|
||||
if(response !== false) {
|
||||
window.location = response;
|
||||
if(response.result && response.form_id) {
|
||||
window.location = mailpoet_form_edit_url + response.form_id;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -155,7 +146,7 @@ const FormList = React.createClass({
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Created on">
|
||||
<abbr>{ form.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -14,6 +14,9 @@ function(
|
||||
})
|
||||
return this.props.onSelectFilter(filters);
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
return this.props.onEmptyTrash();
|
||||
},
|
||||
getAvailableFilters: function() {
|
||||
let filters = this.props.filters;
|
||||
|
||||
@ -34,7 +37,7 @@ function(
|
||||
const available_filters = this.getAvailableFilters()
|
||||
.map(function(filter, i) {
|
||||
let default_value = false;
|
||||
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
|
||||
default_value = selected_filters[filter]
|
||||
} else {
|
||||
jQuery(`select[name="${filter}"]`).val('');
|
||||
@ -60,9 +63,10 @@ function(
|
||||
|
||||
let button = false;
|
||||
|
||||
if(available_filters.length > 0) {
|
||||
if (available_filters.length > 0) {
|
||||
button = (
|
||||
<input
|
||||
id="post-query-submit"
|
||||
onClick={ this.handleFilterAction }
|
||||
type="submit"
|
||||
defaultValue="Filter"
|
||||
@ -70,10 +74,23 @@ function(
|
||||
);
|
||||
}
|
||||
|
||||
let empty_trash = false;
|
||||
if (this.props.group === 'trash') {
|
||||
empty_trash = (
|
||||
<input
|
||||
onClick={ this.handleEmptyTrash }
|
||||
type="submit"
|
||||
value="Empty Trash"
|
||||
className="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alignleft actions actions">
|
||||
{ available_filters }
|
||||
{ button }
|
||||
{ empty_trash }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -74,10 +74,11 @@ define(
|
||||
);
|
||||
}
|
||||
|
||||
var custom_actions = this.props.item_actions;
|
||||
var item_actions = false;
|
||||
const custom_actions = this.props.item_actions;
|
||||
let item_actions = false;
|
||||
|
||||
if(custom_actions.length > 0) {
|
||||
let is_first = true;
|
||||
item_actions = custom_actions.map(function(action, index) {
|
||||
if(action.display !== undefined) {
|
||||
if(action.display(this.props.item) === false) {
|
||||
@ -85,10 +86,12 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
let custom_action = null;
|
||||
|
||||
if(action.name === 'trash') {
|
||||
return (
|
||||
custom_action = (
|
||||
<span key={ 'action-'+index } className="trash">
|
||||
{(index > 0) ? ' | ' : ''}
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
<a
|
||||
href="javascript:;"
|
||||
onClick={ this.handleTrashItem.bind(
|
||||
@ -100,27 +103,27 @@ define(
|
||||
</span>
|
||||
);
|
||||
} else if(action.refresh) {
|
||||
return (
|
||||
custom_action = (
|
||||
<span
|
||||
onClick={ this.props.onRefreshItems }
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(index > 0) ? ' | ' : ''}
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
{ action.link(this.props.item) }
|
||||
</span>
|
||||
);
|
||||
} else if(action.link) {
|
||||
return (
|
||||
custom_action = (
|
||||
<span
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(index > 0) ? ' | ' : ''}
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
{ action.link(this.props.item) }
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
custom_action = (
|
||||
<span
|
||||
key={ 'action-'+index } className={ action.name }>
|
||||
{(index > 0) ? ' | ' : ''}
|
||||
{(!is_first) ? ' | ' : ''}
|
||||
<a href="javascript:;" onClick={
|
||||
(action.onClick !== undefined)
|
||||
? action.onClick.bind(null,
|
||||
@ -132,6 +135,12 @@ define(
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if(custom_action !== null && is_first === true) {
|
||||
is_first = false;
|
||||
}
|
||||
|
||||
return custom_action;
|
||||
}.bind(this));
|
||||
} else {
|
||||
item_actions = (
|
||||
@ -504,10 +513,21 @@ define(
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
handleEmptyTrash: function() {
|
||||
this.handleBulkAction('all', {
|
||||
action: 'delete',
|
||||
group: 'trash'
|
||||
}, function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoetI18n.permanentlyDeleted.replace('%d', response)
|
||||
);
|
||||
});
|
||||
},
|
||||
handleBulkAction: function(selected_ids, params, callback) {
|
||||
if(
|
||||
this.state.selection === false
|
||||
&& this.state.selected_ids.length === 0
|
||||
&& selected_ids !== 'all'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -520,8 +540,10 @@ define(
|
||||
limit: 0,
|
||||
filter: this.state.filter,
|
||||
group: this.state.group,
|
||||
search: this.state.search,
|
||||
selection: selected_ids
|
||||
search: this.state.search
|
||||
}
|
||||
if(selected_ids !== 'all') {
|
||||
data.listing.selection = selected_ids;
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
@ -715,7 +737,10 @@ define(
|
||||
<ListingFilters
|
||||
filters={ this.state.filters }
|
||||
filter={ this.state.filter }
|
||||
onSelectFilter={ this.handleFilter } />
|
||||
group={ this.state.group }
|
||||
onSelectFilter={ this.handleFilter }
|
||||
onEmptyTrash={ this.handleEmptyTrash }
|
||||
/>
|
||||
<ListingPages
|
||||
count={ this.state.count }
|
||||
page={ this.state.page }
|
||||
|
@ -7,7 +7,11 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
}
|
||||
},
|
||||
setPage: function(page) {
|
||||
this.props.onSetPage(page);
|
||||
this.setState({
|
||||
page: null
|
||||
}, function () {
|
||||
this.props.onSetPage(this.constrainPage(page));
|
||||
}.bind(this));
|
||||
},
|
||||
setFirstPage: function() {
|
||||
this.setPage(1);
|
||||
@ -16,10 +20,14 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
this.setPage(this.getLastPage());
|
||||
},
|
||||
setPreviousPage: function() {
|
||||
this.setPage(this.constrainPage(this.props.page - 1));
|
||||
this.setPage(this.constrainPage(
|
||||
parseInt(this.props.page, 10) - 1)
|
||||
);
|
||||
},
|
||||
setNextPage: function() {
|
||||
this.setPage(this.constrainPage(this.props.page + 1));
|
||||
this.setPage(this.constrainPage(
|
||||
parseInt(this.props.page, 10) + 1)
|
||||
);
|
||||
},
|
||||
constrainPage: function(page) {
|
||||
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
|
||||
@ -27,14 +35,16 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
handleSetManualPage: function(e) {
|
||||
if(e.which === 13) {
|
||||
this.setPage(this.state.page);
|
||||
this.setState({ page: null });
|
||||
}
|
||||
},
|
||||
handleChangeManualPage: function(e) {
|
||||
this.setState({
|
||||
page: this.constrainPage(e.target.value)
|
||||
page: e.target.value
|
||||
});
|
||||
},
|
||||
handleBlurManualPage: function(e) {
|
||||
this.setPage(e.target.value);
|
||||
},
|
||||
getLastPage: function() {
|
||||
return Math.ceil(this.props.count / this.props.limit);
|
||||
},
|
||||
@ -101,6 +111,11 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
);
|
||||
}
|
||||
|
||||
let pageValue = this.props.page;
|
||||
if(this.state.page !== null) {
|
||||
pageValue = this.state.page;
|
||||
}
|
||||
|
||||
pagination = (
|
||||
<span className="pagination-links">
|
||||
{firstPage}
|
||||
@ -115,10 +130,11 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
type="text"
|
||||
onChange={ this.handleChangeManualPage }
|
||||
onKeyUp={ this.handleSetManualPage }
|
||||
onBlur={ this.handleBlurManualPage }
|
||||
aria-describedby="table-paging"
|
||||
size="1"
|
||||
ref="page"
|
||||
value={ this.state.page || this.props.page }
|
||||
value={ pageValue }
|
||||
name="paged"
|
||||
id="current-page-selector"
|
||||
className="current-page" />
|
||||
|
@ -47,7 +47,6 @@ define([
|
||||
autoScroll: true,
|
||||
|
||||
onstart: function(event) {
|
||||
console.log('Drag start', event, this);
|
||||
|
||||
if (that.options.cloneOriginal === true) {
|
||||
// Use substitution instead of a clone
|
||||
|
@ -11,10 +11,10 @@ define([
|
||||
'newsletter_editor/blocks/base',
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'newsletter_editor/components/wordpress',
|
||||
'newsletter_editor/components/communication',
|
||||
'underscore',
|
||||
'jquery'
|
||||
], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) {
|
||||
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -32,10 +32,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imagePadded: true, // true|false
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -63,15 +63,15 @@ define([
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
this.fetchPosts();
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
|
||||
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
|
||||
},
|
||||
fetchPosts: function() {
|
||||
var that = this;
|
||||
WordpressComponent.getTransformedPosts(this.toJSON()).done(function(content) {
|
||||
console.log('ALC fetched', arguments);
|
||||
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
|
||||
that.get('_container').get('blocks').reset(content, {parse: true});
|
||||
that.trigger('postsChanged');
|
||||
}).fail(function(error) {
|
||||
console.log('ALC fetchPosts error', arguments);
|
||||
});
|
||||
@ -81,7 +81,7 @@ define([
|
||||
* ALC posts on each model change
|
||||
*/
|
||||
_scheduleFetchPosts: function() {
|
||||
var timeout = 2000,
|
||||
var timeout = 500,
|
||||
that = this;
|
||||
if (this._fetchPostsTimer !== undefined) {
|
||||
clearTimeout(this._fetchPostsTimer);
|
||||
@ -100,6 +100,11 @@ define([
|
||||
toolsRegion: '.mailpoet_tools',
|
||||
postsRegion: '.mailpoet_automated_latest_content_block_posts',
|
||||
},
|
||||
modelEvents: _.extend(
|
||||
_.omit(base.BlockView.prototype.modelEvents, 'change'),
|
||||
{
|
||||
'postsChanged': 'render',
|
||||
}),
|
||||
events: _.extend(base.BlockView.prototype.events, {
|
||||
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings',
|
||||
}),
|
||||
@ -137,9 +142,9 @@ define([
|
||||
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_automated_latest_content_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_automated_latest_content_image_padded": _.partial(this.changeBoolField, "imagePadded"),
|
||||
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -161,7 +166,7 @@ define([
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
@ -174,10 +179,10 @@ define([
|
||||
},
|
||||
transport: function(options, success, failure) {
|
||||
var taxonomies,
|
||||
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
taxonomies = tax;
|
||||
// Fetch available terms based on the list of taxonomies already fetched
|
||||
var promise = WordpressComponent.getTerms({
|
||||
var promise = CommunicationComponent.getTerms({
|
||||
search: options.data.term,
|
||||
taxonomies: _.keys(taxonomies)
|
||||
}).then(function(terms) {
|
||||
@ -268,12 +273,13 @@ define([
|
||||
},
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -282,6 +288,12 @@ define([
|
||||
this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
|
@ -117,12 +117,9 @@ define([
|
||||
* Defines drop behavior of BlockView instance
|
||||
*/
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
//that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
@ -131,25 +128,37 @@ define([
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().done(function() {
|
||||
this.transitionOut().then(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('mailpoet_block_transition_in');
|
||||
return this._transition('slideDown', 'fadeIn', 'easeOut');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('mailpoet_block_transition_out');
|
||||
return this._transition('slideUp', 'fadeOut', 'easeIn');
|
||||
},
|
||||
_transition: function(className) {
|
||||
var that = this,
|
||||
promise = jQuery.Deferred();
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
this.$el.addClass(className);
|
||||
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
|
||||
that.$el.removeClass('mailpoet_block_transition_out');
|
||||
promise.resolve();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
@ -207,16 +216,14 @@ define([
|
||||
Module.BlockSettingsView = Marionette.LayoutView.extend({
|
||||
className: 'mailpoet_editor_settings',
|
||||
initialize: function() {
|
||||
var that = this;
|
||||
|
||||
MailPoet.Modal.panel({
|
||||
element: this.$el,
|
||||
template: '',
|
||||
position: 'right',
|
||||
width: App.getConfig().get('sidepanelWidth'),
|
||||
onCancel: function() {
|
||||
that.destroy();
|
||||
},
|
||||
this.destroy();
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
close: function(event) {
|
||||
|
@ -32,6 +32,7 @@ define([
|
||||
fontColor: '#000000',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'normal', // 'normal'|'bold'
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
@ -72,6 +73,7 @@ define([
|
||||
"change .mailpoet_field_button_font_size": _.partial(this.changeField, "styles.block.fontSize"),
|
||||
"change .mailpoet_field_button_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
|
||||
"change .mailpoet_field_button_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
|
||||
"change .mailpoet_field_button_font_weight": "changeFontWeight",
|
||||
|
||||
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
|
||||
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
|
||||
@ -128,6 +130,13 @@ define([
|
||||
this.$(fieldToUpdate).val(jQuery(event.target).val());
|
||||
callable(event);
|
||||
},
|
||||
changeFontWeight: function(event) {
|
||||
var checked = !!jQuery(event.target).prop('checked');
|
||||
this.model.set(
|
||||
'styles.block.fontWeight',
|
||||
(checked) ? jQuery(event.target).val() : 'normal'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Module.ButtonWidgetView = base.WidgetView.extend({
|
||||
|
@ -232,12 +232,9 @@ define([
|
||||
_.extend(this, this._buildRegions(this.regions));
|
||||
},
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
@ -251,20 +248,32 @@ define([
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('mailpoet_block_transition_in');
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('mailpoet_block_transition_out');
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_transition: function(className) {
|
||||
var that = this,
|
||||
promise = jQuery.Deferred();
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
this.$el.addClass(className);
|
||||
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
|
||||
that.$el.removeClass('mailpoet_block_transition_out');
|
||||
promise.resolve();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
@ -57,11 +57,9 @@ define([
|
||||
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
return _.extend({
|
||||
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px',
|
||||
};
|
||||
}, base.BlockView.prototype.templateHelpers.apply(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
|
||||
|
@ -39,9 +39,9 @@ define([
|
||||
Module.FooterBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.footerBlock; },
|
||||
modelEvents: {
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
},
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
|
||||
@ -60,11 +60,9 @@ define([
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
block_formats: 'Paragraph=p',
|
||||
|
||||
plugins: "link textcolor mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
|
@ -39,9 +39,9 @@ define([
|
||||
Module.HeaderBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.headerBlock; },
|
||||
modelEvents: {
|
||||
modelEvents: _.extend({
|
||||
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
|
||||
},
|
||||
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
|
||||
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
|
||||
@ -60,11 +60,9 @@ define([
|
||||
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
block_formats: 'Paragraph=p',
|
||||
|
||||
plugins: "link textcolor mailpoet_custom_fields",
|
||||
plugins: "link textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
|
@ -20,7 +20,7 @@ define([
|
||||
link: 'http://example.org',
|
||||
src: 'no-image.png',
|
||||
alt: 'An image of...',
|
||||
padded: true, // true | false - Padded or full width
|
||||
fullWidth: true, // true | false
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
styles: {
|
||||
@ -37,20 +37,18 @@ define([
|
||||
getTemplate: function() { return templates.imageBlock; },
|
||||
onDragSubstituteBy: function() { return Module.ImageWidgetView; },
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
return _.extend({
|
||||
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
|
||||
};
|
||||
}, base.BlockView.prototype.templateHelpers.apply(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
|
||||
if (this.model.get('padded')) {
|
||||
this.$el.removeClass('mailpoet_full_image');
|
||||
} else {
|
||||
if (this.model.get('fullWidth')) {
|
||||
this.$el.addClass('mailpoet_full_image');
|
||||
} else {
|
||||
this.$el.removeClass('mailpoet_full_image');
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -66,7 +64,7 @@ define([
|
||||
"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"),
|
||||
"change .mailpoet_field_image_padded": _.partial(this.changeBoolCheckboxField, "padded"),
|
||||
"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",
|
||||
"click .mailpoet_done_editing": "close",
|
||||
|
@ -18,12 +18,12 @@ define([
|
||||
'jquery',
|
||||
'mailpoet',
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/wordpress',
|
||||
'newsletter_editor/components/communication',
|
||||
'newsletter_editor/blocks/base',
|
||||
'newsletter_editor/blocks/button',
|
||||
'newsletter_editor/blocks/divider',
|
||||
'select2'
|
||||
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
|
||||
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -43,10 +43,10 @@ define([
|
||||
inclusionType: 'include', // 'include'|'exclude'
|
||||
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
|
||||
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
|
||||
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
|
||||
titleAlignment: 'left', // 'left'|'center'|'right'
|
||||
titleIsLink: false, // false|true
|
||||
imagePadded: true, // true|false
|
||||
imageFullWidth: false, // true|false
|
||||
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
|
||||
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
|
||||
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
|
||||
authorPrecededBy: 'Author:',
|
||||
@ -88,7 +88,7 @@ define([
|
||||
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
|
||||
|
||||
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
|
||||
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
|
||||
this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
|
||||
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
|
||||
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
|
||||
|
||||
@ -96,8 +96,7 @@ define([
|
||||
},
|
||||
fetchAvailablePosts: function() {
|
||||
var that = this;
|
||||
WordpressComponent.getPosts(this.toJSON()).done(function(posts) {
|
||||
console.log('Posts fetched', arguments);
|
||||
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
|
||||
that.get('_availablePosts').reset(posts);
|
||||
that.get('_selectedPosts').reset(); // Empty out the collection
|
||||
that.trigger('change:_availablePosts');
|
||||
@ -112,12 +111,11 @@ define([
|
||||
data.posts = this.get('_selectedPosts').pluck('ID');
|
||||
|
||||
if (data.posts.length === 0) {
|
||||
this.get('_transformedPosts.blocks').reset();
|
||||
this.get('_transformedPosts').get('blocks').reset();
|
||||
return;
|
||||
}
|
||||
|
||||
WordpressComponent.getTransformedPosts(data).done(function(posts) {
|
||||
console.log('Transformed posts fetched', arguments);
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
|
||||
}).fail(function() {
|
||||
console.log('Posts _refreshTransformedPosts error', arguments);
|
||||
@ -133,8 +131,7 @@ define([
|
||||
|
||||
if (data.posts.length === 0) return;
|
||||
|
||||
WordpressComponent.getTransformedPosts(data).done(function(posts) {
|
||||
console.log('Available posts fetched', arguments);
|
||||
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
|
||||
collection.add(posts, { at: index });
|
||||
}).fail(function() {
|
||||
console.log('Posts fetchPosts error', arguments);
|
||||
@ -145,13 +142,14 @@ define([
|
||||
Module.PostsBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.postsBlock; },
|
||||
modelEvents: {},
|
||||
modelEvents: {}, // Forcefully disable all events
|
||||
regions: _.extend({
|
||||
postsRegion: '.mailpoet_posts_block_posts',
|
||||
}, base.BlockView.prototype.regions),
|
||||
onDragSubstituteBy: function() { return Module.PostsWidgetView; },
|
||||
initialize: function() {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
|
||||
this.model.reply('blockView', this.notifyAboutSelf, this);
|
||||
},
|
||||
@ -271,7 +269,7 @@ define([
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_posts_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
@ -284,10 +282,10 @@ define([
|
||||
},
|
||||
transport: function(options, success, failure) {
|
||||
var taxonomies,
|
||||
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
|
||||
taxonomies = tax;
|
||||
// Fetch available terms based on the list of taxonomies already fetched
|
||||
var promise = WordpressComponent.getTerms({
|
||||
var promise = CommunicationComponent.getTerms({
|
||||
search: options.data.term,
|
||||
taxonomies: _.keys(taxonomies)
|
||||
}).then(function(terms) {
|
||||
@ -396,9 +394,9 @@ define([
|
||||
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
|
||||
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
|
||||
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
|
||||
"change .mailpoet_posts_title_position": _.partial(this.changeField, "titlePosition"),
|
||||
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
|
||||
"change .mailpoet_posts_image_padded": _.partial(this.changeBoolField, "imagePadded"),
|
||||
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
|
||||
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
|
||||
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
|
||||
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
|
||||
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
|
||||
@ -450,11 +448,11 @@ define([
|
||||
changeDisplayType: function(event) {
|
||||
var value = jQuery(event.target).val();
|
||||
if (value == 'titleOnly') {
|
||||
this.$('.mailpoet_posts_title_position_container').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_title_position_container').removeClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
|
||||
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
|
||||
|
||||
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
|
||||
if (this.model.get('titleFormat') === 'ul') {
|
||||
@ -463,6 +461,13 @@ define([
|
||||
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'excerpt') {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
|
||||
} else {
|
||||
this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
|
||||
}
|
||||
|
||||
this.changeField('displayType', event);
|
||||
},
|
||||
changeTitleFormat: function(event) {
|
||||
|
@ -175,12 +175,9 @@ define([
|
||||
_event.stopPropagation();
|
||||
},
|
||||
getDropFunc: function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
var newModel = that.model.clone();
|
||||
//that.model.destroy();
|
||||
return newModel;
|
||||
};
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
_buildRegions: function(regions) {
|
||||
var that = this;
|
||||
@ -211,20 +208,32 @@ define([
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('mailpoet_block_transition_in');
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('mailpoet_block_transition_out');
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_transition: function(className) {
|
||||
var that = this,
|
||||
promise = jQuery.Deferred();
|
||||
_transition: function(slideDirection, fadeDirection, easing) {
|
||||
var promise = jQuery.Deferred();
|
||||
|
||||
this.$el.velocity(
|
||||
slideDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
complete: function() {
|
||||
promise.resolve();
|
||||
}.bind(this),
|
||||
}
|
||||
).velocity(
|
||||
fadeDirection,
|
||||
{
|
||||
duration: 250,
|
||||
easing: easing,
|
||||
queue: false, // Do not enqueue, trigger animation in parallel
|
||||
}
|
||||
);
|
||||
|
||||
this.$el.addClass(className);
|
||||
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
|
||||
that.$el.removeClass('mailpoet_block_transition_out');
|
||||
promise.resolve();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
@ -26,6 +26,8 @@ define([
|
||||
getTemplate: function() { return templates.textBlock; },
|
||||
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
|
||||
initialize: function(options) {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.renderOptions = _.defaults(options.renderOptions || {}, {
|
||||
disableTextEditor: false,
|
||||
});
|
||||
@ -50,21 +52,15 @@ define([
|
||||
inline: true,
|
||||
|
||||
menubar: false,
|
||||
toolbar1: "styleselect bold italic forecolor | link unlink",
|
||||
toolbar1: "formatselect bold italic forecolor | link unlink",
|
||||
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_custom_fields",
|
||||
|
||||
//forced_root_block: 'p',
|
||||
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]",
|
||||
invalid_elements: "script",
|
||||
style_formats: [
|
||||
{title: 'Heading 1', block: 'h1'},
|
||||
{title: 'Heading 2', block: 'h2'},
|
||||
{title: 'Heading 3', block: 'h3'},
|
||||
block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
|
||||
|
||||
{title: 'Paragraph', block: 'p'},
|
||||
],
|
||||
|
||||
plugins: "link code textcolor mailpoet_custom_fields",
|
||||
plugins: "link code textcolor colorpicker mailpoet_custom_fields",
|
||||
|
||||
setup: function(editor) {
|
||||
editor.on('change', function(e) {
|
||||
|
@ -9,7 +9,7 @@ define([
|
||||
|
||||
Module._query = function(args) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'wordpress',
|
||||
endpoint: 'automatedLatestContent',
|
||||
action: args.action,
|
||||
data: args.options || {},
|
||||
});
|
||||
@ -63,16 +63,18 @@ define([
|
||||
};
|
||||
|
||||
Module.saveNewsletter = function(options) {
|
||||
return Module._query({
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
options: options,
|
||||
data: options || {},
|
||||
});
|
||||
};
|
||||
|
||||
Module.previewNewsletter = function(options) {
|
||||
return Module._query({
|
||||
action: 'preview',
|
||||
options: options,
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'sendPreview',
|
||||
data: options || {},
|
||||
});
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ define([
|
||||
// Does not hold newsletter content nor newsletter styles, those are
|
||||
// handled by other components.
|
||||
Module.NewsletterModel = SuperModel.extend({
|
||||
stale: ['body'],
|
||||
stale: ['body', 'created_at', 'deleted_at', 'updated_at'],
|
||||
initialize: function(options) {
|
||||
this.on('change', function() {
|
||||
App.getChannel().trigger('autoSave');
|
||||
@ -44,10 +44,10 @@ define([
|
||||
};
|
||||
|
||||
Module.getBody = function() {
|
||||
return JSON.stringify({
|
||||
return {
|
||||
content: App._contentContainer.toJSON(),
|
||||
globalStyles: App.getGlobalStyles().toJSON(),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
Module.toJSON = function() {
|
||||
@ -73,8 +73,7 @@ define([
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
// TODO: Other newsletter information will be needed as well.
|
||||
var body = JSON.parse(options.newsletter.body);
|
||||
var body = options.newsletter.body;
|
||||
App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true});
|
||||
App._contentContainerView = new (App.getBlockTypeView('container'))({
|
||||
model: App._contentContainer,
|
||||
|
@ -1,5 +1,6 @@
|
||||
define([
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/communication',
|
||||
'mailpoet',
|
||||
'notice',
|
||||
'backbone',
|
||||
@ -8,7 +9,18 @@ define([
|
||||
'blob',
|
||||
'filesaver',
|
||||
'html2canvas'
|
||||
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) {
|
||||
], function(
|
||||
App,
|
||||
CommunicationComponent,
|
||||
MailPoet,
|
||||
Notice,
|
||||
Backbone,
|
||||
Marionette,
|
||||
jQuery,
|
||||
Blob,
|
||||
FileSaver,
|
||||
html2canvas
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -17,26 +29,33 @@ define([
|
||||
|
||||
// Save editor contents to server
|
||||
Module.save = function() {
|
||||
App.getChannel().trigger('beforeEditorSave');
|
||||
|
||||
var json = App.toJSON();
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(json.body)) {
|
||||
json.body = JSON.stringify(json.body);
|
||||
}
|
||||
|
||||
App.getChannel().trigger('beforeEditorSave', json);
|
||||
|
||||
// save newsletter
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: json,
|
||||
}).done(function(response) {
|
||||
CommunicationComponent.saveNewsletter(json).done(function(response) {
|
||||
if(response.success !== undefined && response.success === true) {
|
||||
// TODO: Handle translations
|
||||
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
|
||||
} else if(response.error !== undefined) {
|
||||
if(response.error.length === 0) {
|
||||
// TODO: Handle translations
|
||||
MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
|
||||
MailPoet.Notice.error(
|
||||
"An unknown error occurred, please check your settings.",
|
||||
{
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$(response.error).each(function(i, error) {
|
||||
MailPoet.Notice.error(error);
|
||||
MailPoet.Notice.error(error, { scroll: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -48,7 +67,28 @@ define([
|
||||
};
|
||||
|
||||
Module.getThumbnail = function(element, options) {
|
||||
return html2canvas(element, options || {});
|
||||
var promise = html2canvas(element, options || {});
|
||||
|
||||
return promise.then(function(oldCanvas) {
|
||||
// Temporary workaround for html2canvas-alpha2.
|
||||
// Removes 1px left transparent border from resulting canvas.
|
||||
|
||||
var oldContext = oldCanvas.getContext('2d'),
|
||||
newCanvas = document.createElement("canvas"),
|
||||
newContext = newCanvas.getContext("2d"),
|
||||
leftBorderWidth = 1;
|
||||
|
||||
newCanvas.width = oldCanvas.width;
|
||||
newCanvas.height = oldCanvas.height;
|
||||
|
||||
newContext.drawImage(
|
||||
oldCanvas,
|
||||
leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
|
||||
0, 0, oldCanvas.width, oldCanvas.height
|
||||
);
|
||||
|
||||
return newCanvas;
|
||||
});
|
||||
};
|
||||
|
||||
Module.saveTemplate = function(options) {
|
||||
@ -58,7 +98,7 @@ define([
|
||||
promise.then(function(thumbnail) {
|
||||
var data = _.extend(options || {}, {
|
||||
thumbnail: thumbnail.toDataURL('image/jpeg'),
|
||||
body: App.getBody(),
|
||||
body: JSON.stringify(App.getBody()),
|
||||
});
|
||||
|
||||
return MailPoet.Ajax.post({
|
||||
@ -146,20 +186,52 @@ define([
|
||||
},
|
||||
saveAsTemplate: function() {
|
||||
var templateName = this.$('.mailpoet_save_as_template_name').val(),
|
||||
templateDescription = this.$('.mailpoet_save_as_template_description').val();
|
||||
templateDescription = this.$('.mailpoet_save_as_template_description').val(),
|
||||
that = this;
|
||||
|
||||
console.log('Saving template with ', templateName, templateDescription);
|
||||
Module.saveTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
}).done(function() {
|
||||
console.log('Template saved', arguments);
|
||||
}).fail(function() {
|
||||
// TODO: Handle error messages
|
||||
console.log('Template save failed', arguments);
|
||||
});
|
||||
if (templateName === '') {
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateNameMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else if (templateDescription === '') {
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateDescriptionMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Saving template with ', templateName, templateDescription);
|
||||
Module.saveTemplate({
|
||||
name: templateName,
|
||||
description: templateDescription,
|
||||
}).done(function() {
|
||||
console.log('Template saved', arguments);
|
||||
MailPoet.Notice.success(
|
||||
App.getConfig().get('translations.templateSaved'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
}).fail(function() {
|
||||
console.log('Template save failed', arguments);
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateSaveFailed'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
this.hideOptionContents();
|
||||
}
|
||||
|
||||
this.hideOptionContents();
|
||||
},
|
||||
toggleExportTemplate: function() {
|
||||
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
|
||||
@ -170,12 +242,25 @@ define([
|
||||
},
|
||||
exportTemplate: function() {
|
||||
var templateName = this.$('.mailpoet_export_template_name').val(),
|
||||
templateDescription = this.$('.mailpoet_export_template_description').val();
|
||||
templateDescription = this.$('.mailpoet_export_template_description').val(),
|
||||
that = this;
|
||||
|
||||
if (templateName === '') {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.templateNameMissing'));
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateNameMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else if (templateDescription === '') {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.templateDescriptionMissing'));
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.templateDescriptionMissing'),
|
||||
{
|
||||
positionAfter: that.$el,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('Exporting template with ', templateName, templateDescription);
|
||||
Module.exportTemplate({
|
||||
|
@ -1,12 +1,13 @@
|
||||
define([
|
||||
'newsletter_editor/App',
|
||||
'newsletter_editor/components/communication',
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'backbone.supermodel',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'sticky-kit'
|
||||
], function(App, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
|
||||
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -50,8 +51,33 @@ define([
|
||||
},
|
||||
events: {
|
||||
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) {
|
||||
this.$el.find('.mailpoet_sidebar_region').addClass('closed');
|
||||
this.$el.find(event.target).parent().parent().removeClass('closed');
|
||||
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
|
||||
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
|
||||
|
||||
if ($openRegion.get(0) === $targetRegion.get(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$openRegion.find('.mailpoet_region_content').velocity(
|
||||
'slideUp',
|
||||
{
|
||||
duration: 250,
|
||||
easing: "easeOut",
|
||||
complete: function() {
|
||||
$openRegion.addClass('closed');
|
||||
}.bind(this)
|
||||
}
|
||||
);
|
||||
$targetRegion.find('.mailpoet_region_content').velocity(
|
||||
'slideDown',
|
||||
{
|
||||
duration: 250,
|
||||
easing: "easeIn",
|
||||
complete: function() {
|
||||
$targetRegion.removeClass('closed');
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
initialize: function(options) {
|
||||
@ -90,7 +116,6 @@ define([
|
||||
});
|
||||
},
|
||||
onDomRefresh: function() {
|
||||
var that = this;
|
||||
this.$el.parent().stick_in_parent({
|
||||
offset_top: 32,
|
||||
});
|
||||
@ -169,10 +194,8 @@ define([
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.availableStyles = options.availableStyles;
|
||||
var that = this;
|
||||
},
|
||||
onRender: function() {
|
||||
var that = this;
|
||||
this.$('.mailpoet_color').spectrum({
|
||||
clickoutFiresChange: true,
|
||||
showInput: true,
|
||||
@ -202,15 +225,22 @@ define([
|
||||
showPreview: function() {
|
||||
var json = App.toJSON();
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(json.body)) {
|
||||
json.body = JSON.stringify(json.body);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'render',
|
||||
data: json,
|
||||
}).done(function(response){
|
||||
console.log('Should open a new window');
|
||||
window.open('data:text/html,' + encodeURIComponent(response.rendered_body), '_blank');
|
||||
MailPoet.Modal.loading(false);
|
||||
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
|
||||
}).fail(function(error) {
|
||||
console.log('Preview error', json);
|
||||
MailPoet.Modal.loading(false);
|
||||
alert('Something went wrong, check console');
|
||||
});
|
||||
},
|
||||
@ -218,27 +248,42 @@ define([
|
||||
// testing sending method
|
||||
console.log('trying to send a preview');
|
||||
// get form data
|
||||
var $emailField = this.$('#mailpoet_preview_to_email');
|
||||
var data = {
|
||||
from_name: this.$('#mailpoet_preview_from_name').val(),
|
||||
from_email: this.$('#mailpoet_preview_from_email').val(),
|
||||
to_email: this.$('#mailpoet_preview_to_email').val(),
|
||||
newsletter: App.newsletterId,
|
||||
subscriber: $emailField.val(),
|
||||
id: App.getNewsletter().get('id'),
|
||||
};
|
||||
|
||||
if (data.subscriber.length <= 0) {
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.newsletterPreviewEmailMissing'),
|
||||
{
|
||||
positionAfter: $emailField,
|
||||
scroll: true,
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// send test email
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
// TODO: Migrate logic to new AJAX format
|
||||
Wordpress.previewNewsletter(data).done(function(response) {
|
||||
if(response.success !== undefined && response.success === true) {
|
||||
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
|
||||
} else if(response.error !== undefined) {
|
||||
if(response.error.length === 0) {
|
||||
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
|
||||
} else {
|
||||
$(response.error).each(function(i, error) {
|
||||
MailPoet.Notice.error(error);
|
||||
CommunicationComponent.previewNewsletter(data).done(function(response) {
|
||||
if(response.result !== undefined && response.result === true) {
|
||||
MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true });
|
||||
} else {
|
||||
if (_.isArray(response.errors)) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error, { scroll: true });
|
||||
});
|
||||
} else {
|
||||
MailPoet.Notice.error(
|
||||
App.getConfig().get('translations.newsletterPreviewFailedToSend'),
|
||||
{
|
||||
scroll: true,
|
||||
static: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
MailPoet.Modal.loading(false);
|
||||
|
@ -17,7 +17,7 @@ define([
|
||||
},
|
||||
h1: {
|
||||
fontColor: '#111111',
|
||||
fontFamily: 'Arial Black',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '40px'
|
||||
},
|
||||
h2: {
|
||||
@ -72,7 +72,7 @@ define([
|
||||
|
||||
App.getAvailableStyles = Module.getAvailableStyles;
|
||||
|
||||
var body = JSON.parse(options.newsletter.body);
|
||||
var body = options.newsletter.body;
|
||||
this.setGlobalStyles(body.globalStyles);
|
||||
});
|
||||
|
||||
|
@ -43,58 +43,49 @@ define(
|
||||
|
||||
var messages = {
|
||||
onTrash: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter was moved to the trash.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters were moved to the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter was permanently deleted.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters were permanently deleted.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
var count = ~~response.newsletters;
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 newsletter has been restored from the trash.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
} else {
|
||||
message = (
|
||||
'%$1d newsletters have been restored from the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -144,7 +135,7 @@ define(
|
||||
});
|
||||
},
|
||||
renderStatus: function(item) {
|
||||
if(item.queue === null) {
|
||||
if(!item.queue) {
|
||||
return (
|
||||
<span>Not sent yet.</span>
|
||||
);
|
||||
@ -217,10 +208,8 @@ define(
|
||||
'has-row-actions'
|
||||
);
|
||||
|
||||
var segments = mailpoet_segments.filter(function(segment) {
|
||||
return (jQuery.inArray(segment.id, newsletter.segments) !== -1);
|
||||
}).map(function(segment) {
|
||||
return segment.name;
|
||||
var segments = newsletter.segments.map(function(segment) {
|
||||
return segment.name
|
||||
}).join(', ');
|
||||
|
||||
return (
|
||||
@ -238,10 +227,10 @@ define(
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed on">
|
||||
<abbr>{ newsletter.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Last modified on">
|
||||
<abbr>{ newsletter.updated_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ define(
|
||||
Breadcrumb
|
||||
) {
|
||||
|
||||
var settings = window.mailpoet_settings || {};
|
||||
var settings = window.mailpoet_settings || {};
|
||||
|
||||
var fields = [
|
||||
{
|
||||
@ -24,14 +24,18 @@ define(
|
||||
label: 'Subject line',
|
||||
tip: "Be creative! It's the first thing your subscribers see."+
|
||||
"Tempt them to open your email.",
|
||||
type: 'text'
|
||||
type: 'text',
|
||||
validation: {
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': 'You need to specify a subject.'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'segments',
|
||||
label: 'Lists',
|
||||
tip: "The subscriber list that will be used for this campaign.",
|
||||
label: 'Segments',
|
||||
tip: "The subscriber segment that will be used for this campaign.",
|
||||
type: 'selection',
|
||||
placeholder: "Select a list",
|
||||
placeholder: "Select a segment",
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
multiple: true,
|
||||
@ -39,7 +43,8 @@ define(
|
||||
return !!(!segment.deleted_at);
|
||||
},
|
||||
validation: {
|
||||
'data-parsley-required': true
|
||||
'data-parsley-required': true,
|
||||
'data-parsley-required-message': 'You need to select a segment.'
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -104,27 +109,40 @@ define(
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
componentDidMount: function() {
|
||||
jQuery('#mailpoet_newsletter').parsley();
|
||||
},
|
||||
isValid: function() {
|
||||
return jQuery('#mailpoet_newsletter').parsley().isValid();
|
||||
},
|
||||
handleSend: function() {
|
||||
if(jQuery('#mailpoet_newsletter').parsley().validate() === true) {
|
||||
if(!this.isValid()) {
|
||||
jQuery('#mailpoet_newsletter').parsley().validate();
|
||||
} else {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'add',
|
||||
data: {
|
||||
newsletter_id: this.props.params.id,
|
||||
segments: jQuery('#mailpoet_segments').val()
|
||||
segments: jQuery('#mailpoet_segments').val(),
|
||||
sender: {
|
||||
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
|
||||
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
|
||||
},
|
||||
reply_to: {
|
||||
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
|
||||
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
|
||||
}
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response.result === true) {
|
||||
this.history.pushState(null, '/');
|
||||
|
||||
MailPoet.Notice.success(
|
||||
'The newsletter is being sent...'
|
||||
);
|
||||
} else {
|
||||
if(response.errors) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.join("<br />")
|
||||
);
|
||||
MailPoet.Notice.error(response.errors);
|
||||
} else {
|
||||
MailPoet.Notice.error(
|
||||
'An error occurred while trying to send. '+
|
||||
@ -134,14 +152,7 @@ define(
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.isMounted()) {
|
||||
jQuery('#mailpoet_newsletter').parsley();
|
||||
}
|
||||
},
|
||||
isValid: function() {
|
||||
return (jQuery('#mailpoet_newsletter').parsley().validate());
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
@ -156,8 +167,8 @@ define(
|
||||
fields={ fields }
|
||||
params={ this.props.params }
|
||||
messages={ messages }
|
||||
isValid={ this.isValid }>
|
||||
|
||||
isValid={ this.isValid }
|
||||
>
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
|
@ -18,12 +18,21 @@ define(
|
||||
|
||||
var ImportTemplate = React.createClass({
|
||||
saveTemplate: function(template) {
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(template.body)) {
|
||||
template.body = JSON.stringify(template.body);
|
||||
}
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: template
|
||||
}).done(function(response) {
|
||||
if(response === true) {
|
||||
MailPoet.Modal.loading(false);
|
||||
if(response.result === true) {
|
||||
this.props.onImport(template);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
@ -86,10 +95,13 @@ define(
|
||||
getTemplates: function() {
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'getAll',
|
||||
}).done(function(response) {
|
||||
MailPoet.Modal.loading(false);
|
||||
if(this.isMounted()) {
|
||||
|
||||
if(response.length === 0) {
|
||||
@ -111,12 +123,19 @@ define(
|
||||
}.bind(this));
|
||||
},
|
||||
handleSelectTemplate: function(template) {
|
||||
var body = template.body;
|
||||
|
||||
// Stringify to enable transmission of primitive non-string value types
|
||||
if (!_.isUndefined(body)) {
|
||||
body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: {
|
||||
id: this.props.params.id,
|
||||
body: template.body
|
||||
body: body
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response.result === true) {
|
||||
@ -153,7 +172,7 @@ define(
|
||||
handleShowTemplate: function(template) {
|
||||
MailPoet.Modal.popup({
|
||||
title: template.name,
|
||||
template: '<img src="{{ thumbnail }}" />',
|
||||
template: '<div class="mailpoet_boxes_preview" style="background-color: {{ body.globalStyles.body.backgroundColor }}"><img src="{{ thumbnail }}" /></div>',
|
||||
data: template,
|
||||
});
|
||||
},
|
||||
|
@ -26,14 +26,17 @@ define(
|
||||
action: 'create',
|
||||
data: {
|
||||
type: type,
|
||||
subject: 'Draft newsletter',
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.history.pushState(null, `/template/${response.id}`);
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.history.pushState(null, `/template/${response.newsletter.id}`);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -138,12 +138,14 @@ define(
|
||||
options: this.state,
|
||||
},
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.showTemplateSelection(response.id);
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.showTemplateSelection(response.newsletter.id);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -32,12 +32,15 @@ define(
|
||||
type: 'standard',
|
||||
}
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.showTemplateSelection(response.id);
|
||||
console.log(response);
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.showTemplateSelection(response.newsletter.id);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -111,12 +111,14 @@ define(
|
||||
options: this.state,
|
||||
},
|
||||
}).done(function(response) {
|
||||
if(response.id !== undefined) {
|
||||
this.showTemplateSelection(response.id);
|
||||
if(response.result && response.newsletter.id) {
|
||||
this.showTemplateSelection(response.newsletter.id);
|
||||
} else {
|
||||
response.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
if(response.errors.length > 0) {
|
||||
response.errors.map(function(error) {
|
||||
MailPoet.Notice.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -49,6 +49,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
static: false,
|
||||
hideClose: false,
|
||||
id: null,
|
||||
positionAfter: false,
|
||||
scroll: false,
|
||||
timeout: 2000,
|
||||
onOpen: null,
|
||||
@ -69,7 +70,15 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
this.element.removeAttr('id');
|
||||
|
||||
// insert notice after its parent
|
||||
jQuery('#mailpoet_notice_'+this.options.type).after(this.element);
|
||||
var positionAfter;
|
||||
if (typeof this.options.positionAfter === 'object') {
|
||||
positionAfter = this.options.positionAfter;
|
||||
} else if (typeof this.options.positionAfter === 'string') {
|
||||
positionAfter = jQuery(this.options.positionAfter);
|
||||
} else {
|
||||
positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
|
||||
}
|
||||
positionAfter.after(this.element);
|
||||
|
||||
// setup onClose callback
|
||||
var onClose = null;
|
||||
@ -183,21 +192,28 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
error: function(message, options) {
|
||||
this.show(jQuery.extend({}, {
|
||||
type: 'error',
|
||||
message: '<p>'+message+'</p>'
|
||||
message: '<p>'+this.formatMessage(message)+'</p>'
|
||||
}, options));
|
||||
},
|
||||
success: function(message, options) {
|
||||
this.show(jQuery.extend({}, {
|
||||
type: 'success',
|
||||
message: '<p>'+message+'</p>'
|
||||
message: '<p>'+this.formatMessage(message)+'</p>'
|
||||
}, options));
|
||||
},
|
||||
system: function(message, options) {
|
||||
this.show(jQuery.extend({}, {
|
||||
type: 'system',
|
||||
static: true,
|
||||
message: message
|
||||
message: '<p>'+this.formatMessage(message)+'</p>'
|
||||
}, options));
|
||||
},
|
||||
formatMessage: function(message) {
|
||||
if(Array.isArray(message)) {
|
||||
return message.join('<br />');
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -12,7 +12,7 @@ define(
|
||||
Form
|
||||
) {
|
||||
|
||||
var fields = [
|
||||
let fields = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
@ -25,7 +25,7 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
var messages = {
|
||||
const messages = {
|
||||
onUpdate: function() {
|
||||
MailPoet.Notice.success('Segment successfully updated!');
|
||||
},
|
||||
@ -34,7 +34,7 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
var SegmentForm = React.createClass({
|
||||
const SegmentForm = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
@ -42,11 +42,7 @@ define(
|
||||
return (
|
||||
<div>
|
||||
<h2 className="title">
|
||||
Segment <a
|
||||
href="javascript:;"
|
||||
className="add-new-h2"
|
||||
onClick={ this.history.goBack }
|
||||
>Back to list</a>
|
||||
Segment
|
||||
</h2>
|
||||
|
||||
<Form
|
||||
|
@ -42,58 +42,49 @@ var columns = [
|
||||
|
||||
const messages = {
|
||||
onTrash: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 segment was moved to the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d segments were moved to the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 segment was moved to the trash.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d segments were moved to the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onDelete: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 segment was permanently deleted.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d segments were permanently deleted.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 segment was permanently deleted.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d segments were permanently deleted.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onRestore: function(response) {
|
||||
if(response) {
|
||||
let message = null;
|
||||
if(~~response === 1) {
|
||||
message = (
|
||||
'1 segment has been restored from the trash.'
|
||||
);
|
||||
} else if(~~response > 1) {
|
||||
message = (
|
||||
'%$1d segments have been restored from the trash.'
|
||||
).replace('%$1d', ~~response);
|
||||
}
|
||||
var count = ~~response;
|
||||
var message = null;
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
if(count === 1) {
|
||||
message = (
|
||||
'1 segment has been restored from the trash.'
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
'%$1d segments have been restored from the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -105,6 +96,9 @@ const item_actions = [
|
||||
return (
|
||||
<Link to={ `/edit/${item.id}` }>Edit</Link>
|
||||
);
|
||||
},
|
||||
display: function(segment) {
|
||||
return (segment.type !== 'wp_users');
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -187,16 +181,16 @@ const SegmentList = React.createClass({
|
||||
<abbr>{ segment.description }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed">
|
||||
<abbr>{ segment.subscribed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.subscribed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Unconfirmed">
|
||||
<abbr>{ segment.unconfirmed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.unconfirmed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Unsubscribed">
|
||||
<abbr>{ segment.unsubscribed || 0 }</abbr>
|
||||
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Created on">
|
||||
<abbr>{ segment.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ define(
|
||||
MailPoet,
|
||||
Form
|
||||
) {
|
||||
|
||||
var fields = [
|
||||
{
|
||||
name: 'email',
|
||||
@ -45,12 +44,59 @@ define(
|
||||
placeholder: "Select a list",
|
||||
endpoint: "segments",
|
||||
multiple: true,
|
||||
selected: function(subscriber) {
|
||||
if (Array.isArray(subscriber.subscriptions) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return subscriber.subscriptions.map(function(subscription) {
|
||||
if (subscription.status === 'subscribed') {
|
||||
return subscription.segment_id;
|
||||
}
|
||||
});
|
||||
},
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
},
|
||||
getSearchLabel: function(segment, subscriber) {
|
||||
let label = '';
|
||||
|
||||
if (subscriber.subscriptions !== undefined) {
|
||||
subscriber.subscriptions.map(function(subscription) {
|
||||
if (segment.id === subscription.segment_id) {
|
||||
label = segment.name;
|
||||
|
||||
if (subscription.status === 'unsubscribed') {
|
||||
const unsubscribed_at = MailPoet.Date
|
||||
.format(subscription.updated_at);
|
||||
label += ' (Unsubscribed on '+unsubscribed_at+')';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var custom_fields = window.mailpoet_custom_fields || [];
|
||||
custom_fields.map(custom_field => {
|
||||
let field = {
|
||||
name: 'cf_' + custom_field.id,
|
||||
label: custom_field.name,
|
||||
type: custom_field.type
|
||||
};
|
||||
if (custom_field.params) {
|
||||
field.params = custom_field.params;
|
||||
}
|
||||
|
||||
if (custom_field.params.values) {
|
||||
field.values = custom_field.params.values;
|
||||
}
|
||||
|
||||
fields.push(field);
|
||||
});
|
||||
|
||||
var messages = {
|
||||
onUpdate: function() {
|
||||
MailPoet.Notice.success('Subscriber successfully updated!');
|
||||
@ -70,11 +116,7 @@ define(
|
||||
return (
|
||||
<div>
|
||||
<h2 className="title">
|
||||
Subscriber <a
|
||||
href="javascript:;"
|
||||
className="add-new-h2"
|
||||
onClick={ this.history.goBack }
|
||||
>Back to list</a>
|
||||
Subscriber
|
||||
</h2>
|
||||
|
||||
<Form
|
||||
|
@ -62,7 +62,7 @@ define(
|
||||
if (_.contains(fieldsToExclude, selectedOptionId)) {
|
||||
selectEvent.preventDefault();
|
||||
if (selectedOptionId === 'deselect') {
|
||||
jQuery(selectElement).select2('val', '');
|
||||
jQuery(selectElement).val('').trigger('change');
|
||||
} else {
|
||||
var allOptions = [];
|
||||
_.each(container.find('option'), function (field) {
|
||||
@ -70,7 +70,7 @@ define(
|
||||
allOptions.push(field.value);
|
||||
}
|
||||
});
|
||||
jQuery(selectElement).select2('val', allOptions);
|
||||
jQuery(selectElement).val(allOptions).trigger('change');
|
||||
}
|
||||
jQuery(selectElement).select2('close');
|
||||
}
|
||||
@ -115,7 +115,7 @@ define(
|
||||
exportData.exportConfirmedOption = false;
|
||||
renderSegmentsAndFields(segmentsContainerElement, segments);
|
||||
}
|
||||
segmentsContainerElement.select2('val', selectedSegments);
|
||||
segmentsContainerElement.val(selectedSegments).trigger('change');
|
||||
});
|
||||
|
||||
function toggleNextStepButton(condition) {
|
||||
@ -138,17 +138,17 @@ define(
|
||||
endpoint: 'ImportExport',
|
||||
action: 'processExport',
|
||||
data: JSON.stringify({
|
||||
'exportConfirmedOption': exportData.exportConfirmedOption,
|
||||
'exportFormatOption': jQuery(':radio[name="option_format"]:checked').val(),
|
||||
'groupBySegmentOption': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
|
||||
'export_confirmed_option': exportData.exportConfirmedOption,
|
||||
'export_format_option': jQuery(':radio[name="option_format"]:checked').val(),
|
||||
'group_by_segment_option': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
|
||||
'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
|
||||
'subscriberFields': subscriberFieldsContainerElement.val()
|
||||
'subscriber_fields': subscriberFieldsContainerElement.val()
|
||||
})
|
||||
})
|
||||
.done(function (response) {
|
||||
MailPoet.Modal.loading(false);
|
||||
if (response.result === false) {
|
||||
MailPoet.Notice.error(response.error);
|
||||
MailPoet.Notice.error(response.errors);
|
||||
} else {
|
||||
resultMessage = MailPoetI18n.exportMessage
|
||||
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -208,6 +208,16 @@ const bulk_actions = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'sendConfirmationEmail',
|
||||
label: 'Resend confirmation email',
|
||||
onSuccess: function(response) {
|
||||
MailPoet.Notice.success(
|
||||
'%$1d confirmation emails have been sent.'
|
||||
.replace('%$1d', ~~response)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
@ -231,6 +241,15 @@ const item_actions = [
|
||||
];
|
||||
|
||||
const SubscriberList = React.createClass({
|
||||
getSegmentFromId: function(segment_id) {
|
||||
let result = false;
|
||||
mailpoet_segments.map(function(segment) {
|
||||
if (segment.id === segment_id) {
|
||||
result = segment;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
renderItem: function(subscriber, actions) {
|
||||
let row_classes = classNames(
|
||||
'manage-column',
|
||||
@ -255,11 +274,41 @@ const SubscriberList = React.createClass({
|
||||
break;
|
||||
}
|
||||
|
||||
let segments = mailpoet_segments.filter(function(segment) {
|
||||
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
|
||||
}).map(function(segment) {
|
||||
return segment.name;
|
||||
}).join(', ');
|
||||
let segments = false;
|
||||
|
||||
if (subscriber.subscriptions.length > 0) {
|
||||
let subscribed_segments = [];
|
||||
let unsubscribed_segments = [];
|
||||
|
||||
subscriber.subscriptions.map((subscription) => {
|
||||
const segment = this.getSegmentFromId(subscription.segment_id);
|
||||
if(segment === false) return;
|
||||
if (subscription.status === 'subscribed') {
|
||||
subscribed_segments.push(segment.name);
|
||||
} else if (subscription.status === 'unsubscribed') {
|
||||
unsubscribed_segments.push(segment.name);
|
||||
}
|
||||
});
|
||||
segments = (
|
||||
<span>
|
||||
<span className="mailpoet_segments_subscribed">
|
||||
{ subscribed_segments.join(', ') }
|
||||
{
|
||||
(
|
||||
subscribed_segments.length > 0
|
||||
&& unsubscribed_segments.length > 0
|
||||
) ? ' / ' : ''
|
||||
}
|
||||
</span>
|
||||
<span
|
||||
className="mailpoet_segments_unsubscribed"
|
||||
title="Lists to which the subscriber was subscribed."
|
||||
>
|
||||
{ unsubscribed_segments.join(', ') }
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let avatar = false;
|
||||
if(subscriber.avatar_url) {
|
||||
@ -292,10 +341,10 @@ const SubscriberList = React.createClass({
|
||||
{ segments }
|
||||
</td>
|
||||
<td className="column-date" data-colname="Subscribed on">
|
||||
<abbr>{ subscriber.created_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
|
||||
</td>
|
||||
<td className="column-date" data-colname="Last modified on">
|
||||
<abbr>{ subscriber.updated_at }</abbr>
|
||||
<abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
@ -313,6 +362,7 @@ const SubscriberList = React.createClass({
|
||||
</h2>
|
||||
|
||||
<Listing
|
||||
limit={ mailpoet_listing_per_page }
|
||||
location={ this.props.location }
|
||||
params={ this.props.params }
|
||||
endpoint="subscribers"
|
||||
|
79
assets/js/src/vendor/jquery.asyncqueue.js
vendored
Normal file
79
assets/js/src/vendor/jquery.asyncqueue.js
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of the jquery plugin "asyncQueue".
|
||||
*
|
||||
* (c) Sebastien Roch <roch.sebastien@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
(function($){
|
||||
$.AsyncQueue = function() {
|
||||
var that = this,
|
||||
queue = [],
|
||||
failureFunc,
|
||||
completeFunc,
|
||||
paused = false,
|
||||
lastCallbackData,
|
||||
_run;
|
||||
|
||||
_run = function() {
|
||||
var f = queue.shift();
|
||||
|
||||
if (f) {
|
||||
f.apply(that, [that]);
|
||||
if (paused === false) {
|
||||
_run();
|
||||
}
|
||||
} else {
|
||||
if(completeFunc){
|
||||
completeFunc.apply(that);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.onFailure = function(func) {
|
||||
failureFunc = func;
|
||||
}
|
||||
|
||||
this.onComplete = function(func) {
|
||||
completeFunc = func;
|
||||
}
|
||||
|
||||
this.add = function(func) {
|
||||
queue.push(func);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.storeData = function(dataObject) {
|
||||
lastCallbackData = dataObject;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.lastCallbackData = function () {
|
||||
return lastCallbackData;
|
||||
}
|
||||
|
||||
this.run = function() {
|
||||
paused = false;
|
||||
_run();
|
||||
}
|
||||
|
||||
this.pause = function () {
|
||||
paused = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
this.failure = function() {
|
||||
paused = true;
|
||||
if (failureFunc) {
|
||||
var args = [that];
|
||||
for(i = 0; i < arguments.length; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
failureFunc.apply(that, args);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
})(jQuery);
|
39
build
39
build
@ -1,39 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Remove previous build.
|
||||
rm wysija-newsletters.zip;
|
||||
|
||||
# Create temp dir.
|
||||
mkdir wysija-newsletters;
|
||||
|
||||
# Production assets.
|
||||
npm install;
|
||||
./do compile:all;
|
||||
|
||||
# Production libraries.
|
||||
./composer.phar install --no-dev;
|
||||
|
||||
# Copy release folders.
|
||||
cp -Rf lang wysija-newsletters;
|
||||
cp -RfL assets wysija-newsletters;
|
||||
cp -Rf lib wysija-newsletters;
|
||||
cp -Rf vendor wysija-newsletters;
|
||||
cp -Rf views wysija-newsletters;
|
||||
rm -Rf wysija-newsletters/assets/css/src;
|
||||
rm -Rf wysija-newsletters/assets/js/src;
|
||||
|
||||
# Copy release files.
|
||||
cp LICENSE wysija-newsletters;
|
||||
cp index.php wysija-newsletters;
|
||||
cp mailpoet.php wysija-newsletters;
|
||||
cp readme.txt wysija-newsletters;
|
||||
cp uninstall.php wysija-newsletters;
|
||||
|
||||
# Zip final release.
|
||||
zip -r wysija-newsletters.zip wysija-newsletters;
|
||||
|
||||
# Remove temp dir.
|
||||
rm -rf wysija-newsletters;
|
||||
|
||||
# Reinstall dev dependencies.
|
||||
./composer.phar install;
|
42
build.sh
Executable file
42
build.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
plugin_name='mailpoet'
|
||||
|
||||
# Remove previous build.
|
||||
rm $plugin_name.zip
|
||||
|
||||
# Create temp dir.
|
||||
mkdir $plugin_name
|
||||
|
||||
# Production assets.
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
./do compile:all
|
||||
|
||||
# Production libraries.
|
||||
./composer.phar install --no-dev
|
||||
|
||||
# Copy release folders.
|
||||
cp -Rf lang $plugin_name
|
||||
cp -RfL assets $plugin_name
|
||||
cp -Rf lib $plugin_name
|
||||
cp -Rf vendor $plugin_name
|
||||
cp -Rf views $plugin_name
|
||||
rm -Rf $plugin_name/assets/css/src
|
||||
rm -Rf $plugin_name/assets/js/src
|
||||
|
||||
# Copy release files.
|
||||
cp LICENSE $plugin_name
|
||||
cp index.php $plugin_name
|
||||
cp $plugin_name.php $plugin_name
|
||||
cp readme.txt $plugin_name
|
||||
cp uninstall.php $plugin_name
|
||||
|
||||
# Zip final release.
|
||||
zip -r $plugin_name.zip $plugin_name
|
||||
|
||||
# Remove temp dir.
|
||||
rm -rf $plugin_name
|
||||
|
||||
# Reinstall dev dependencies.
|
||||
./composer.phar install
|
@ -19,3 +19,12 @@ modules:
|
||||
user: ''
|
||||
password: ''
|
||||
dump: tests/_data/dump.sql
|
||||
coverage:
|
||||
enabled: true
|
||||
whitelist:
|
||||
include:
|
||||
- lib/*
|
||||
exclude:
|
||||
blacklist:
|
||||
include:
|
||||
exclude:
|
@ -10,7 +10,8 @@
|
||||
"swiftmailer/swiftmailer": "^5.4",
|
||||
"phpseclib/phpseclib": "*",
|
||||
"mtdowling/cron-expression": "^1.0",
|
||||
"nesbot/carbon": "^1.21"
|
||||
"nesbot/carbon": "^1.21",
|
||||
"soundasleep/html2text": "^0.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "*",
|
||||
|
702
composer.lock
generated
702
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,13 +9,6 @@ class Activator {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
register_activation_hook(
|
||||
Env::$file,
|
||||
array($this, 'activate')
|
||||
);
|
||||
}
|
||||
|
||||
function activate() {
|
||||
$migrator = new Migrator();
|
||||
$migrator->up();
|
||||
@ -23,4 +16,9 @@ class Activator {
|
||||
$populator = new Populator();
|
||||
$populator->up();
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
$migrator = new Migrator();
|
||||
$migrator->down();
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ class Analytics {
|
||||
}
|
||||
|
||||
function init() {
|
||||
add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
|
||||
// review: this creates a fatal error when mailpoet tables are dropped.
|
||||
//add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
|
||||
}
|
||||
|
||||
function setupAdminDependencies() {
|
||||
|
@ -1,8 +1,12 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use \MailPoet\Models\Setting;
|
||||
use \MailPoet\Util\Url;
|
||||
|
||||
class Changelog {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
|
||||
|
||||
@ -42,20 +46,7 @@ class Changelog {
|
||||
// save version number
|
||||
Setting::setValue('version', Env::$version);
|
||||
|
||||
global $wp;
|
||||
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
|
||||
|
||||
if($redirect_url !== $current_url) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
'mailpoet_redirect' => urlencode($current_url)
|
||||
),
|
||||
$redirect_url
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
Url::redirectWithReferer($redirect_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,14 @@ if(!defined('ABSPATH')) exit;
|
||||
class Env {
|
||||
static $version;
|
||||
static $plugin_name;
|
||||
static $plugin_url;
|
||||
static $plugin_path;
|
||||
static $file;
|
||||
static $path;
|
||||
static $views_path;
|
||||
static $assets_path;
|
||||
static $assets_url;
|
||||
static $temp_name;
|
||||
static $temp_path;
|
||||
static $temp_URL;
|
||||
static $languages_path;
|
||||
static $lib_path;
|
||||
static $plugin_prefix;
|
||||
@ -34,12 +33,12 @@ class Env {
|
||||
self::$file = $file;
|
||||
self::$path = dirname(self::$file);
|
||||
self::$plugin_name = 'mailpoet';
|
||||
self::$plugin_url = plugin_dir_url(__FILE__);
|
||||
self::$views_path = self::$path . '/views';
|
||||
self::$assets_path = self::$path . '/assets';
|
||||
self::$assets_url = plugins_url('/assets', $file);
|
||||
self::$temp_name = 'temp';
|
||||
self::$temp_path = self::$path . '/' . self::$temp_name;
|
||||
$wp_upload_dir = wp_upload_dir();
|
||||
self::$temp_path = $wp_upload_dir['path'];
|
||||
self::$temp_URL = $wp_upload_dir['url'];
|
||||
self::$languages_path = self::$path . '/lang';
|
||||
self::$lib_path = self::$path . '/lib';
|
||||
self::$plugin_prefix = 'mailpoet_';
|
||||
@ -74,19 +73,4 @@ class Env {
|
||||
);
|
||||
return implode('', $source_name);
|
||||
}
|
||||
|
||||
static function isPluginActivated() {
|
||||
$activatesPlugins = get_option('active_plugins');
|
||||
$isActivated = (
|
||||
in_array(
|
||||
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
|
||||
$activatesPlugins
|
||||
) ||
|
||||
in_array(
|
||||
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
|
||||
$activatesPlugins
|
||||
)
|
||||
);
|
||||
return ($isActivated) ? true : false;
|
||||
}
|
||||
}
|
@ -1,11 +1,89 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use MailPoet\Cron\Workers\Scheduler;
|
||||
use MailPoet\Cron\Workers\SendingQueue;
|
||||
use \MailPoet\Models\Setting;
|
||||
|
||||
class Hooks {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->setupSubscribe();
|
||||
$this->setupWPUsers();
|
||||
$this->setupImageSize();
|
||||
$this->setupListing();
|
||||
$this->setupCronWorkers();
|
||||
}
|
||||
|
||||
function setupSubscribe() {
|
||||
$subscribe = Setting::getValue('subscribe', array());
|
||||
// Subscribe in comments
|
||||
if(
|
||||
isset($subscribe['on_comment']['enabled'])
|
||||
&&
|
||||
(bool)$subscribe['on_comment']['enabled']
|
||||
) {
|
||||
if(is_user_logged_in()) {
|
||||
add_action(
|
||||
'comment_form_field_comment',
|
||||
'\MailPoet\Subscription\Comment::extendLoggedInForm'
|
||||
);
|
||||
} else {
|
||||
add_action(
|
||||
'comment_form_after_fields',
|
||||
'\MailPoet\Subscription\Comment::extendLoggedOutForm'
|
||||
);
|
||||
}
|
||||
|
||||
add_action(
|
||||
'comment_post',
|
||||
'\MailPoet\Subscription\Comment::onSubmit',
|
||||
60,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wp_set_comment_status',
|
||||
'\MailPoet\Subscription\Comment::onStatusUpdate',
|
||||
60,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
// Subscribe in registration form
|
||||
if(
|
||||
isset($subscribe['on_register']['enabled'])
|
||||
&&
|
||||
(bool)$subscribe['on_register']['enabled']
|
||||
) {
|
||||
if(is_multisite()) {
|
||||
add_action(
|
||||
'signup_extra_fields',
|
||||
'\MailPoet\Subscription\Registration::extendForm'
|
||||
);
|
||||
add_action(
|
||||
'wpmu_validate_user_signup',
|
||||
'\MailPoet\Subscription\Registration::onMultiSiteRegister',
|
||||
60,
|
||||
1
|
||||
);
|
||||
} else {
|
||||
add_action(
|
||||
'register_form',
|
||||
'\MailPoet\Subscription\Registration::extendForm'
|
||||
);
|
||||
add_action(
|
||||
'register_post',
|
||||
'\MailPoet\Subscription\Registration::onRegister',
|
||||
60,
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupWPUsers() {
|
||||
// WP Users synchronization
|
||||
add_action(
|
||||
'user_register',
|
||||
@ -38,19 +116,50 @@ class Hooks {
|
||||
'\MailPoet\Segments\WP::synchronizeUser',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
function setupImageSize() {
|
||||
add_filter(
|
||||
'image_size_names_choose',
|
||||
array(
|
||||
$this,
|
||||
'appendImageSizes'
|
||||
)
|
||||
array($this, 'appendImageSize'),
|
||||
10, 1
|
||||
);
|
||||
}
|
||||
|
||||
function appendImageSizes($sizes) {
|
||||
function appendImageSize($sizes) {
|
||||
return array_merge($sizes, array(
|
||||
'mailpoet_newsletter_max' => __('MailPoet Newsletter'),
|
||||
'mailpoet_newsletter_max' => __('MailPoet Newsletter')
|
||||
));
|
||||
}
|
||||
|
||||
function setupListing() {
|
||||
add_filter(
|
||||
'set-screen-option',
|
||||
array($this, 'setScreenOption'),
|
||||
10, 3
|
||||
);
|
||||
}
|
||||
|
||||
function setScreenOption($status, $option, $value) {
|
||||
if(preg_match('/^mailpoet_(.*)_per_page$/', $option)) {
|
||||
return $value;
|
||||
} else {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
|
||||
function setupCronWorkers() {
|
||||
add_action('mailpoet_cron_worker', array($this, 'runSchedulerWorker'), 10, 1);
|
||||
add_action('mailpoet_cron_worker', array($this, 'runSendingQueueWorker'), 10, 1);
|
||||
}
|
||||
|
||||
function runSchedulerWorker($timer) {
|
||||
$scheduler = new Scheduler($timer);
|
||||
$scheduler->process();
|
||||
|
||||
}
|
||||
function runSendingQueueWorker($timer) {
|
||||
$sending_queue = new SendingQueue($timer);
|
||||
$sending_queue->process();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ use MailPoet\Router;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
|
||||
class Initializer {
|
||||
function __construct($params = array(
|
||||
'file' => '',
|
||||
@ -17,19 +19,39 @@ class Initializer {
|
||||
|
||||
function init() {
|
||||
$this->setupDB();
|
||||
$this->setupActivator();
|
||||
$this->setupRenderer();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
|
||||
register_activation_hook(Env::$file, array($this, 'runMigrator'));
|
||||
register_activation_hook(Env::$file, array($this, 'runPopulator'));
|
||||
|
||||
add_action('plugins_loaded', array($this, 'setup'));
|
||||
add_action('init', array($this, 'onInit'));
|
||||
add_action('widgets_init', array($this, 'setupWidget'));
|
||||
}
|
||||
|
||||
function setup() {
|
||||
try {
|
||||
$this->setupRenderer();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
$this->setupPermissions();
|
||||
$this->setupAnalytics();
|
||||
$this->setupChangelog();
|
||||
$this->setupShortcodes();
|
||||
$this->setupHooks();
|
||||
$this->setupImages();
|
||||
$this->setupPublicAPI();
|
||||
$this->runQueueSupervisor();
|
||||
} catch(\Exception $e) {
|
||||
// if anything goes wrong during init
|
||||
// automatically deactivate the plugin
|
||||
deactivate_plugins(Env::$file);
|
||||
}
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
$this->setupRouter();
|
||||
$this->setupWidget();
|
||||
$this->setupAnalytics();
|
||||
$this->setupPermissions();
|
||||
$this->setupChangelog();
|
||||
$this->setupPublicAPI();
|
||||
$this->setupPages();
|
||||
$this->runQueueSupervisor();
|
||||
$this->setupHooks();
|
||||
$this->setupImages();
|
||||
}
|
||||
|
||||
function setupDB() {
|
||||
@ -37,6 +59,10 @@ class Initializer {
|
||||
\ORM::configure('username', Env::$db_username);
|
||||
\ORM::configure('password', Env::$db_password);
|
||||
\ORM::configure('logging', WP_DEBUG);
|
||||
\ORM::configure('logger', function($query, $time) {
|
||||
// error_log("\n".$query."\n");
|
||||
});
|
||||
|
||||
\ORM::configure('driver_options', array(
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"'
|
||||
@ -47,8 +73,6 @@ class Initializer {
|
||||
$newsletters = Env::$db_prefix . 'newsletters';
|
||||
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
|
||||
$segments = Env::$db_prefix . 'segments';
|
||||
$filters = Env::$db_prefix . 'filters';
|
||||
$segment_filter = Env::$db_prefix . 'segment_filter';
|
||||
$forms = Env::$db_prefix . 'forms';
|
||||
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
|
||||
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
|
||||
@ -63,8 +87,6 @@ class Initializer {
|
||||
define('MP_SETTINGS_TABLE', $settings);
|
||||
define('MP_NEWSLETTERS_TABLE', $newsletters);
|
||||
define('MP_SEGMENTS_TABLE', $segments);
|
||||
define('MP_FILTERS_TABLE', $filters);
|
||||
define('MP_SEGMENT_FILTER_TABLE', $segment_filter);
|
||||
define('MP_FORMS_TABLE', $forms);
|
||||
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
|
||||
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
|
||||
@ -73,13 +95,18 @@ class Initializer {
|
||||
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
|
||||
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
|
||||
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
|
||||
define('MP_SENDING_QUEUE_TABLE', $sending_queues);
|
||||
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
|
||||
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
|
||||
}
|
||||
|
||||
function setupActivator() {
|
||||
$activator = new Activator();
|
||||
$activator->init();
|
||||
function runMigrator() {
|
||||
$migrator = new Migrator();
|
||||
$migrator->up();
|
||||
}
|
||||
|
||||
function runPopulator() {
|
||||
$populator = new Populator();
|
||||
$populator->up();
|
||||
}
|
||||
|
||||
function setupRenderer() {
|
||||
@ -93,10 +120,7 @@ class Initializer {
|
||||
}
|
||||
|
||||
function setupMenu() {
|
||||
$menu = new Menu(
|
||||
$this->renderer,
|
||||
Env::$assets_url
|
||||
);
|
||||
$menu = new Menu($this->renderer, Env::$assets_url);
|
||||
$menu->init();
|
||||
}
|
||||
|
||||
@ -125,6 +149,19 @@ class Initializer {
|
||||
$changelog->init();
|
||||
}
|
||||
|
||||
function setupPages() {
|
||||
$pages = new \MailPoet\Settings\Pages();
|
||||
$pages->init();
|
||||
|
||||
$subscription_pages = new \MailPoet\Subscription\Pages();
|
||||
$subscription_pages->init();
|
||||
}
|
||||
|
||||
function setupShortcodes() {
|
||||
$shortcodes = new Shortcodes();
|
||||
$shortcodes->init();
|
||||
}
|
||||
|
||||
function setupHooks() {
|
||||
$hooks = new Hooks();
|
||||
$hooks->init();
|
||||
@ -136,10 +173,12 @@ class Initializer {
|
||||
}
|
||||
|
||||
function runQueueSupervisor() {
|
||||
if(php_sapi_name() === 'cli') return;
|
||||
try {
|
||||
$supervisor = new Supervisor();
|
||||
$supervisor->checkDaemon();
|
||||
} catch (\Exception $e) {}
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
function setupImages() {
|
||||
|
@ -11,8 +11,7 @@ class Localizer {
|
||||
function init() {
|
||||
add_action(
|
||||
'init',
|
||||
array($this, 'setup'),
|
||||
0
|
||||
array($this, 'setup')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ use MailPoet\Settings\Pages;
|
||||
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
|
||||
use MailPoet\Util\DKIM;
|
||||
use MailPoet\Util\Permissions;
|
||||
use MailPoet\Listing;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -67,7 +68,7 @@ class Menu {
|
||||
'forms'
|
||||
)
|
||||
);
|
||||
add_submenu_page(
|
||||
$subscribers_page = add_submenu_page(
|
||||
'mailpoet',
|
||||
__('Subscribers'),
|
||||
__('Subscribers'),
|
||||
@ -78,6 +79,17 @@ class Menu {
|
||||
'subscribers'
|
||||
)
|
||||
);
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$subscribers_page, function() {
|
||||
add_screen_option('per_page', array(
|
||||
'label' => _x(
|
||||
'Number of subscribers per page',
|
||||
'subscribers per page (screen options)'
|
||||
),
|
||||
'option' => 'mailpoet_subscribers_per_page'
|
||||
));
|
||||
});
|
||||
|
||||
add_submenu_page(
|
||||
'mailpoet',
|
||||
__('Segments'),
|
||||
@ -190,6 +202,8 @@ class Menu {
|
||||
}
|
||||
|
||||
function welcome() {
|
||||
if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
|
||||
|
||||
global $wp;
|
||||
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
|
||||
$redirect_url =
|
||||
@ -240,6 +254,7 @@ class Menu {
|
||||
|
||||
function settings() {
|
||||
$settings = Setting::getAll();
|
||||
$flags = $this->_getFlags();
|
||||
|
||||
// dkim: check if public/private keys have been generated
|
||||
if(
|
||||
@ -258,10 +273,9 @@ class Menu {
|
||||
|
||||
$data = array(
|
||||
'settings' => $settings,
|
||||
'segments' => Segment::getPublished()
|
||||
->findArray(),
|
||||
'segments' => Segment::getPublic()->findArray(),
|
||||
'pages' => Pages::getAll(),
|
||||
'flags' => $this->_getFlags(),
|
||||
'flags' => $flags,
|
||||
'charsets' => Charsets::getAll(),
|
||||
'current_user' => wp_get_current_user(),
|
||||
'permissions' => Permissions::getAll(),
|
||||
@ -294,7 +308,7 @@ class Menu {
|
||||
} else {
|
||||
// check if users can register
|
||||
$flags['registration_enabled'] =
|
||||
(bool) get_option('users_can_register', false);
|
||||
(bool)get_option('users_can_register', false);
|
||||
}
|
||||
|
||||
return $flags;
|
||||
@ -303,8 +317,33 @@ class Menu {
|
||||
function subscribers() {
|
||||
$data = array();
|
||||
|
||||
// listing: limit per page
|
||||
$listing_per_page = get_user_meta(
|
||||
get_current_user_id(), 'mailpoet_subscribers_per_page', true
|
||||
);
|
||||
$data['per_page'] = (!empty($listing_per_page))
|
||||
? (int)$listing_per_page
|
||||
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
|
||||
|
||||
$data['segments'] = Segment::findArray();
|
||||
|
||||
$data['custom_fields'] = array_map(function($field) {
|
||||
$field['params'] = unserialize($field['params']);
|
||||
|
||||
if(!empty($field['params']['values'])) {
|
||||
$values = array();
|
||||
|
||||
foreach($field['params']['values'] as $value) {
|
||||
$values[$value['value']] = $value['value'];
|
||||
}
|
||||
$field['params']['values'] = $values;
|
||||
}
|
||||
return $field;
|
||||
}, CustomField::findArray());
|
||||
|
||||
$data['date_formats'] = Block\Date::getDateFormats();
|
||||
$data['month_names'] = Block\Date::getMonthNames();
|
||||
|
||||
echo $this->renderer->render('subscribers/subscribers.html', $data);
|
||||
}
|
||||
|
||||
@ -341,6 +380,7 @@ class Menu {
|
||||
|
||||
$data = array(
|
||||
'customFields' => $custom_fields,
|
||||
'settings' => Setting::getAll(),
|
||||
);
|
||||
wp_enqueue_media();
|
||||
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
|
||||
@ -374,15 +414,14 @@ class Menu {
|
||||
->findArray(),
|
||||
'styles' => FormRenderer::getStyles($form),
|
||||
'date_types' => Block\Date::getDateTypes(),
|
||||
'date_formats' => Block\Date::getDateFormats()
|
||||
'date_formats' => Block\Date::getDateFormats(),
|
||||
'month_names' => Block\Date::getMonthNames()
|
||||
);
|
||||
|
||||
echo $this->renderer->render('form/editor.html', $data);
|
||||
}
|
||||
|
||||
function cron() {
|
||||
$daemon = new \MailPoet\Cron\BootStrapMenu();
|
||||
$data['daemon'] = json_encode($daemon->bootstrap());
|
||||
echo $this->renderer->render('cron.html', $data);
|
||||
echo $this->renderer->render('cron.html');
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
|
||||
@ -41,7 +41,7 @@ class Migrator {
|
||||
function down() {
|
||||
global $wpdb;
|
||||
|
||||
$drop_table = function($model) {
|
||||
$drop_table = function($model) use($wpdb) {
|
||||
$table = $this->prefix . $model;
|
||||
$wpdb->query("DROP TABLE {$table}");
|
||||
};
|
||||
@ -105,7 +105,7 @@ class Migrator {
|
||||
'description varchar(250) NOT NULL,',
|
||||
'body LONGTEXT,',
|
||||
'thumbnail LONGTEXT,',
|
||||
'readonly TINYINT(1) DEFAULT 0',
|
||||
'readonly TINYINT(1) DEFAULT 0,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)'
|
||||
@ -236,10 +236,7 @@ class Migrator {
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'sent_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
|
||||
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
|
@ -3,8 +3,12 @@ namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Config\PopulatorData\Templates\FranksRoastHouseTemplate;
|
||||
use MailPoet\Config\PopulatorData\Templates\BlankTemplate;
|
||||
use MailPoet\Config\PopulatorData\Templates\WelcomeTemplate;
|
||||
use MailPoet\Config\PopulatorData\Templates\PostNotificationsBlankTemplate;
|
||||
use \MailPoet\Models\Segment;
|
||||
use \MailPoet\Segments\WP;
|
||||
use \MailPoet\Models\Setting;
|
||||
use \MailPoet\Settings\Pages;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
@ -44,6 +48,57 @@ class Populator {
|
||||
array_map(array($this, 'populate'), $this->models);
|
||||
|
||||
$this->createDefaultSegments();
|
||||
$this->createDefaultSettings();
|
||||
$this->createMailPoetPage();
|
||||
}
|
||||
|
||||
private function createMailPoetPage() {
|
||||
$pages = get_posts(array(
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'post_type' => 'mailpoet_page'
|
||||
));
|
||||
|
||||
$page = null;
|
||||
if(!empty($pages)) {
|
||||
$page = array_shift($pages);
|
||||
if(strpos($page->post_content, '[mailpoet_page]') === false) {
|
||||
$page = null;
|
||||
}
|
||||
}
|
||||
|
||||
if($page === null) {
|
||||
$mailpoet_page_id = Pages::createMailPoetPage();
|
||||
Setting::setValue('subscription.page', $mailpoet_page_id);
|
||||
}
|
||||
}
|
||||
|
||||
private function createDefaultSettings() {
|
||||
$current_user = wp_get_current_user();
|
||||
|
||||
// user name
|
||||
$user_name = '';
|
||||
if($current_user->user_firstname) {
|
||||
$user_name = $current_user->user_firstname;
|
||||
}
|
||||
if($current_user->user_lastname) {
|
||||
if($user_name) {
|
||||
$user_name .= ' '.$current_user->user_lastname;
|
||||
}
|
||||
}
|
||||
if(!$user_name) {
|
||||
$user_name = $current_user->display_name;
|
||||
}
|
||||
|
||||
// default from name & address
|
||||
Setting::setValue('sender', array(
|
||||
'name' => $user_name,
|
||||
'address' => $current_user->user_email
|
||||
));
|
||||
|
||||
// enable signup confirmation by default
|
||||
Setting::setValue('signup_confirmation.enabled', true);
|
||||
}
|
||||
|
||||
private function createDefaultSegments() {
|
||||
@ -127,6 +182,8 @@ class Populator {
|
||||
return array(
|
||||
(new FranksRoastHouseTemplate(Env::$assets_url))->get(),
|
||||
(new BlankTemplate(Env::$assets_url))->get(),
|
||||
(new WelcomeTemplate(Env::$assets_url))->get(),
|
||||
(new PostNotificationsBlankTemplate(Env::$assets_url))->get(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,6 @@ class BlankTemplate {
|
||||
|
||||
function __construct($assets_url) {
|
||||
$this->assets_url = $assets_url;
|
||||
$this->template_image_url = $this->assets_url . '/img/sample_template';
|
||||
$this->social_icon_url = $this->assets_url . '/img/newsletter_editor/social-icons';
|
||||
}
|
||||
|
||||
function get() {
|
||||
@ -181,7 +179,7 @@ class BlankTemplate {
|
||||
),
|
||||
"h1" => array(
|
||||
"fontColor" => "#111111",
|
||||
"fontFamily" => "Arial Black",
|
||||
"fontFamily" => "Arial",
|
||||
"fontSize" => "24px"
|
||||
),
|
||||
"h2" => array(
|
||||
|
@ -68,7 +68,7 @@ class FranksRoastHouseTemplate {
|
||||
"link" => "http://www.example.com",
|
||||
"src" => $this->template_image_url . "/header-v2.jpg",
|
||||
"alt" => __("Frank's Roast House"),
|
||||
"padded" => false,
|
||||
"fullWidth" => true,
|
||||
"width" => "600px",
|
||||
"height" => "220px",
|
||||
"styles" => array(
|
||||
@ -95,7 +95,7 @@ class FranksRoastHouseTemplate {
|
||||
"link" => "http://example.org",
|
||||
"src" => $this->template_image_url . "/coffee-grain.jpg",
|
||||
"alt" => __("coffee-grain-3-1329675-1599x941"),
|
||||
"padded" => true,
|
||||
"fullWidth" => false,
|
||||
"width" => "1599px",
|
||||
"height" => "777px",
|
||||
"styles" => array(
|
||||
@ -139,7 +139,7 @@ class FranksRoastHouseTemplate {
|
||||
"link" => "http://example.org",
|
||||
"src" => $this->template_image_url . "/sandwich.jpg",
|
||||
"alt" => "sandwich",
|
||||
"padded" => true,
|
||||
"fullWidth" => false,
|
||||
"width" => "640px",
|
||||
"height" => "344px",
|
||||
"styles" => array(
|
||||
@ -168,6 +168,7 @@ class FranksRoastHouseTemplate {
|
||||
"fontColor" => "#ffffff",
|
||||
"fontFamily" => "Arial",
|
||||
"fontSize" => "14px",
|
||||
"fontWeight" => "normal",
|
||||
"textAlign" => "center"
|
||||
)
|
||||
)
|
||||
@ -238,7 +239,7 @@ class FranksRoastHouseTemplate {
|
||||
"link" => "http://example.org",
|
||||
"src" => $this->template_image_url . "/map-v2.jpg",
|
||||
"alt" => __("map-v2"),
|
||||
"padded" => true,
|
||||
"fullWidth" => false,
|
||||
"width" => "636px",
|
||||
"height" => "342px",
|
||||
"styles" => array(
|
||||
@ -279,19 +280,19 @@ class FranksRoastHouseTemplate {
|
||||
"blocks" => array(
|
||||
array(
|
||||
"type" => "footer",
|
||||
"text" => __("<p><span style=\"color: #000000;\"><a href=\"[unsubscribeUrl]\" style=\"color: #000000;\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\" style=\"color: #000000;\">Manage subscription</a></span><br /><span style=\"color: #000000;\">12345 MailPoet Drive, EmailVille, 76543</span></p>"),
|
||||
"text" => __("<p><a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
|
||||
"styles" => array(
|
||||
"block" => array(
|
||||
"backgroundColor" => "#a9a7a7"
|
||||
),
|
||||
"text" => array(
|
||||
"fontColor" => "#ffffff",
|
||||
"fontColor" => "#000000",
|
||||
"fontFamily" => "Arial",
|
||||
"fontSize" => "12px",
|
||||
"textAlign" => "center"
|
||||
),
|
||||
"link" => array(
|
||||
"fontColor" => "#ffffff",
|
||||
"fontColor" => "#000000",
|
||||
"textDecoration" => "underline"
|
||||
)
|
||||
)
|
||||
|
File diff suppressed because one or more lines are too long
295
lib/Config/PopulatorData/Templates/WelcomeTemplate.php
Normal file
295
lib/Config/PopulatorData/Templates/WelcomeTemplate.php
Normal file
File diff suppressed because one or more lines are too long
@ -7,15 +7,20 @@ use MailPoet\Util\Helpers;
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class PublicAPI {
|
||||
public $api;
|
||||
public $section;
|
||||
public $action;
|
||||
public $request_payload;
|
||||
|
||||
function __construct() {
|
||||
# http://example.com/?mailpoet-api§ion=&action=&payload=
|
||||
# http://example.com/?mailpoet-api§ion=&action=&request_payload=
|
||||
$this->api = isset($_GET['mailpoet-api']) ? true : false;
|
||||
$this->section = isset($_GET['section']) ? $_GET['section'] : false;
|
||||
$this->action = isset($_GET['action']) ?
|
||||
Helpers::underscoreToCamelCase($_GET['action']) :
|
||||
false;
|
||||
$this->payload = isset($_GET['payload']) ?
|
||||
json_decode(urldecode($_GET['payload']), true) :
|
||||
$this->request_payload = isset($_GET['request_payload']) ?
|
||||
unserialize(base64_decode($_GET['request_payload'])) :
|
||||
false;
|
||||
}
|
||||
|
||||
@ -25,8 +30,11 @@ class PublicAPI {
|
||||
}
|
||||
|
||||
function queue() {
|
||||
$queue = new Daemon($this->payload);
|
||||
$this->_checkAndCallMethod($queue, $this->action);
|
||||
try {
|
||||
$queue = new Daemon($this->request_payload);
|
||||
$this->_checkAndCallMethod($queue, $this->action);
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
private function _checkAndCallMethod($class, $method, $terminate = false) {
|
||||
|
121
lib/Config/Shortcodes.php
Normal file
121
lib/Config/Shortcodes.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use \MailPoet\Models\Newsletter;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Models\SubscriberSegment;
|
||||
|
||||
class Shortcodes {
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
// form widget shortcode
|
||||
add_shortcode('mailpoet_form', array($this, 'formWidget'));
|
||||
add_shortcode('wysija_form', array($this, 'formWidget'));
|
||||
|
||||
// subscribers count shortcode
|
||||
add_shortcode('mailpoet_subscribers_count', array(
|
||||
$this, 'getSubscribersCount'
|
||||
));
|
||||
add_shortcode('wysija_subscribers_count', array(
|
||||
$this, 'getSubscribersCount'
|
||||
));
|
||||
|
||||
// archives page
|
||||
add_shortcode('mailpoet_archive', array(
|
||||
$this, 'getArchive'
|
||||
));
|
||||
|
||||
add_filter('mailpoet_archive_date', array(
|
||||
$this, 'renderArchiveDate'
|
||||
), 2);
|
||||
add_filter('mailpoet_archive_subject', array(
|
||||
$this, 'renderArchiveSubject'
|
||||
), 2);
|
||||
}
|
||||
|
||||
function formWidget($params = array()) {
|
||||
// IMPORTANT: fixes conflict with MagicMember
|
||||
remove_shortcode('user_list');
|
||||
|
||||
if(isset($params['id']) && (int)$params['id'] > 0) {
|
||||
$form_widget = new \MailPoet\Form\Widget();
|
||||
return $form_widget->widget(array(
|
||||
'form' => (int)$params['id'],
|
||||
'form_type' => 'shortcode'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
function getSubscribersCount($params) {
|
||||
if(!empty($params['segments'])) {
|
||||
$segment_ids = array_map(function($segment_id) {
|
||||
return (int)trim($segment_id);
|
||||
}, explode(',', $params['segments']));
|
||||
}
|
||||
|
||||
if(empty($segment_ids)) {
|
||||
return Subscriber::filter('subscribed')->count();
|
||||
} else {
|
||||
return SubscriberSegment::whereIn('segment_id', $segment_ids)
|
||||
->select('subscriber_id')->distinct()
|
||||
->filter('subscribed')
|
||||
->findResultSet()->count();
|
||||
}
|
||||
}
|
||||
|
||||
function getArchive($params) {
|
||||
if(!empty($params['segments'])) {
|
||||
$segment_ids = array_map(function($segment_id) {
|
||||
return (int)trim($segment_id);
|
||||
}, explode(',', $params['segments']));
|
||||
}
|
||||
|
||||
$newsletters = array();
|
||||
$html = '';
|
||||
|
||||
// TODO: needs more advanced newsletters in order to finish
|
||||
$newsletters = Newsletter::limit(10)->orderByDesc('created_at')->findMany();
|
||||
|
||||
if(empty($newsletters)) {
|
||||
return apply_filters(
|
||||
'mailpoet_archive_no_newsletters',
|
||||
__('Oops! There are no newsletters to display.')
|
||||
);
|
||||
} else {
|
||||
$title = apply_filters('mailpoet_archive_title', '');
|
||||
if(!empty($title)) {
|
||||
$html .= '<h3 class="mailpoet_archive_title">'.$title.'</h3>';
|
||||
}
|
||||
|
||||
$html .= '<ul class="mailpoet_archive">';
|
||||
foreach($newsletters as $newsletter) {
|
||||
$html .= '<li>'.
|
||||
'<span class="mailpoet_archive_date">'.
|
||||
apply_filters('mailpoet_archive_date', $newsletter).
|
||||
'</span>
|
||||
<span class="mailpoet_archive_subject">'.
|
||||
apply_filters('mailpoet_archive_subject', $newsletter).
|
||||
'</span>
|
||||
</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
function renderArchiveDate($newsletter) {
|
||||
return date_i18n(
|
||||
get_option('date_format'),
|
||||
strtotime($newsletter->created_at)
|
||||
);
|
||||
}
|
||||
|
||||
function renderArchiveSubject($newsletter) {
|
||||
return '<a href="TODO" target="_blank" title="'
|
||||
.esc_attr(__('Preview in new tab')).'">'
|
||||
.esc_attr($newsletter->subject).
|
||||
'</a>';
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
@ -10,30 +9,17 @@ class Widget {
|
||||
}
|
||||
|
||||
function init() {
|
||||
add_action('widgets_init', array($this, 'registerWidget'));
|
||||
$this->registerWidget();
|
||||
|
||||
if(!is_admin()) {
|
||||
//$this->setupActions();
|
||||
add_action('widgets_init', array($this, 'setupDependencies'));
|
||||
$this->setupDependencies();
|
||||
} else {
|
||||
add_action('widgets_init', array($this, 'setupAdminDependencies'));
|
||||
$this->setupAdminDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
function registerWidget() {
|
||||
register_widget('\MailPoet\Form\Widget');
|
||||
|
||||
// subscribers count shortcode
|
||||
add_shortcode('mailpoet_subscribers_count', array(
|
||||
$this, 'getSubscribersCount'
|
||||
));
|
||||
add_shortcode('wysija_subscribers_count', array(
|
||||
$this, 'getSubscribersCount'
|
||||
));
|
||||
}
|
||||
|
||||
function getSubscribersCount($params) {
|
||||
return Subscriber::filter('subscribed')->count();
|
||||
}
|
||||
|
||||
function setupDependencies() {
|
||||
@ -82,6 +68,9 @@ class Widget {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: extract this method into an Initializer
|
||||
// - the "ajax" part might probably be useless
|
||||
// - the "post" (non-ajax) part needs to be redone properly
|
||||
function setupActions() {
|
||||
// ajax requests
|
||||
add_action(
|
||||
|
78
lib/Cron/CronHelper.php
Normal file
78
lib/Cron/CronHelper.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class CronHelper {
|
||||
const daemon_execution_limit = 20;
|
||||
const daemon_execution_timeout = 35;
|
||||
const daemon_request_timeout = 2;
|
||||
|
||||
static function createDaemon($token) {
|
||||
$daemon = array(
|
||||
'status' => 'starting',
|
||||
'counter' => 0,
|
||||
'token' => $token
|
||||
);
|
||||
self::saveDaemon($daemon);
|
||||
return $daemon;
|
||||
}
|
||||
|
||||
static function getDaemon() {
|
||||
return Setting::getValue('cron_daemon');
|
||||
}
|
||||
|
||||
static function saveDaemon($daemon) {
|
||||
$daemon['updated_at'] = time();
|
||||
return Setting::setValue(
|
||||
'cron_daemon',
|
||||
$daemon
|
||||
);
|
||||
}
|
||||
|
||||
static function createToken() {
|
||||
return Security::generateRandomString();
|
||||
}
|
||||
|
||||
static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
|
||||
$payload = serialize(array('token' => $token));
|
||||
$url = '/?mailpoet-api§ion=queue&action=run&request_payload=' .
|
||||
base64_encode($payload);
|
||||
$args = array(
|
||||
'timeout' => $timeout,
|
||||
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
|
||||
);
|
||||
$result = wp_remote_get(
|
||||
self::getSiteUrl() . $url,
|
||||
$args
|
||||
);
|
||||
return wp_remote_retrieve_body($result);
|
||||
}
|
||||
|
||||
private static function getSiteUrl() {
|
||||
// additional check for some sites running on 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
|
||||
throw new \Exception(__('Site URL is unreachable.'));
|
||||
}
|
||||
|
||||
static function checkExecutionTimer($timer) {
|
||||
$elapsed_time = microtime(true) - $timer;
|
||||
if($elapsed_time >= self::daemon_execution_limit) {
|
||||
throw new \Exception(__('Maximum execution time reached.'));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +1,80 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Cron\Workers\Scheduler;
|
||||
use MailPoet\Cron\Workers\SendingQueue;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\Models\Newsletter;
|
||||
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Daemon {
|
||||
function __construct($payload = array()) {
|
||||
public $daemon;
|
||||
public $request_payload;
|
||||
public $refreshed_token;
|
||||
const daemon_request_timeout = 5;
|
||||
private $timer;
|
||||
|
||||
function __construct($request_payload = array()) {
|
||||
set_time_limit(0);
|
||||
ignore_user_abort();
|
||||
list ($this->daemon, $this->daemonData) = $this->getDaemon();
|
||||
$this->refreshedToken = $this->refreshToken();
|
||||
$this->payload = $payload;
|
||||
$this->daemon = CronHelper::getDaemon();
|
||||
$this->token = CronHelper::createToken();
|
||||
$this->request_payload = $request_payload;
|
||||
$this->timer = microtime(true);
|
||||
}
|
||||
|
||||
function start() {
|
||||
if(!isset($this->payload['session'])) {
|
||||
$this->abortWithError('missing session ID');
|
||||
}
|
||||
$this->manageSession('start');
|
||||
$daemon = $this->daemon;
|
||||
$daemonData = $this->daemonData;
|
||||
if(!$daemon) {
|
||||
$daemon = Setting::create();
|
||||
$daemon->name = 'cron_daemon';
|
||||
$daemonData = array(
|
||||
'status' => null,
|
||||
'counter' => 0
|
||||
);
|
||||
$daemon->value = json_encode($daemonData);
|
||||
$daemon->save();
|
||||
}
|
||||
if($daemonData['status'] !== 'started') {
|
||||
$_SESSION['cron_daemon'] = 'started';
|
||||
$daemonData['status'] = 'started';
|
||||
$daemonData['token'] = $this->refreshedToken;
|
||||
$_SESSION['cron_daemon'] = array('result' => true);
|
||||
$this->manageSession('end');
|
||||
$daemon->value = json_encode($daemonData);
|
||||
$daemon->save();
|
||||
$this->callSelf();
|
||||
} else {
|
||||
$_SESSION['cron_daemon'] = array(
|
||||
'result' => false,
|
||||
'error' => 'already started'
|
||||
);
|
||||
}
|
||||
$this->manageSession('end');
|
||||
}
|
||||
|
||||
function run() {
|
||||
if(!$this->daemon || $this->daemonData['status'] !== 'started') {
|
||||
$this->abortWithError('not running');
|
||||
$daemon = $this->daemon;
|
||||
if(!$daemon) {
|
||||
$this->abortWithError(__('Daemon does not exist.'));
|
||||
}
|
||||
if(!isset($this->payload['token']) ||
|
||||
$this->payload['token'] !== $this->daemonData['token']
|
||||
if(!isset($this->request_payload['token']) ||
|
||||
$this->request_payload['token'] !== $daemon['token']
|
||||
) {
|
||||
$this->abortWithError('invalid token');
|
||||
$this->abortWithError(__('Invalid or missing token.'));
|
||||
}
|
||||
|
||||
$this->abortIfStopped($daemon);
|
||||
try {
|
||||
$sendingQueue = new SendingQueue($this->timer);
|
||||
$sendingQueue->process();
|
||||
} catch(Exception $e) {
|
||||
do_action('mailpoet_cron_worker', $this->timer);
|
||||
} catch(\Exception $e) {
|
||||
}
|
||||
|
||||
$elapsedTime = microtime(true) - $this->timer;
|
||||
if($elapsedTime < 30) {
|
||||
sleep(30 - $elapsedTime);
|
||||
$elapsed_time = microtime(true) - $this->timer;
|
||||
if($elapsed_time < CronHelper::daemon_execution_limit) {
|
||||
sleep(CronHelper::daemon_execution_limit - $elapsed_time);
|
||||
}
|
||||
|
||||
// after each execution, read daemon in case it's status was modified
|
||||
list($daemon, $daemonData) = $this->getDaemon();
|
||||
$daemonData['counter']++;
|
||||
$daemonData['token'] = $this->refreshedToken;
|
||||
$daemon->value = json_encode($daemonData);
|
||||
$daemon->save();
|
||||
if($daemonData['status'] === 'strated') $this->callSelf();
|
||||
// after each execution, re-read daemon data in case it was deleted or
|
||||
// its status has changed
|
||||
$daemon = CronHelper::getDaemon();
|
||||
if(!$daemon || $daemon['token'] !== $this->request_payload['token']) {
|
||||
exit;
|
||||
}
|
||||
$daemon['counter']++;
|
||||
$this->abortIfStopped($daemon);
|
||||
if($daemon['status'] === 'starting') {
|
||||
$daemon['status'] = 'started';
|
||||
}
|
||||
$daemon['token'] = $this->token;
|
||||
CronHelper::saveDaemon($daemon);
|
||||
$this->callSelf();
|
||||
}
|
||||
|
||||
function getDaemon() {
|
||||
$daemon = Setting::where('name', 'cron_daemon')
|
||||
->findOne();
|
||||
return array(
|
||||
($daemon) ? $daemon : null,
|
||||
($daemon) ? json_decode($daemon->value, true) : null
|
||||
);
|
||||
}
|
||||
|
||||
function refreshToken() {
|
||||
return Security::generateRandomString();
|
||||
}
|
||||
|
||||
function manageSession($action) {
|
||||
switch($action) {
|
||||
case 'start':
|
||||
if(session_id()) {
|
||||
session_write_close();
|
||||
}
|
||||
session_id($this->payload['session']);
|
||||
session_start();
|
||||
break;
|
||||
case 'end':
|
||||
session_write_close();
|
||||
break;
|
||||
function abortIfStopped($daemon) {
|
||||
if($daemon['status'] === 'stopped') exit;
|
||||
if($daemon['status'] === 'stopping') {
|
||||
$daemon['status'] = 'stopped';
|
||||
CronHelper::saveDaemon($daemon);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function abortWithError($message) {
|
||||
exit('[mailpoet_cron_error:' . base64_encode($message) . ']');
|
||||
}
|
||||
|
||||
function callSelf() {
|
||||
$payload = json_encode(array('token' => $this->refreshedToken));
|
||||
Supervisor::getRemoteUrl(
|
||||
'/?mailpoet-api§ion=queue&action=run&payload=' . urlencode($payload)
|
||||
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
function abortWithError($error) {
|
||||
wp_send_json(
|
||||
array(
|
||||
'result' => false,
|
||||
'error' => $error
|
||||
));
|
||||
CronHelper::accessDaemon($this->token, self::daemon_request_timeout);
|
||||
exit;
|
||||
}
|
||||
}
|
@ -1,84 +1,94 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Models\Setting;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Supervisor {
|
||||
function __construct($forceStart = false) {
|
||||
$this->forceStart = $forceStart;
|
||||
if(!Env::isPluginActivated()) {
|
||||
throw new \Exception('Database has not been configured.');
|
||||
}
|
||||
list ($this->daemon, $this->daemonData) = $this->getDaemon();
|
||||
public $daemon;
|
||||
public $token;
|
||||
public $force_run;
|
||||
|
||||
function __construct($force_run = false) {
|
||||
$this->daemon = CronHelper::getDaemon();
|
||||
$this->token = CronHelper::createToken();
|
||||
$this->force_run = $force_run;
|
||||
}
|
||||
|
||||
function checkDaemon() {
|
||||
if(!$this->daemon) {
|
||||
return $this->startDaemon();
|
||||
$daemon = $this->daemon;
|
||||
if(!$daemon) {
|
||||
$daemon = CronHelper::createDaemon($this->token);
|
||||
return $this->runDaemon($daemon);
|
||||
}
|
||||
if(!$this->forceStart && $this->daemonData['status'] === 'stopped') {
|
||||
return;
|
||||
// if the daemon is stopped, return its status and do nothing
|
||||
if(!$this->force_run &&
|
||||
isset($daemon['status']) &&
|
||||
$daemon['status'] === 'stopped'
|
||||
) {
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
|
||||
}
|
||||
$currentTime = Carbon::now('UTC');
|
||||
$lastUpdateTime = Carbon::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$this->daemon->updated_at, 'UTC'
|
||||
);
|
||||
$timeSinceLastStart = $currentTime->diffInSeconds($lastUpdateTime);
|
||||
if($timeSinceLastStart < 40) return;
|
||||
$this->daemonData['status'] = null;
|
||||
$this->daemon->value = json_encode($this->daemonData);
|
||||
$this->daemon->save();
|
||||
return $this->startDaemon();
|
||||
$elapsed_time = time() - (int) $daemon['updated_at'];
|
||||
// if it's been less than 40 seconds since last execution and we're not
|
||||
// force-running the daemon, return its status and do nothing
|
||||
if($elapsed_time < CronHelper::daemon_execution_timeout && !$this->force_run) {
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
}
|
||||
// if it's been less than 40 seconds since last execution, we are
|
||||
// force-running the daemon and it's either being started or stopped,
|
||||
// return its status and do nothing
|
||||
elseif($elapsed_time < CronHelper::daemon_execution_timeout &&
|
||||
$this->force_run &&
|
||||
in_array($daemon['status'], array(
|
||||
'stopping',
|
||||
'starting'
|
||||
))
|
||||
) {
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
}
|
||||
// re-create (restart) daemon
|
||||
CronHelper::createDaemon($this->token);
|
||||
return $this->runDaemon();
|
||||
}
|
||||
|
||||
function startDaemon() {
|
||||
if(!session_id()) session_start();
|
||||
$sessionId = session_id();
|
||||
session_write_close();
|
||||
$_SESSION['cron_daemon'] = null;
|
||||
$payload = json_encode(array('session' => $sessionId));
|
||||
self::getRemoteUrl(
|
||||
'/?mailpoet-api§ion=queue&action=start&payload=' . urlencode($payload)
|
||||
);
|
||||
session_start();
|
||||
$daemonStatus = $_SESSION['cron_daemon'];
|
||||
unset($_SESSION['daemon']);
|
||||
session_write_close();
|
||||
return $daemonStatus;
|
||||
function runDaemon() {
|
||||
$request = CronHelper::accessDaemon($this->token);
|
||||
preg_match('/\[(mailpoet_cron_error:.*?)\]/i', $request, $status);
|
||||
$daemon = CronHelper::getDaemon();
|
||||
if(!empty($status) || !$daemon) {
|
||||
if(!$daemon) {
|
||||
$message = __('Daemon failed to run.');
|
||||
} else {
|
||||
list(, $message) = explode(':', $status[0]);
|
||||
$message = base64_decode($message);
|
||||
}
|
||||
return $this->formatResultMessage(
|
||||
false,
|
||||
$message
|
||||
);
|
||||
}
|
||||
return $this->formatDaemonStatusMessage($daemon['status']);
|
||||
}
|
||||
|
||||
function getDaemon() {
|
||||
$daemon = Setting::where('name', 'cron_daemon')
|
||||
->findOne();
|
||||
$daemonData = ($daemon) ? json_decode($daemon->value, true) : false;
|
||||
return array(
|
||||
$daemon,
|
||||
$daemonData
|
||||
private function formatDaemonStatusMessage($status) {
|
||||
return $this->formatResultMessage(
|
||||
true,
|
||||
sprintf(
|
||||
__('Daemon is currently %s.'),
|
||||
__($status)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static function getRemoteUrl($url) {
|
||||
$args = array(
|
||||
'timeout' => 1,
|
||||
'user-agent' => 'MailPoet (www.mailpoet.com)'
|
||||
private function formatResultMessage($result, $message) {
|
||||
$formattedResult = array(
|
||||
'result' => $result
|
||||
);
|
||||
wp_remote_get(
|
||||
self::getSiteUrl() . $url,
|
||||
$args
|
||||
);
|
||||
}
|
||||
|
||||
static function getSiteUrl() {
|
||||
if(preg_match('!:\d+/!', site_url())) return site_url();
|
||||
preg_match('!http://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
|
||||
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
|
||||
return ($fp) ?
|
||||
site_url() :
|
||||
preg_replace('/(?=:\d+):\d+/', '$1', site_url());
|
||||
if(!$result) {
|
||||
$formattedResult['errors'] = array($message);
|
||||
} else {
|
||||
$formattedResult['message'] = $message;
|
||||
}
|
||||
return $formattedResult;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user