Compare commits
356 Commits
3.0.0-beta
...
3.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
5293589eea | |||
07f4c0bc1e | |||
7d9a072545 | |||
67ffcb5485 | |||
02098a3cf0 | |||
eab8065154 | |||
fb8ecef1c3 | |||
14955e5022 | |||
e83c0ff0bd | |||
09db1aac22 | |||
5fa1eb643a | |||
898913a517 | |||
3a4b364bcd | |||
d373d10f6c | |||
c07e1eff3c | |||
f5e985baa4 | |||
2c93a105cc | |||
cbf0a7684f | |||
2632feba31 | |||
c96bc755c7 | |||
bbad772d7a | |||
520a3c43be | |||
eb70df1466 | |||
5977b8b4bc | |||
2bb7d95e37 | |||
c64959dce0 | |||
ead0792b32 | |||
7bd52d456c | |||
8517896660 | |||
c78933f7c4 | |||
707d5efec1 | |||
acfb3aefba | |||
cb6f4046a2 | |||
667aa91581 | |||
b7f7dc6728 | |||
ad1e8feb23 | |||
486b382c88 | |||
472be3b071 | |||
37a9fd9e1b | |||
c984ac7a66 | |||
a1ea56f505 | |||
dd7f959731 | |||
09f4f2e78a | |||
502250a1a3 | |||
48e37f6797 | |||
420058a86d | |||
6c777ca074 | |||
a481debb77 | |||
c91f8ccc7a | |||
6a2b5e28c2 | |||
bc51b6efc8 | |||
152edda03f | |||
5029b73027 | |||
35c25d3337 | |||
ae25e95d51 | |||
5b8d0c63a1 | |||
94fb8c6096 | |||
93ef1d0197 | |||
501d00b0cc | |||
9af3dc1f9d | |||
3705ed7da0 | |||
5b99e66d8d | |||
cbedd5ff40 | |||
bfcd6f10fc | |||
79362e9955 | |||
fcf272b44a | |||
b80683a9a1 | |||
5349f3a59a | |||
95072a9ac5 | |||
8c372b0909 | |||
580dd38b3a | |||
47d4e98aae | |||
7ebb7bac17 | |||
6cbce2fc97 | |||
e8a950f32c | |||
4f722ecd8a | |||
478359f9ff | |||
a1720a5cf1 | |||
3f0ef3ded7 | |||
dcb25c1a6b | |||
c5dd575324 | |||
6eca26a4e2 | |||
e10fa065bd | |||
49673fabbd | |||
1c1a210542 | |||
30277d92cd | |||
fb940065ea | |||
afa06342a5 | |||
03d2ff5f26 | |||
ec71dff40d | |||
58faf64a5c | |||
65ff14a81d | |||
f7efe44f09 | |||
cf22e81ae1 | |||
7aa0f21d11 | |||
2e31e3d37c | |||
3e988b7a56 | |||
ce3eb06924 | |||
a37ff8d398 | |||
d0bdb1a47b | |||
855f2a55d4 | |||
fc7ec9bded | |||
028de860a2 | |||
5af91d028d | |||
a4bcf870bb | |||
e06f2f5f0b | |||
c101645d93 | |||
b8904c2d51 | |||
099db4e1c8 | |||
cdf36ccb20 | |||
79b6ab1d15 | |||
95114774da | |||
7f566fb672 | |||
d27968a215 | |||
344990d59e | |||
ea831ef160 | |||
8314b05fce | |||
fd33cc7068 | |||
92e4cc6a24 | |||
dd4bebb570 | |||
99aed2cb01 | |||
92616063ec | |||
c56b56f4aa | |||
33d6533c64 | |||
55d7a0dd01 | |||
8b2ac99eda | |||
dba21c68fd | |||
5b40652737 | |||
7f0396747d | |||
e9dfff8e66 | |||
040c4da6c3 | |||
80a237504d | |||
4e2e09ea24 | |||
87b9fbdc16 | |||
a071a14eec | |||
5ae006b10f | |||
9d21ebd26e | |||
fcff6de3c3 | |||
3d2168856d | |||
a6eb1b06da | |||
21d0c3518e | |||
3532a3c8e9 | |||
79cba4cace | |||
a5dee8da12 | |||
3783384ea6 | |||
766c0dfcfc | |||
83e9de8e95 | |||
0a512f6349 | |||
a4c1095db7 | |||
87a6c7100e | |||
fc51d5f98c | |||
a1b3aaf1f8 | |||
3a1bf88c22 | |||
bd39c34f03 | |||
73121c2ca5 | |||
5e23fa4295 | |||
5e34bbf9d5 | |||
cedd94550f | |||
8b13889c7a | |||
3c7ac5488a | |||
398d7d3d80 | |||
b727ba423e | |||
45b9550293 | |||
d2e520e2fd | |||
b9c3ae97cd | |||
b90c0b173b | |||
f498f4df0c | |||
2f10f89fc5 | |||
a49f9d9c80 | |||
e71e23bbb5 | |||
adc86ef247 | |||
765b2bad21 | |||
2354cac719 | |||
7f509f66ff | |||
d8ff251c71 | |||
12979cc2c0 | |||
e974c06a89 | |||
f2ceff8252 | |||
cd5f3165c7 | |||
6e700b0cfa | |||
5b41fc212c | |||
2b7a5452b8 | |||
cfed133fb6 | |||
0beff9a090 | |||
d6e707fb85 | |||
a3e8d47199 | |||
cab3f3a96e | |||
5f0d4abe7f | |||
ff5f87eeca | |||
e85b969e11 | |||
2eb98905b6 | |||
ac1274c6fd | |||
94f91afce1 | |||
73d5fb8cff | |||
90b2b46db4 | |||
f2bf61240a | |||
3f151fd235 | |||
7598363cae | |||
4b1f216cd3 | |||
3d5f13a2b8 | |||
98eab956e9 | |||
a7260cba3d | |||
787e022382 | |||
d8e1c76155 | |||
3cb08e3c09 | |||
0474985866 | |||
8d15ef6d06 | |||
0fbc7fb7eb | |||
1379bdbbeb | |||
64d3e659a4 | |||
19458546a0 | |||
bba7460423 | |||
956fdd5cff | |||
a0289775cb | |||
4c785902bc | |||
e29ae4d7c9 | |||
1ea915017a | |||
6441c781a5 | |||
589c54e205 | |||
e10b99eaac | |||
0316f3ea3e | |||
166fef899f | |||
4e850408fc | |||
6e2494831c | |||
38a7d8f80a | |||
abfebc8643 | |||
40a3487d3d | |||
a93865e594 | |||
4e76286b44 | |||
fbe57e96c6 | |||
950bfb04d6 | |||
6d43b7b6a9 | |||
e1991deafd | |||
2f1b31aeb2 | |||
ca29eefd7f | |||
1421407a23 | |||
36e4bf468d | |||
5cd3917f4d | |||
586470e8f9 | |||
b02e9f5ab3 | |||
4a538e677d | |||
cc2fdbe5be | |||
3833688115 | |||
1639741e55 | |||
ab0d573a66 | |||
26c582b19f | |||
3bc53f9f09 | |||
bb220baf6a | |||
121a78f42a | |||
4257aa634e | |||
95ff83557f | |||
e9070de9c4 | |||
72aa087411 | |||
fbc0a3ad8d | |||
afedc409f5 | |||
0360f16dc8 | |||
f4800dbbae | |||
15ddc8454e | |||
f8df4de711 | |||
a0cb18e1a1 | |||
509ec7d3d3 | |||
aa2416f353 | |||
167a605658 | |||
592f11bd5f | |||
92b128039a | |||
5efe611b2d | |||
477e2737b1 | |||
dc8bacc27d | |||
0b8c787cda | |||
4f5c464659 | |||
4f432645b1 | |||
5fa7930896 | |||
f9efd536d9 | |||
6a65ff5e5d | |||
b549f83422 | |||
a9c80c031f | |||
405bea3049 | |||
6954acd0b3 | |||
efd15d5d18 | |||
6566622167 | |||
8157780b68 | |||
975546915e | |||
319d591662 | |||
1dd6c91529 | |||
c4f0426775 | |||
53f5a122bd | |||
a7142ed21b | |||
771a1bfc44 | |||
53169bba78 | |||
e3b8c1836b | |||
a4b091dc32 | |||
448c9ddaa8 | |||
ac574acf8e | |||
aa15b9420a | |||
2b7f5c321e | |||
bee9bfcfcc | |||
b7d73dcfaa | |||
5b4fa4ea2b | |||
12e5fe77de | |||
2dca10c539 | |||
ceba5b3d0b | |||
c05cf3cad4 | |||
d6f5a39829 | |||
30d67508cb | |||
63b8d892f7 | |||
10137d8551 | |||
9ef74e0951 | |||
89ff93958f | |||
8d870e85eb | |||
0cdb426712 | |||
b9f7a5673f | |||
7ffbf6c378 | |||
3a9c006cf9 | |||
a9edb383b4 | |||
ec23a73edb | |||
10a164ee0c | |||
37fcd5699b | |||
66d969cc2f | |||
9d358f74dd | |||
57e00e3097 | |||
53afbea6ec | |||
2c2c0b3db4 | |||
e235ee66eb | |||
0ef430567b | |||
74aef73f75 | |||
99eb72428f | |||
065b160155 | |||
6811d8e38d | |||
5f75efddf1 | |||
822a7ac5f5 | |||
06e1ac9bb5 | |||
a3530c3367 | |||
ec35bfb2d4 | |||
ed3e46bebb | |||
87b270482b | |||
d22ba55858 | |||
835f25cc82 | |||
11944283b0 | |||
dc704a92de | |||
dca1e9e1a7 | |||
00781be077 | |||
ac80148f5b | |||
ff36833270 | |||
612c7d76a0 | |||
32097b4512 | |||
d686f75222 | |||
bcc01df0b8 | |||
ee12f4d304 | |||
16c1607850 | |||
e2864e2243 | |||
16dc81150d | |||
ed4d3d52ed | |||
37a6a74b6e | |||
136a531047 | |||
bef0097f5b | |||
a0d2be50e8 |
43
.gitignore
vendored
43
.gitignore
vendored
@ -1,21 +1,22 @@
|
||||
.DS_Store
|
||||
TODO
|
||||
composer.phar
|
||||
/vendor
|
||||
tests/_output/*
|
||||
tests/acceptance.suite.yml
|
||||
tests/_support/_generated/*
|
||||
node_modules
|
||||
.env
|
||||
npm-debug.log
|
||||
!tasks/**
|
||||
/views/cache/**
|
||||
temp
|
||||
.idea
|
||||
mailpoet.zip
|
||||
tests/javascript/testBundles
|
||||
assets/css/*.css
|
||||
assets/js/*.js
|
||||
.vagrant
|
||||
lang
|
||||
.mp_svn
|
||||
.DS_Store
|
||||
TODO
|
||||
composer.phar
|
||||
/vendor
|
||||
tests/_output/*
|
||||
tests/acceptance.suite.yml
|
||||
tests/_support/_generated/*
|
||||
node_modules
|
||||
.env
|
||||
npm-debug.log
|
||||
!tasks/**
|
||||
/views/cache/**
|
||||
temp
|
||||
.idea
|
||||
mailpoet.zip
|
||||
tests/javascript/testBundles
|
||||
assets/css/*.css
|
||||
assets/js/*.js
|
||||
.vagrant
|
||||
lang
|
||||
.mp_svn
|
||||
/nbproject/
|
@ -6,4 +6,4 @@ source_file = lang/mailpoet.pot
|
||||
file_filter = lang/mailpoet-<lang>.po
|
||||
source_lang = en_US
|
||||
type = PO
|
||||
minimum_perc = 100
|
||||
minimum_perc = 75
|
||||
|
@ -5,30 +5,32 @@
|
||||
- CamelCase for classes.
|
||||
- camelCase for methods.
|
||||
- snake_case for variables and class properties.
|
||||
- Max line length at 80 chars.
|
||||
- Classes can be no longer than 100 LOC.
|
||||
- Methods can be no longer than 5 LOC.
|
||||
- Pass no more than 4 parameters/hash keys into a method.
|
||||
- Composition over Inheritance.
|
||||
- Comments are a code smell.
|
||||
- Routes can instantiate only one object.
|
||||
- Comments are a code smell. If you need to use a comment - see if same idea can be achieved by more clearly expressing code.
|
||||
- Require other classes with 'use' at the beginning of the class file.
|
||||
- Do not specify 'public' if method is public, it's implicit.
|
||||
- Always use guard clauses.
|
||||
- Ensure compatibility with PHP 5.3 and newer versions.
|
||||
- Cover your code in tests.
|
||||
|
||||
Recommendations:
|
||||
- Max line length at 80 chars.
|
||||
- Keep classes under 100 LOC.
|
||||
- Keep methods under 10 LOC.
|
||||
- Pass no more than 4 parameters/hash keys into a method.
|
||||
- Keep Pull Requests small, under 100 LOC changed.
|
||||
|
||||
## Git flow.
|
||||
- Do not commit to master.
|
||||
- Open a short-living feature branch.
|
||||
- Open a pull request.
|
||||
- Add close #issue in pull request description.
|
||||
- Add Jira issue reference in the title of the Pull Request.
|
||||
- Work on the pull request.
|
||||
- Wait for confirmation before merging to master.
|
||||
- No one will accept a pull request that doesn't have 100% test coverage.
|
||||
- Wait for review and confirmation from another developer before merging to master.
|
||||
- Commit title no more than 80 chars, empty line after.
|
||||
- Commit description as long as you want, 80 chars wrap.
|
||||
- Keep the GitHub open issues count at less than 10.
|
||||
|
||||
## Issues creation.
|
||||
- Issues are managed on Jira.
|
||||
- Discuss issues on public Slack chats, discuss code in pull requests.
|
||||
- Organize features on Trello.
|
||||
- Open a small github issue only when it has been discussed.
|
||||
- Open a small Jira issue only when it has been discussed.
|
||||
|
23
README.md
23
README.md
@ -46,11 +46,21 @@ $ ./do compile:all
|
||||
$ ./do test:unit
|
||||
```
|
||||
|
||||
- JS tests (using Mocha):
|
||||
```sh
|
||||
$ ./do test:javascript
|
||||
```
|
||||
|
||||
- Debug tests:
|
||||
```sh
|
||||
$ ./do test:debug
|
||||
```
|
||||
|
||||
- Code linters and quality checkers:
|
||||
```sh
|
||||
$ ./do qa
|
||||
```
|
||||
|
||||
# CSS
|
||||
- [Stylus](https://learnboost.github.io/stylus/)
|
||||
- [Nib extension](http://tj.github.io/nib/)
|
||||
@ -109,6 +119,7 @@ Once javascript is compiled with `./do compile:javascript`, your module will be
|
||||
```php
|
||||
__()
|
||||
_n()
|
||||
_x()
|
||||
```
|
||||
|
||||
```html
|
||||
@ -144,17 +155,23 @@ Finally , a `WP_TRANSIFEX_API_TOKEN` environment variable should be initialized
|
||||
|
||||
# Publish
|
||||
|
||||
Before you run a publishing command, you need to:
|
||||
The `publish` command currently does the following:
|
||||
* Pushes translations POT file to Transifex;
|
||||
* Publishes the release in SVN.
|
||||
|
||||
Before you run it, you need to:
|
||||
1. Ensure there is an up-to-date local copy of MailPoet SVN repository in `.mp_svn` directory by running `./do svn:checkout`.
|
||||
2. Have all your features merged in Git `master`, your `mailpoet.php` and `readme.txt` tagged with a new version.
|
||||
3. Run `./build.sh` to produce a `mailpoet.zip` distributable archive.
|
||||
|
||||
Everything's ready? Then run `./do svn:publish`.
|
||||
Everything's ready? Then run `./do publish`.
|
||||
If the job goes fine, you'll get a message like this:
|
||||
```
|
||||
Go to '.mp_svn' and run 'svn ci -m "Release 3.0.0-beta.9"' to publish the
|
||||
release
|
||||
|
||||
Run 'svn copy ...' to tag the release
|
||||
```
|
||||
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
|
||||
|
||||
If you're confident, execute `./do svn:publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
|
||||
If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
|
||||
|
56
RoboFile.php
56
RoboFile.php
@ -104,10 +104,24 @@ class RoboFile extends \Robo\Tasks {
|
||||
);
|
||||
}
|
||||
|
||||
function pushpot() {
|
||||
return $this->collectionBuilder()
|
||||
->addCode(array($this, 'txinit'))
|
||||
->taskExec('tx push -s')
|
||||
->run();
|
||||
}
|
||||
|
||||
function packtranslations() {
|
||||
return $this->collectionBuilder()
|
||||
->addCode(array($this, 'txinit'))
|
||||
->taskExec('./tasks/pack_translations.sh')
|
||||
->run();
|
||||
}
|
||||
|
||||
function txinit() {
|
||||
// Define WP_TRANSIFEX_API_TOKEN env. variable
|
||||
$this->loadEnv();
|
||||
return $this->_exec('./tasks/pack_translations.sh');
|
||||
return $this->_exec('./tasks/transifex_init.sh');
|
||||
}
|
||||
|
||||
function testUnit($opts=['file' => null, 'xml' => false]) {
|
||||
@ -199,7 +213,26 @@ class RoboFile extends \Robo\Tasks {
|
||||
}
|
||||
|
||||
function svnCheckout() {
|
||||
return $this->_exec('svn co https://plugins.svn.wordpress.org/mailpoet/ .mp_svn');
|
||||
$svn_dir = ".mp_svn";
|
||||
|
||||
$collection = $this->collectionBuilder();
|
||||
|
||||
// Clean up the SVN dir for faster shallow checkout
|
||||
if(file_exists($svn_dir)) {
|
||||
$collection->taskExecStack()
|
||||
->exec('rm -rf ' . $svn_dir);
|
||||
}
|
||||
|
||||
$collection->taskFileSystemStack()
|
||||
->mkdir($svn_dir);
|
||||
|
||||
return $collection->taskExecStack()
|
||||
->stopOnFail()
|
||||
->dir($svn_dir)
|
||||
->exec('svn co https://plugins.svn.wordpress.org/mailpoet/ -N .')
|
||||
->exec('svn up trunk')
|
||||
->exec('svn up assets')
|
||||
->run();
|
||||
}
|
||||
|
||||
function svnPublish($opts = ['force' => false]) {
|
||||
@ -279,15 +312,15 @@ class RoboFile extends \Robo\Tasks {
|
||||
// Remove files from SVN repo that have already been removed locally
|
||||
->exec("svn st | grep ^! | awk '$awkCmd' | xargs $xargsFlag svn rm")
|
||||
// Recursively add files to SVN that haven't been added yet
|
||||
->exec("svn add --force * --auto-props --parents --depth infinity -q")
|
||||
// Tag the release
|
||||
->exec("svn cp trunk tags/$plugin_version");
|
||||
->exec("svn add --force * --auto-props --parents --depth infinity -q");
|
||||
|
||||
$result = $collection->run();
|
||||
|
||||
if($result->wasSuccessful()) {
|
||||
// Run or suggest release command depending on a flag
|
||||
$repo_url = "https://plugins.svn.wordpress.org/$plugin_dist_name";
|
||||
$release_cmd = "svn ci -m \"Release $plugin_version\"";
|
||||
$tag_cmd = "svn copy $repo_url/trunk $repo_url/tags/$plugin_version -m \"Tag $plugin_version\"";
|
||||
if(!empty($opts['force'])) {
|
||||
$svn_login = getenv('WP_SVN_USERNAME');
|
||||
$svn_password = getenv('WP_SVN_PASSWORD');
|
||||
@ -300,17 +333,30 @@ class RoboFile extends \Robo\Tasks {
|
||||
->stopOnFail()
|
||||
->dir($svn_dir)
|
||||
->exec($release_cmd)
|
||||
->exec($tag_cmd)
|
||||
->run();
|
||||
} else {
|
||||
$this->yell(
|
||||
"Go to '$svn_dir' and run '$release_cmd' to publish the release"
|
||||
);
|
||||
$this->yell(
|
||||
"Run '$tag_cmd' to tag the release"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function publish($opts = ['force' => false]) {
|
||||
return $this->collectionBuilder()
|
||||
->addCode(array($this, 'pushpot'))
|
||||
->addCode(function () use ($opts) {
|
||||
return $this->svnPublish($opts);
|
||||
})
|
||||
->run();
|
||||
}
|
||||
|
||||
protected function loadEnv() {
|
||||
$dotenv = new Dotenv\Dotenv(__DIR__);
|
||||
$dotenv->load();
|
||||
|
@ -22,3 +22,5 @@
|
||||
@require 'progress_bar'
|
||||
|
||||
@require 'subscribers'
|
||||
|
||||
@require 'mp2migrator'
|
||||
|
@ -1,3 +1,53 @@
|
||||
$excellent-badge-color = #2993ab
|
||||
$good-badge-color = #f0b849
|
||||
$bad-badge-color = #d54e21
|
||||
$green-badge-color = #55bd56
|
||||
|
||||
#newsletters_container
|
||||
h2.nav-tab-wrapper
|
||||
margin-bottom: 1rem
|
||||
margin-bottom: 1rem
|
||||
|
||||
.mailpoet_stats_text
|
||||
font-size: 14px
|
||||
font-weight: 600;
|
||||
|
||||
.mailpoet_stat
|
||||
|
||||
&_excellent
|
||||
color: $excellent-badge-color
|
||||
|
||||
&_good
|
||||
color: $good-badge-color
|
||||
|
||||
&_bad
|
||||
color: $bad-badge-color
|
||||
|
||||
&_hidden
|
||||
display: none
|
||||
|
||||
&_link_small
|
||||
text-decoration: underline !important
|
||||
font-size: 0.75rem
|
||||
|
||||
.mailpoet_badge
|
||||
padding: 4px 6px 3px 6px
|
||||
color: #FFFFFF
|
||||
margin-right: 4px
|
||||
text-transform: uppercase
|
||||
font-size: 0.5625rem
|
||||
font-weight: 500
|
||||
border-radius: 3px
|
||||
letter-spacing: 1px
|
||||
vertical-align: middle
|
||||
|
||||
&_excellent
|
||||
background: $excellent-badge-color
|
||||
|
||||
&_good
|
||||
background: $good-badge-color
|
||||
|
||||
&_bad
|
||||
background: $bad-badge-color
|
||||
|
||||
&_green
|
||||
background: $green-badge-color
|
||||
|
33
assets/css/src/mp2migrator.styl
Normal file
33
assets/css/src/mp2migrator.styl
Normal file
@ -0,0 +1,33 @@
|
||||
#logger
|
||||
width: 100%
|
||||
height: 300px
|
||||
background-color: transparent
|
||||
border: 0
|
||||
border-top: 1px #aba9a9 solid
|
||||
padding: 2px
|
||||
overflow: scroll
|
||||
resize: both
|
||||
font-size: 0.85em
|
||||
margin-top: 20px
|
||||
|
||||
#progressbar
|
||||
width: 50%
|
||||
background-color: #d8d8d8
|
||||
border-radius: 5px
|
||||
|
||||
progressbar_color = #fecf23
|
||||
progressbar_gradient_to_color = #fd9215
|
||||
|
||||
.ui-progressbar .ui-progressbar-value
|
||||
height: 100%
|
||||
background-color: progressbar_color
|
||||
background-image: linear-gradient(to bottom, progressbar_color, progressbar_gradient_to_color)
|
||||
border-radius: 3px
|
||||
box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset
|
||||
border 0
|
||||
|
||||
.mailpoet_progress_label
|
||||
font-size: 15px
|
||||
|
||||
.error_msg
|
||||
color: #f00
|
@ -4,6 +4,7 @@
|
||||
padding: 0
|
||||
width: 100%
|
||||
margin: 0
|
||||
margin-bottom: 10px
|
||||
border-radius: 5px
|
||||
position: relative
|
||||
|
||||
@ -25,5 +26,5 @@
|
||||
|
||||
.mailpoet_progress_complete
|
||||
.mailpoet_progress_bar
|
||||
background-color: #fecf23
|
||||
background-image: linear-gradient(top, #fecf23, #fd9215)
|
||||
background-color: hsla(191, 78%, 80%, 1)
|
||||
background-image: linear-gradient(top, hsla(191, 78%, 80%, 1), hsla(191, 76%, 67%, 1))
|
||||
|
@ -21,7 +21,6 @@
|
||||
h3
|
||||
text-align center
|
||||
height 54px
|
||||
line-height 54px
|
||||
font-size 1.5em
|
||||
.mailpoet_description
|
||||
font-size 14px
|
||||
@ -39,6 +38,7 @@
|
||||
font-weight bold
|
||||
.mailpoet_active
|
||||
.mailpoet_status
|
||||
background-color #088b00
|
||||
span
|
||||
visibility visible
|
||||
#mailpoet_mta_activate
|
||||
@ -52,6 +52,15 @@
|
||||
.button-secondary
|
||||
margin 0 -6px -4px 0
|
||||
|
||||
// premium key
|
||||
.mailpoet_key
|
||||
&_valid
|
||||
&::before
|
||||
content '✔ '
|
||||
&_invalid
|
||||
&::before
|
||||
content: '✗ '
|
||||
|
||||
// responsive
|
||||
@media screen and (max-width: 782px)
|
||||
.form-table th
|
||||
|
BIN
assets/img/mailpoet_logo_newsletter.png
Normal file
BIN
assets/img/mailpoet_logo_newsletter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
@ -5,6 +5,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery,
|
||||
options: {},
|
||||
defaults: {
|
||||
url: null,
|
||||
api_version: null,
|
||||
endpoint: null,
|
||||
action: null,
|
||||
token: null,
|
||||
@ -30,6 +31,7 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery,
|
||||
getParams: function() {
|
||||
return {
|
||||
action: 'mailpoet',
|
||||
api_version: this.options.api_version,
|
||||
token: this.options.token,
|
||||
endpoint: this.options.endpoint,
|
||||
method: this.options.action,
|
||||
|
@ -64,6 +64,7 @@ define(
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'get',
|
||||
data: {
|
||||
@ -112,6 +113,7 @@ define(
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'save',
|
||||
data: item
|
||||
|
@ -97,6 +97,7 @@ const item_actions = [
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(item, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'forms',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
@ -125,6 +126,7 @@ const item_actions = [
|
||||
const FormList = React.createClass({
|
||||
createForm() {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'forms',
|
||||
action: 'create'
|
||||
}).done((response) => {
|
||||
|
@ -198,6 +198,17 @@ const ListingItem = React.createClass({
|
||||
const ListingItems = React.createClass({
|
||||
render: function() {
|
||||
if (this.props.items.length === 0) {
|
||||
let message;
|
||||
if (this.props.loading === true) {
|
||||
message = (this.props.messages.onLoadingItems
|
||||
&& this.props.messages.onLoadingItems(this.props.group))
|
||||
|| MailPoet.I18n.t('loadingItems');
|
||||
} else {
|
||||
message = (this.props.messages.onNoItemsFound
|
||||
&& this.props.messages.onNoItemsFound(this.props.group))
|
||||
|| MailPoet.I18n.t('noItemsFound');
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
<tr className="no-items">
|
||||
@ -207,11 +218,7 @@ const ListingItems = React.createClass({
|
||||
+ (this.props.is_selectable ? 1 : 0)
|
||||
}
|
||||
className="colspanchange">
|
||||
{
|
||||
(this.props.loading === true)
|
||||
? MailPoet.I18n.t('loadingItems')
|
||||
: MailPoet.I18n.t('noItemsFound')
|
||||
}
|
||||
{message}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -445,6 +452,7 @@ const Listing = React.createClass({
|
||||
this.clearSelection();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'listing',
|
||||
data: {
|
||||
@ -495,6 +503,7 @@ const Listing = React.createClass({
|
||||
});
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'restore',
|
||||
data: {
|
||||
@ -522,6 +531,7 @@ const Listing = React.createClass({
|
||||
});
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'trash',
|
||||
data: {
|
||||
@ -549,6 +559,7 @@ const Listing = React.createClass({
|
||||
});
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'delete',
|
||||
data: {
|
||||
@ -611,6 +622,7 @@ const Listing = React.createClass({
|
||||
}
|
||||
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: this.props.endpoint,
|
||||
action: 'bulkAction',
|
||||
data: data
|
||||
@ -788,6 +800,12 @@ const Listing = React.createClass({
|
||||
groups = false;
|
||||
}
|
||||
|
||||
// messages
|
||||
let messages = {};
|
||||
if (this.props.messages !== undefined) {
|
||||
messages = this.props.messages;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ groups }
|
||||
@ -841,6 +859,7 @@ const Listing = React.createClass({
|
||||
count={ this.state.count }
|
||||
limit={ this.state.limit }
|
||||
item_actions={ item_actions }
|
||||
messages={ messages }
|
||||
items={ items } />
|
||||
|
||||
<tfoot>
|
||||
|
187
assets/js/src/mp2migrator.js
Normal file
187
assets/js/src/mp2migrator.js
Normal file
@ -0,0 +1,187 @@
|
||||
define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
'use strict';
|
||||
MailPoet.MP2Migrator = {
|
||||
|
||||
fatal_error: '',
|
||||
is_logging: false,
|
||||
|
||||
startLogger: function () {
|
||||
MailPoet.MP2Migrator.is_logging = true;
|
||||
clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout);
|
||||
clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout);
|
||||
clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout);
|
||||
setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000)
|
||||
},
|
||||
|
||||
stopLogger: function () {
|
||||
MailPoet.MP2Migrator.is_logging = false;
|
||||
},
|
||||
|
||||
updateDisplay: function () {
|
||||
MailPoet.MP2Migrator.displayLogs();
|
||||
MailPoet.MP2Migrator.updateProgressbar();
|
||||
},
|
||||
|
||||
displayLogs: function () {
|
||||
jQuery.ajax({
|
||||
url: mailpoet_mp2_migrator.log_file_url,
|
||||
cache: false
|
||||
}).done(function (result) {
|
||||
jQuery("#logger").html('');
|
||||
result.split("\n").forEach(function (row) {
|
||||
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
|
||||
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
|
||||
}
|
||||
// Test if the import is complete
|
||||
else if(row === MailPoet.I18n.t('import_complete')) {
|
||||
jQuery('#import-actions').hide();
|
||||
jQuery('#upgrade-completed').show();
|
||||
}
|
||||
jQuery("#logger").append(row + "<br />\n");
|
||||
|
||||
});
|
||||
jQuery("#logger").append('<span class="error_msg">' + MailPoet.MP2Migrator.fatal_error + '</span>' + "<br />\n");
|
||||
}).always(function () {
|
||||
if(MailPoet.MP2Migrator.is_logging) {
|
||||
MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateProgressbar: function () {
|
||||
jQuery.ajax({
|
||||
url: mailpoet_mp2_migrator.progress_url,
|
||||
cache: false,
|
||||
dataType: 'json'
|
||||
}).always(function (result) {
|
||||
// Move the progress bar
|
||||
var progress = 0;
|
||||
if((result.total !== undefined) && (Number(result.total) !== 0)) {
|
||||
progress = Math.round(Number(result.current) / Number(result.total) * 100);
|
||||
}
|
||||
jQuery('#progressbar').progressbar('option', 'value', progress);
|
||||
jQuery('#progresslabel').html(progress + '%');
|
||||
if(Number(result.current !== 0)) {
|
||||
jQuery('#skip-import').hide();
|
||||
}
|
||||
if(MailPoet.MP2Migrator.is_logging) {
|
||||
MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
startImport: function () {
|
||||
MailPoet.MP2Migrator.fatal_error = '';
|
||||
// Start displaying the logs
|
||||
MailPoet.MP2Migrator.startLogger();
|
||||
|
||||
// Disable the import button
|
||||
MailPoet.MP2Migrator.import_button_label = jQuery('#import').val();
|
||||
jQuery('#import').val(MailPoet.I18n.t('importing')).attr('disabled', 'disabled');
|
||||
// Hide the Skip button
|
||||
jQuery('#skip-import').hide();
|
||||
// Show the stop button
|
||||
jQuery('#stop-import').show();
|
||||
|
||||
// Run the import
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'MP2Migrator',
|
||||
action: 'import',
|
||||
data: {
|
||||
}
|
||||
}).always(function () {
|
||||
MailPoet.MP2Migrator.stopLogger();
|
||||
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
|
||||
MailPoet.MP2Migrator.reactivateImportButton();
|
||||
}).done(function (response) {
|
||||
if(response) {
|
||||
MailPoet.MP2Migrator.fatal_error = response.data;
|
||||
}
|
||||
}).fail(function (response) {
|
||||
if(response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function (error) {
|
||||
return error.message;
|
||||
}),
|
||||
{scroll: true}
|
||||
);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
reactivateImportButton: function () {
|
||||
jQuery('#import').val(MailPoet.MP2Migrator.import_button_label).removeAttr('disabled');
|
||||
jQuery('#stop-import').hide();
|
||||
},
|
||||
|
||||
stopImport: function () {
|
||||
jQuery('#stop-import').attr('disabled', 'disabled');
|
||||
// Stop the import
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'MP2Migrator',
|
||||
action: 'stopImport',
|
||||
data: {
|
||||
}
|
||||
}).always(function () {
|
||||
jQuery('#stop-import').removeAttr('disabled'); // Enable the button
|
||||
MailPoet.MP2Migrator.reactivateImportButton();
|
||||
MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
|
||||
});
|
||||
MailPoet.MP2Migrator.stopLogger();
|
||||
return false;
|
||||
},
|
||||
|
||||
skipImport: function () {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'MP2Migrator',
|
||||
action: 'skipImport',
|
||||
data: {
|
||||
}
|
||||
}).done(function () {
|
||||
MailPoet.MP2Migrator.gotoWelcomePage();
|
||||
});
|
||||
return false;
|
||||
},
|
||||
|
||||
gotoWelcomePage: function () {
|
||||
window.location.href = 'admin.php?page=mailpoet-welcome';
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Actions to run when the DOM is ready
|
||||
*/
|
||||
jQuery(function () {
|
||||
jQuery('#progressbar').progressbar({value: 0});
|
||||
|
||||
// Import button
|
||||
jQuery('#import').click(function() {
|
||||
MailPoet.MP2Migrator.startImport();
|
||||
});
|
||||
|
||||
// Stop import button
|
||||
jQuery('#stop-import').click(function() {
|
||||
MailPoet.MP2Migrator.stopImport();
|
||||
});
|
||||
|
||||
// Skip import link
|
||||
jQuery('#skip-import').click(function() {
|
||||
MailPoet.MP2Migrator.skipImport();
|
||||
});
|
||||
|
||||
// Go to welcome page
|
||||
jQuery('#goto-welcome').click(function() {
|
||||
MailPoet.MP2Migrator.gotoWelcomePage();
|
||||
});
|
||||
|
||||
// Update the display
|
||||
MailPoet.MP2Migrator.updateDisplay();
|
||||
});
|
||||
|
||||
});
|
@ -1,21 +1,14 @@
|
||||
define([
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'backbone.radio',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'handlebars',
|
||||
'handlebars_helpers'
|
||||
], function(Backbone, Marionette, jQuery, _, Handlebars) {
|
||||
], function(Backbone, Marionette, Radio, jQuery, _, Handlebars) {
|
||||
|
||||
var app = new Marionette.Application(), AppView;
|
||||
|
||||
// Decoupled communication between application components
|
||||
app.getChannel = function(channel) {
|
||||
if (channel === undefined) return app.channel;
|
||||
return Radio.channel(channel);
|
||||
};
|
||||
|
||||
AppView = Marionette.LayoutView.extend({
|
||||
var AppView = Marionette.View.extend({
|
||||
el: '#mailpoet_editor',
|
||||
regions: {
|
||||
stylesRegion: '#mailpoet_editor_styles',
|
||||
@ -26,10 +19,23 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
app.on('start', function(options) {
|
||||
app._appView = new AppView();
|
||||
var EditorApplication = Marionette.Application.extend({
|
||||
region: '#mailpoet_editor',
|
||||
|
||||
onStart: function() {
|
||||
this._appView = new AppView();
|
||||
this.showView(this._appView);
|
||||
},
|
||||
|
||||
getChannel: function(channel) {
|
||||
if (channel === undefined) channel = 'global';
|
||||
return Radio.channel(channel);
|
||||
}
|
||||
});
|
||||
|
||||
var app = new EditorApplication();
|
||||
window.EditorApplication = app;
|
||||
|
||||
return app;
|
||||
|
||||
});
|
||||
|
@ -71,6 +71,7 @@ define([
|
||||
markerWidth = '',
|
||||
markerHeight = '',
|
||||
containerOffset = element.offset(),
|
||||
viewCollection = that.getCollection(),
|
||||
marker, targetModel, targetView, targetElement,
|
||||
topOffset, leftOffset, isLastBlockInsertion,
|
||||
$targetBlock, margin;
|
||||
@ -80,19 +81,19 @@ define([
|
||||
element.find('.mailpoet_drop_marker').remove();
|
||||
|
||||
// Allow empty collections to handle their own drop marking
|
||||
if (view.model.get('blocks').isEmpty()) return;
|
||||
if (viewCollection.isEmpty()) return;
|
||||
|
||||
if (view.collection.length === 0) {
|
||||
if (viewCollection.length === 0) {
|
||||
targetElement = element.find(view.childViewContainer);
|
||||
topOffset = targetElement.offset().top - element.offset().top;
|
||||
leftOffset = targetElement.offset().left - element.offset().left;
|
||||
markerWidth = targetElement.width();
|
||||
markerHeight = targetElement.height();
|
||||
} else {
|
||||
isLastBlockInsertion = view.collection.length === dropPosition.index;
|
||||
targetModel = isLastBlockInsertion ? view.collection.at(dropPosition.index - 1) : view.collection.at(dropPosition.index);
|
||||
isLastBlockInsertion = that.getCollection().length === dropPosition.index;
|
||||
targetModel = isLastBlockInsertion ? viewCollection.at(dropPosition.index - 1) : viewCollection.at(dropPosition.index);
|
||||
|
||||
targetView = view.children.findByModel(targetModel);
|
||||
targetView = that.getChildren().findByModel(targetModel);
|
||||
targetElement = targetView.$el;
|
||||
|
||||
topOffset = targetElement.offset().top - containerOffset.top;
|
||||
@ -135,10 +136,10 @@ define([
|
||||
if (dropPosition.index === 0) {
|
||||
marker.addClass('mailpoet_drop_marker_first');
|
||||
}
|
||||
if (view.collection.length - 1 === dropPosition.index) {
|
||||
if (viewCollection.length - 1 === dropPosition.index) {
|
||||
marker.addClass('mailpoet_drop_marker_last');
|
||||
}
|
||||
if (dropPosition.index > 0 && view.collection.length - 1 > dropPosition.index) {
|
||||
if (dropPosition.index > 0 && viewCollection.length - 1 > dropPosition.index) {
|
||||
marker.addClass('mailpoet_drop_marker_middle');
|
||||
}
|
||||
marker.addClass('mailpoet_drop_marker_' + dropPosition.position);
|
||||
@ -147,9 +148,9 @@ define([
|
||||
// compensated for to position marker right in the middle of two
|
||||
// blocks
|
||||
if (dropPosition.position === 'before') {
|
||||
$targetBlock = view.children.findByModel(view.collection.at(dropPosition.index-1)).$el;
|
||||
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index-1)).$el;
|
||||
} else {
|
||||
$targetBlock = view.children.findByModel(view.collection.at(dropPosition.index)).$el;
|
||||
$targetBlock = that.getChildren().findByModel(viewCollection.at(dropPosition.index)).$el;
|
||||
}
|
||||
margin = $targetBlock.outerHeight(true) - $targetBlock.outerHeight();
|
||||
|
||||
@ -182,6 +183,7 @@ define([
|
||||
view.model.get('blocks').length
|
||||
),
|
||||
droppableModel = event.draggable.getDropModel(),
|
||||
viewCollection = that.getCollection(),
|
||||
droppedView, droppedModel, index, tempCollection, tempCollection2;
|
||||
|
||||
if (dropPosition === undefined) return;
|
||||
@ -196,22 +198,22 @@ define([
|
||||
orientation: 'vertical',
|
||||
});
|
||||
tempCollection.get('blocks').add(droppableModel);
|
||||
view.collection.add(tempCollection, {at: index});
|
||||
viewCollection.add(tempCollection, {at: index});
|
||||
} else {
|
||||
view.collection.add(droppableModel, {at: index});
|
||||
viewCollection.add(droppableModel, {at: index});
|
||||
}
|
||||
|
||||
droppedView = view.children.findByModel(droppableModel);
|
||||
droppedView = that.getChildren().findByModel(droppableModel);
|
||||
} else {
|
||||
// Special insertion by replacing target block with collection
|
||||
// and inserting dropModel into that
|
||||
var tempModel = view.collection.at(dropPosition.index);
|
||||
var tempModel = viewCollection.at(dropPosition.index);
|
||||
|
||||
tempCollection = new (EditorApplication.getBlockTypeModel('container'))({
|
||||
orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical',
|
||||
});
|
||||
|
||||
view.collection.remove(tempModel);
|
||||
viewCollection.remove(tempModel);
|
||||
|
||||
if (tempCollection.get('orientation') === 'horizontal') {
|
||||
if (dropPosition.position === 'before') {
|
||||
@ -242,10 +244,10 @@ define([
|
||||
tempCollection.get('blocks').add(droppableModel);
|
||||
}
|
||||
}
|
||||
view.collection.add(tempCollection, {at: dropPosition.index});
|
||||
viewCollection.add(tempCollection, {at: dropPosition.index});
|
||||
|
||||
// Call post add actions
|
||||
droppedView = view.children.findByModel(tempCollection).children.findByModel(droppableModel);
|
||||
droppedView = that.getChildren().findByModel(tempCollection).children.findByModel(droppableModel);
|
||||
}
|
||||
|
||||
// Call post add actions
|
||||
@ -290,7 +292,7 @@ define([
|
||||
|
||||
unsafe = !!unsafe;
|
||||
|
||||
if (this.view.collection.length === 0) {
|
||||
if (this.getCollection().length === 0) {
|
||||
return {
|
||||
insertionType: 'normal',
|
||||
index: 0,
|
||||
@ -327,7 +329,7 @@ define([
|
||||
index = indexAndPosition.index;
|
||||
}
|
||||
|
||||
if (!unsafe && orientation === 'vertical' && insertionType === 'special' && this.view.collection.at(index).get('orientation') === 'horizontal') {
|
||||
if (!unsafe && orientation === 'vertical' && insertionType === 'special' && this.getCollection().at(index).get('orientation') === 'horizontal') {
|
||||
// Prevent placing horizontal container in another horizontal container,
|
||||
// which would allow breaking the column limit.
|
||||
// Switch that to normal insertion
|
||||
@ -356,7 +358,7 @@ define([
|
||||
|
||||
var index = this._computeCellIndex(eventX, eventY),
|
||||
// TODO: Handle case when there are no children, container is empty
|
||||
targetView = this.view.children.findByModel(this.view.collection.at(index)),
|
||||
targetView = this.getChildren().findByModel(this.getCollection().at(index)),
|
||||
orientation = this.view.model.get('orientation'),
|
||||
element = targetView.$el,
|
||||
eventOffset, closeOffset, elementDimension;
|
||||
@ -391,7 +393,7 @@ define([
|
||||
_computeCellIndex: function(eventX, eventY) {
|
||||
var orientation = this.view.model.get('orientation'),
|
||||
eventOffset = (orientation === 'vertical') ? eventY : eventX,
|
||||
resultView = this.view.children.find(function(view) {
|
||||
resultView = this.getChildren().find(function(view) {
|
||||
var element = view.$el,
|
||||
closeOffset, farOffset;
|
||||
|
||||
@ -414,15 +416,24 @@ define([
|
||||
_canAcceptNormalInsertion: function() {
|
||||
var orientation = this.view.model.get('orientation'),
|
||||
depth = this.view.renderOptions.depth,
|
||||
childCount = this.view.children.length;
|
||||
childCount = this.getChildren().length;
|
||||
// Note that depth is zero indexed. Root container has depth=0
|
||||
return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
|
||||
},
|
||||
_canAcceptSpecialInsertion: function() {
|
||||
var orientation = this.view.model.get('orientation'),
|
||||
depth = this.view.renderOptions.depth,
|
||||
childCount = this.view.children.length;
|
||||
childCount = this.getChildren().length;
|
||||
return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
|
||||
},
|
||||
getCollectionView: function() {
|
||||
return this.view.getChildView('blocks');
|
||||
},
|
||||
getChildren: function() {
|
||||
return this.getCollectionView().children;
|
||||
},
|
||||
getCollection: function() {
|
||||
return this.getCollectionView().collection;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -60,10 +60,17 @@ define([
|
||||
|
||||
editor.on('click', function(e) {
|
||||
editor.focus();
|
||||
if (that._isActivationClick) {
|
||||
editor.selection.setRng(
|
||||
tinymce.dom.RangeUtils.getCaretRangeFromPoint(e.clientX, e.clientY, editor.getDoc())
|
||||
);
|
||||
that._isActivationClick = false;
|
||||
}
|
||||
});
|
||||
|
||||
editor.on('focus', function(e) {
|
||||
that.view.triggerMethod('text:editor:focus');
|
||||
that._isActivationClick = true;
|
||||
});
|
||||
|
||||
editor.on('blur', function(e) {
|
||||
|
@ -129,6 +129,12 @@ define([
|
||||
|
||||
Module.AutomatedLatestContentBlockView = base.BlockView.extend({
|
||||
className: "mailpoet_block mailpoet_automated_latest_content_block mailpoet_droppable_block",
|
||||
initialize: function() {
|
||||
function replaceButtonStylesHandler(data) {
|
||||
this.model.set({"readMoreButton": data});
|
||||
}
|
||||
App.getChannel().on("replaceAllButtonStyles", replaceButtonStylesHandler.bind(this));
|
||||
},
|
||||
getTemplate: function() { return templates.automatedLatestContentBlock; },
|
||||
regions: {
|
||||
toolsRegion: '.mailpoet_tools',
|
||||
@ -151,8 +157,8 @@ define([
|
||||
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
|
||||
};
|
||||
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.postsRegion.show(new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
|
||||
},
|
||||
});
|
||||
|
||||
@ -189,11 +195,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
};
|
||||
},
|
||||
onRender: function() {
|
||||
var that = this;
|
||||
|
||||
@ -377,7 +378,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('automatedLatestContent', {
|
||||
blockModel: Module.AutomatedLatestContentBlockModel,
|
||||
blockView: Module.AutomatedLatestContentBlockView,
|
||||
@ -390,7 +391,7 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
App.on('start', function() {
|
||||
App.on('start', function(App, options) {
|
||||
App._ALCSupervisor = new Module.ALCSupervisor();
|
||||
App._ALCSupervisor.refresh();
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ define([
|
||||
"use strict";
|
||||
|
||||
var Module = {},
|
||||
AugmentedView = Marionette.LayoutView.extend({});
|
||||
AugmentedView = Marionette.View.extend({});
|
||||
|
||||
Module.BlockModel = SuperModel.extend({
|
||||
stale: [], // Attributes to be removed upon saving
|
||||
@ -82,7 +82,7 @@ define([
|
||||
},
|
||||
HighlightEditingBehavior: {},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
@ -125,6 +125,12 @@ define([
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
disableDragging: function() {
|
||||
this.$el.addClass('mailpoet_ignore_drag');
|
||||
},
|
||||
enableDragging: function() {
|
||||
this.$el.removeClass('mailpoet_ignore_drag');
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
@ -193,7 +199,7 @@ define([
|
||||
this.on('hideTools', this.hideDeletionConfirmation, this);
|
||||
this.on('showSettings', this.changeSettings);
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
@ -217,7 +223,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.BlockSettingsView = Marionette.LayoutView.extend({
|
||||
Module.BlockSettingsView = Marionette.View.extend({
|
||||
className: 'mailpoet_editor_settings',
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
@ -240,6 +246,11 @@ define([
|
||||
MailPoet.Modal.panel(panelParams);
|
||||
}
|
||||
},
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON()
|
||||
};
|
||||
},
|
||||
close: function(event) {
|
||||
this.destroy();
|
||||
},
|
||||
@ -271,7 +282,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.WidgetView = Marionette.ItemView.extend({
|
||||
Module.WidgetView = Marionette.View.extend({
|
||||
className: 'mailpoet_widget mailpoet_droppable_block mailpoet_droppable_widget',
|
||||
behaviors: {
|
||||
DraggableBehavior: {
|
||||
|
@ -56,7 +56,7 @@ define([
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.ButtonBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
});
|
||||
|
||||
@ -98,12 +98,11 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
templateContext: function() {
|
||||
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
|
||||
availableStyles: App.getAvailableStyles().toJSON(),
|
||||
renderOptions: this.renderOptions,
|
||||
};
|
||||
});
|
||||
},
|
||||
applyToAll: function() {
|
||||
App.getChannel().trigger('replaceAllButtonStyles', _.pick(this.model.toJSON(), 'styles', 'type'));
|
||||
@ -133,7 +132,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('button', {
|
||||
blockModel: Module.ButtonBlockModel,
|
||||
blockView: Module.ButtonBlockView,
|
||||
|
@ -74,29 +74,43 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockView = Marionette.CompositeView.extend({
|
||||
regionClass: Marionette.Region,
|
||||
Module.ContainerBlocksView = Marionette.CollectionView.extend({
|
||||
className: 'mailpoet_container',
|
||||
childView: function(model) {
|
||||
return App.getBlockTypeView(model.get('type'));
|
||||
},
|
||||
childViewOptions: function() {
|
||||
var newRenderOptions = _.clone(this.renderOptions);
|
||||
if (newRenderOptions.depth !== undefined) {
|
||||
newRenderOptions.depth += 1;
|
||||
}
|
||||
return {
|
||||
renderOptions: newRenderOptions
|
||||
};
|
||||
},
|
||||
emptyView: function() { return Module.ContainerBlockEmptyView; },
|
||||
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
|
||||
initialize: function(options) {
|
||||
this.renderOptions = options.renderOptions;
|
||||
}
|
||||
});
|
||||
|
||||
Module.ContainerBlockView = base.BlockView.extend({
|
||||
regions: _.extend({}, base.BlockView.prototype.regions, {
|
||||
blocks: {
|
||||
el: '> .mailpoet_container',
|
||||
replaceElement: true
|
||||
},
|
||||
}),
|
||||
className: 'mailpoet_block mailpoet_container_block mailpoet_droppable_block mailpoet_droppable_layout_block',
|
||||
getTemplate: function() { return templates.containerBlock; },
|
||||
childViewContainer: '> .mailpoet_container',
|
||||
getEmptyView: function() { return Module.ContainerBlockEmptyView; },
|
||||
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
'delete': 'deleteBlock',
|
||||
},
|
||||
events: {
|
||||
"mouseenter": "showTools",
|
||||
"mouseleave": "hideTools",
|
||||
events: _.extend({}, base.BlockView.prototype.events, {
|
||||
"click .mailpoet_newsletter_layer_selector": "toggleEditingLayer",
|
||||
},
|
||||
regions: {
|
||||
toolsRegion: '> .mailpoet_tools',
|
||||
},
|
||||
}),
|
||||
ui: {
|
||||
tools: '> .mailpoet_tools'
|
||||
},
|
||||
behaviors: {
|
||||
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
|
||||
ContainerDropZoneBehavior: {},
|
||||
DraggableBehavior: {
|
||||
cloneOriginal: true,
|
||||
@ -125,8 +139,7 @@ define([
|
||||
return view.renderOptions.depth === 1;
|
||||
},
|
||||
},
|
||||
HighlightEditingBehavior: {}
|
||||
},
|
||||
}),
|
||||
onDragSubstituteBy: function() {
|
||||
// For two and three column layouts display their respective widgets,
|
||||
// otherwise always default to one column layout widget
|
||||
@ -137,39 +150,12 @@ define([
|
||||
return Module.OneColumnContainerWidgetView;
|
||||
|
||||
},
|
||||
constructor: function() {
|
||||
// Set the block collection to be handled by this view as well
|
||||
arguments[0].collection = arguments[0].model.get('blocks');
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
this.$el.addClass('mailpoet_editor_view_' + this.cid);
|
||||
},
|
||||
initialize: function(options) {
|
||||
base.BlockView.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
||||
this.on('dom:refresh', this.showBlock, this);
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
// Determines which view type should be used for a child
|
||||
getChildView: function(model) {
|
||||
// TODO: If type does not have a type registered, use a generic one
|
||||
return App.getBlockTypeView(model.get('type'));
|
||||
},
|
||||
childViewOptions: function() {
|
||||
var newRenderOptions = _.clone(this.renderOptions);
|
||||
if (newRenderOptions.depth !== undefined) {
|
||||
newRenderOptions.depth += 1;
|
||||
}
|
||||
return {
|
||||
renderOptions: newRenderOptions
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
};
|
||||
},
|
||||
onRender: function() {
|
||||
this._rebuildRegions();
|
||||
this.toolsView = new Module.ContainerBlockToolsView({
|
||||
model: this.model,
|
||||
tools: {
|
||||
@ -179,10 +165,15 @@ define([
|
||||
layerSelector: false,
|
||||
},
|
||||
});
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
this.regionManager.destroy();
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
this.showChildView('blocks', new Module.ContainerBlocksView({
|
||||
collection: this.model.get('blocks'),
|
||||
renderOptions: this.renderOptions
|
||||
}));
|
||||
|
||||
// TODO: Look for a better way to do this than here
|
||||
// Sets child container orientation HTML class here, as child CollectionView won't have access to model and will overwrite existing region element instead
|
||||
this.$('> .mailpoet_container').attr('class', 'mailpoet_container mailpoet_container_' + this.model.get('orientation'));
|
||||
},
|
||||
showTools: function() {
|
||||
if (this.renderOptions.depth === 1 && !this.$el.hasClass('mailpoet_container_layer_active')) {
|
||||
@ -222,76 +213,14 @@ define([
|
||||
}
|
||||
event.stopPropagation();
|
||||
},
|
||||
_buildRegions: function(regions) {
|
||||
var that = this;
|
||||
|
||||
var defaults = {
|
||||
regionClass: this.getOption('regionClass'),
|
||||
parentEl: function() { return that.$el; }
|
||||
};
|
||||
|
||||
return this.regionManager.addRegions(regions, defaults);
|
||||
},
|
||||
_rebuildRegions: function() {
|
||||
if (this.regionManager === undefined) {
|
||||
this.regionManager = new Marionette.RegionManager();
|
||||
}
|
||||
this.regionManager.destroy();
|
||||
_.extend(this, this._buildRegions(this.regions));
|
||||
},
|
||||
getDropFunc: function() {
|
||||
return function() {
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().done(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_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
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockEmptyView = Marionette.ItemView.extend({
|
||||
Module.ContainerBlockEmptyView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.containerEmpty; },
|
||||
initialize: function(options) {
|
||||
this.renderOptions = _.defaults(options.renderOptions || {}, {});
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
isRoot: this.renderOptions.depth === 0,
|
||||
emptyContainerMessage: this.renderOptions.emptyContainerMessage || '',
|
||||
@ -322,12 +251,12 @@ define([
|
||||
});
|
||||
},
|
||||
onRender: function() {
|
||||
this.columnsSettingsRegion.show(this._columnsSettingsView);
|
||||
this.showChildView('columnsSettingsRegion', this._columnsSettingsView);
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockColumnsSettingsView = Marionette.CollectionView.extend({
|
||||
getChildView: function() { return Module.ContainerBlockColumnSettingsView; },
|
||||
childView: function() { return Module.ContainerBlockColumnSettingsView; },
|
||||
childViewOptions: function(model, index) {
|
||||
return {
|
||||
columnIndex: index,
|
||||
@ -335,12 +264,12 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.ContainerBlockColumnSettingsView = Marionette.ItemView.extend({
|
||||
Module.ContainerBlockColumnSettingsView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.containerBlockColumnSettings; },
|
||||
initialize: function(options) {
|
||||
this.columnNumber = (options.columnIndex || 0) + 1;
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
columnNumber: this.columnNumber,
|
||||
@ -405,7 +334,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('container', {
|
||||
blockModel: Module.ContainerBlockModel,
|
||||
blockView: Module.ContainerBlockView,
|
||||
|
@ -59,14 +59,14 @@ define([
|
||||
this.listenTo(this.model, 'change:src change:styles.block.backgroundColor change:styles.block.borderStyle change:styles.block.borderWidth change:styles.block.borderColor applyToAll', this.render);
|
||||
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
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));
|
||||
}, base.BlockView.prototype.templateContext.apply(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.DividerBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
App.getChannel().off('replaceAllDividers', this._replaceDividerHandler);
|
||||
@ -104,12 +104,11 @@ define([
|
||||
'change:styles.block.borderColor': 'repaintDividerStyleOptions',
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
templateContext: function() {
|
||||
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
|
||||
availableStyles: App.getAvailableStyles().toJSON(),
|
||||
renderOptions: this.renderOptions,
|
||||
};
|
||||
});
|
||||
},
|
||||
changeStyle: function(event) {
|
||||
var style = jQuery(event.currentTarget).data('style');
|
||||
@ -140,7 +139,7 @@ define([
|
||||
}
|
||||
},
|
||||
});
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('divider', {
|
||||
blockModel: Module.DividerBlockModel,
|
||||
blockView: Module.DividerBlockView,
|
||||
|
@ -55,7 +55,7 @@ define([
|
||||
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
onTextEditorChange: function(newContent) {
|
||||
this.model.set('text', newContent);
|
||||
@ -68,12 +68,6 @@ define([
|
||||
this.enableDragging();
|
||||
this.enableShowingTools();
|
||||
},
|
||||
disableDragging: function() {
|
||||
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
|
||||
},
|
||||
enableDragging: function() {
|
||||
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
|
||||
},
|
||||
});
|
||||
|
||||
Module.FooterBlockToolsView = base.BlockToolsView.extend({
|
||||
@ -96,11 +90,10 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
templateContext: function() {
|
||||
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
|
||||
availableStyles: App.getAvailableStyles().toJSON(),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -116,7 +109,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('footer', {
|
||||
blockModel: Module.FooterBlockModel,
|
||||
blockView: Module.FooterBlockView,
|
||||
|
@ -55,7 +55,7 @@ define([
|
||||
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
onTextEditorChange: function(newContent) {
|
||||
this.model.set('text', newContent);
|
||||
@ -68,12 +68,6 @@ define([
|
||||
this.enableDragging();
|
||||
this.enableShowingTools();
|
||||
},
|
||||
disableDragging: function() {
|
||||
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
|
||||
},
|
||||
enableDragging: function() {
|
||||
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
|
||||
},
|
||||
});
|
||||
|
||||
Module.HeaderBlockToolsView = base.BlockToolsView.extend({
|
||||
@ -96,11 +90,10 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
templateContext: function() {
|
||||
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
|
||||
availableStyles: App.getAvailableStyles().toJSON(),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -116,7 +109,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('header', {
|
||||
blockModel: Module.HeaderBlockModel,
|
||||
blockView: Module.HeaderBlockView,
|
||||
|
@ -36,17 +36,17 @@ define([
|
||||
className: "mailpoet_block mailpoet_image_block mailpoet_droppable_block",
|
||||
getTemplate: function() { return templates.imageBlock; },
|
||||
onDragSubstituteBy: function() { return Module.ImageWidgetView; },
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return _.extend({
|
||||
imageMissingSrc: App.getConfig().get('urls.imageMissing'),
|
||||
}, base.BlockView.prototype.templateHelpers.apply(this));
|
||||
}, base.BlockView.prototype.templateContext.apply(this));
|
||||
},
|
||||
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
|
||||
ShowSettingsBehavior: {},
|
||||
}),
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
|
||||
if (this.model.get('fullWidth')) {
|
||||
this.$el.addClass('mailpoet_full_image');
|
||||
@ -65,7 +65,7 @@ define([
|
||||
events: function() {
|
||||
return {
|
||||
"input .mailpoet_field_image_link": _.partial(this.changeField, "link"),
|
||||
"input .mailpoet_field_image_address": _.partial(this.changeField, "src"),
|
||||
"input .mailpoet_field_image_address": 'changeAddress',
|
||||
"input .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
|
||||
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
|
||||
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
|
||||
@ -327,6 +327,20 @@ define([
|
||||
|
||||
this._mediaManager.open();
|
||||
},
|
||||
changeAddress: function(event) {
|
||||
var src = jQuery(event.target).val();
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function() {
|
||||
this.model.set({
|
||||
src: src,
|
||||
width: image.naturalWidth + 'px',
|
||||
height: image.naturalHeight + 'px'
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
image.src = src;
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
|
||||
if (typeof this._mediaManager === 'object') {
|
||||
@ -351,7 +365,7 @@ define([
|
||||
});
|
||||
Module.ImageWidgetView = ImageWidgetView;
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('image', {
|
||||
blockModel: Module.ImageBlockModel,
|
||||
blockView: Module.ImageBlockView,
|
||||
|
@ -166,8 +166,8 @@ define([
|
||||
this.model.reply('blockView', this.notifyAboutSelf, this);
|
||||
},
|
||||
onRender: function() {
|
||||
if (!this.toolsRegion.hasView()) {
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
if (!this.getRegion('toolsRegion').hasView()) {
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
}
|
||||
this.trigger('showSettings');
|
||||
|
||||
@ -177,7 +177,7 @@ define([
|
||||
disableDragAndDrop: true,
|
||||
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay'),
|
||||
};
|
||||
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
|
||||
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
|
||||
},
|
||||
notifyAboutSelf: function() {
|
||||
return this;
|
||||
@ -202,7 +202,7 @@ define([
|
||||
'click .mailpoet_settings_posts_show_post_selection': 'switchToPostSelection',
|
||||
'click .mailpoet_settings_posts_insert_selected': 'insertPosts',
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
};
|
||||
@ -259,16 +259,24 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
var PostSelectionSettingsView = Marionette.CompositeView.extend({
|
||||
getTemplate: function() { return templates.postSelectionPostsBlockSettings; },
|
||||
getChildView: function() { return SinglePostSelectionSettingsView; },
|
||||
childViewContainer: '.mailpoet_post_selection_container',
|
||||
getEmptyView: function() { return EmptyPostSelectionSettingsView; },
|
||||
var PostsSelectionCollectionView = Marionette.CollectionView.extend({
|
||||
childView: function() { return SinglePostSelectionSettingsView; },
|
||||
emptyView: function() { return EmptyPostSelectionSettingsView; },
|
||||
childViewOptions: function() {
|
||||
return {
|
||||
blockModel: this.model,
|
||||
blockModel: this.blockModel,
|
||||
};
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.blockModel = options.blockModel;
|
||||
},
|
||||
});
|
||||
|
||||
var PostSelectionSettingsView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.postSelectionPostsBlockSettings; },
|
||||
regions: {
|
||||
posts: '.mailpoet_post_selection_container',
|
||||
},
|
||||
events: function() {
|
||||
return {
|
||||
'change .mailpoet_settings_posts_content_type': _.partial(this.changeField, 'contentType'),
|
||||
@ -276,21 +284,19 @@ define([
|
||||
'input .mailpoet_posts_search_term': _.partial(this.changeField, 'search'),
|
||||
};
|
||||
},
|
||||
constructor: function() {
|
||||
// Set the block collection to be handled by this view as well
|
||||
arguments[0].collection = arguments[0].model.get('_availablePosts');
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
},
|
||||
onRender: function() {
|
||||
// Dynamically update available post types
|
||||
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
var postsView = new PostsSelectionCollectionView({
|
||||
collection: this.model.get('_availablePosts'),
|
||||
blockModel: this.model
|
||||
});
|
||||
|
||||
this.showChildView('posts', postsView);
|
||||
},
|
||||
onAttach: function() {
|
||||
var that = this;
|
||||
|
||||
// Dynamically update available post types
|
||||
//CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
|
||||
|
||||
this.$('.mailpoet_posts_categories_and_tags').select2({
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
@ -372,18 +378,18 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
var EmptyPostSelectionSettingsView = Marionette.ItemView.extend({
|
||||
var EmptyPostSelectionSettingsView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.emptyPostPostsBlockSettings; },
|
||||
});
|
||||
|
||||
var SinglePostSelectionSettingsView = Marionette.ItemView.extend({
|
||||
var SinglePostSelectionSettingsView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.singlePostPostsBlockSettings; },
|
||||
events: function() {
|
||||
return {
|
||||
'change .mailpoet_select_post_checkbox': 'postSelectionChange',
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
index: this._index,
|
||||
@ -428,7 +434,7 @@ define([
|
||||
"change .mailpoet_posts_sort_by": _.partial(this.changeField, "sortBy"),
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
};
|
||||
@ -520,7 +526,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('posts', {
|
||||
blockModel: Module.PostsBlockModel,
|
||||
blockView: Module.PostsBlockView,
|
||||
|
@ -17,6 +17,7 @@ define([
|
||||
base = BaseBlock,
|
||||
SocialBlockSettingsIconSelectorView,
|
||||
SocialBlockSettingsIconView,
|
||||
SocialBlockSettingsIconCollectionView,
|
||||
SocialBlockSettingsStylesView;
|
||||
|
||||
Module.SocialIconModel = SuperModel.extend({
|
||||
@ -82,13 +83,13 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
var SocialIconView = Marionette.ItemView.extend({
|
||||
var SocialIconView = Marionette.View.extend({
|
||||
tagName: 'span',
|
||||
getTemplate: function() { return templates.socialIconBlock; },
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
var allIconSets = App.getAvailableStyles().get('socialIconSets');
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
@ -98,150 +99,29 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.SocialBlockView = Marionette.CompositeView.extend({
|
||||
regionClass: Marionette.Region,
|
||||
Module.SocialIconCollectionView = Marionette.CollectionView.extend({
|
||||
childView: SocialIconView,
|
||||
});
|
||||
|
||||
Module.SocialBlockView = base.BlockView.extend({
|
||||
className: 'mailpoet_block mailpoet_social_block mailpoet_droppable_block',
|
||||
getTemplate: function() { return templates.socialBlock; },
|
||||
childViewContainer: '.mailpoet_social',
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
'delete': 'deleteBlock',
|
||||
},
|
||||
events: {
|
||||
"mouseover": "showTools",
|
||||
"mouseout": "hideTools",
|
||||
},
|
||||
regions: {
|
||||
toolsRegion: '> .mailpoet_tools',
|
||||
},
|
||||
regions: _.extend({}, base.BlockView.prototype.regions, {
|
||||
icons: '.mailpoet_social'
|
||||
}),
|
||||
ui: {
|
||||
tools: '> .mailpoet_tools'
|
||||
},
|
||||
behaviors: {
|
||||
DraggableBehavior: {
|
||||
cloneOriginal: true,
|
||||
hideOriginal: true,
|
||||
onDrop: function(options) {
|
||||
// After a clone of model has been dropped, cleanup
|
||||
// and destroy self
|
||||
options.dragBehavior.view.model.destroy();
|
||||
},
|
||||
onDragSubstituteBy: function(behavior) {
|
||||
var WidgetView, node;
|
||||
// When block is being dragged, display the widget icon instead.
|
||||
// This will create an instance of block's widget view and
|
||||
// use it's rendered DOM element instead of the content block
|
||||
if (_.isFunction(behavior.view.onDragSubstituteBy)) {
|
||||
WidgetView = new (behavior.view.onDragSubstituteBy())();
|
||||
WidgetView.render();
|
||||
node = WidgetView.$el.get(0).cloneNode(true);
|
||||
WidgetView.destroy();
|
||||
return node;
|
||||
}
|
||||
},
|
||||
},
|
||||
HighlightEditingBehavior: {},
|
||||
behaviors: _.extend({}, base.BlockView.prototype.behaviors, {
|
||||
ShowSettingsBehavior: {},
|
||||
},
|
||||
}),
|
||||
onDragSubstituteBy: function() { return Module.SocialWidgetView; },
|
||||
constructor: function() {
|
||||
// Set the block collection to be handled by this view as well
|
||||
arguments[0].collection = arguments[0].model.get('icons');
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
},
|
||||
initialize: function() {
|
||||
this.on('showSettings', this.showSettings, this);
|
||||
this.on('dom:refresh', this.showBlock, this);
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
// Determines which view type should be used for a child
|
||||
childView: SocialIconView,
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
viewCid: this.cid,
|
||||
};
|
||||
},
|
||||
onRender: function() {
|
||||
this._rebuildRegions();
|
||||
this.toolsView = new Module.SocialBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
},
|
||||
onBeforeDestroy: function() {
|
||||
this.regionManager.destroy();
|
||||
},
|
||||
showTools: function(_event) {
|
||||
this.$(this.ui.tools).addClass('mailpoet_display_tools');
|
||||
_event.stopPropagation();
|
||||
},
|
||||
hideTools: function(_event) {
|
||||
this.$(this.ui.tools).removeClass('mailpoet_display_tools');
|
||||
_event.stopPropagation();
|
||||
},
|
||||
showSettings: function(options) {
|
||||
this.toolsView.triggerMethod('showSettings', options);
|
||||
},
|
||||
getDropFunc: function() {
|
||||
return function() {
|
||||
return this.model.clone();
|
||||
}.bind(this);
|
||||
},
|
||||
_buildRegions: function(regions) {
|
||||
var that = this;
|
||||
|
||||
var defaults = {
|
||||
regionClass: this.getOption('regionClass'),
|
||||
parentEl: function() { return that.$el; }
|
||||
};
|
||||
|
||||
return this.regionManager.addRegions(regions, defaults);
|
||||
},
|
||||
_rebuildRegions: function() {
|
||||
if (this.regionManager === undefined) {
|
||||
this.regionManager = new Marionette.RegionManager();
|
||||
}
|
||||
this.regionManager.destroy();
|
||||
_.extend(this, this._buildRegions(this.regions));
|
||||
},
|
||||
showBlock: function() {
|
||||
if (this._isFirstRender) {
|
||||
this.transitionIn();
|
||||
this._isFirstRender = false;
|
||||
}
|
||||
},
|
||||
deleteBlock: function() {
|
||||
this.transitionOut().done(function() {
|
||||
this.model.destroy();
|
||||
}.bind(this));
|
||||
},
|
||||
transitionIn: function() {
|
||||
return this._transition('slideDown', 'fadeIn', 'easeIn');
|
||||
},
|
||||
transitionOut: function() {
|
||||
return this._transition('slideUp', 'fadeOut', 'easeOut');
|
||||
},
|
||||
_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
|
||||
}
|
||||
);
|
||||
|
||||
return promise;
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
this.showChildView('icons', new Module.SocialIconCollectionView({
|
||||
collection: this.model.get('icons')
|
||||
}))
|
||||
},
|
||||
});
|
||||
|
||||
@ -268,13 +148,13 @@ define([
|
||||
this._stylesView = new SocialBlockSettingsStylesView({ model: this.model });
|
||||
},
|
||||
onRender: function() {
|
||||
this.iconRegion.show(this._iconSelectorView);
|
||||
this.stylesRegion.show(this._stylesView);
|
||||
this.showChildView('iconRegion', this._iconSelectorView);
|
||||
this.showChildView('stylesRegion', this._stylesView);
|
||||
}
|
||||
});
|
||||
|
||||
// Single icon settings view, used by the selector view
|
||||
SocialBlockSettingsIconView = Marionette.ItemView.extend({
|
||||
SocialBlockSettingsIconView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.socialSettingsIcon; },
|
||||
events: function() {
|
||||
return {
|
||||
@ -294,17 +174,16 @@ define([
|
||||
this.$('.mailpoet_social_icon_image').attr('alt', this.model.get('text'));
|
||||
},
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
var icons = App.getConfig().get('socialIcons'),
|
||||
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
|
||||
availableIconTypes = _.map(_.keys(icons.attributes), function(key) { return { iconType: key, title: icons.get(key).get('title') }; }),
|
||||
allIconSets = App.getAvailableStyles().get('socialIconSets');
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
|
||||
iconTypes: availableIconTypes,
|
||||
currentType: icons.get(this.model.get('iconType')).toJSON(),
|
||||
allIconSets: allIconSets.toJSON(),
|
||||
};
|
||||
});
|
||||
},
|
||||
deleteIcon: function() {
|
||||
this.model.destroy();
|
||||
@ -321,34 +200,41 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
// Select icons section container view
|
||||
SocialBlockSettingsIconSelectorView = Marionette.CompositeView.extend({
|
||||
getTemplate: function() { return templates.socialSettingsIconSelector; },
|
||||
childView: SocialBlockSettingsIconView,
|
||||
SocialBlockSettingsIconCollectionView = Marionette.CollectionView.extend({
|
||||
behaviors: {
|
||||
SortableBehavior: {
|
||||
items: '> div',
|
||||
},
|
||||
},
|
||||
childViewContainer: '#mailpoet_social_icon_selector_contents',
|
||||
childView: SocialBlockSettingsIconView,
|
||||
});
|
||||
|
||||
// Select icons section container view
|
||||
SocialBlockSettingsIconSelectorView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.socialSettingsIconSelector; },
|
||||
regions: {
|
||||
'icons': '#mailpoet_social_icon_selector_contents'
|
||||
},
|
||||
events: {
|
||||
'click .mailpoet_add_social_icon': 'addSocialIcon',
|
||||
},
|
||||
modelEvents: {
|
||||
'change:iconSet': 'render',
|
||||
},
|
||||
behaviors: {
|
||||
SortableBehavior: {
|
||||
items: '#mailpoet_social_icon_selector_contents > div',
|
||||
},
|
||||
},
|
||||
constructor: function() {
|
||||
// Set the icon collection to be handled by this view as well
|
||||
arguments[0].collection = arguments[0].model.get('icons');
|
||||
Marionette.CompositeView.apply(this, arguments);
|
||||
},
|
||||
addSocialIcon: function() {
|
||||
// Add a social icon with default values
|
||||
this.collection.add({});
|
||||
this.model.get('icons').add({});
|
||||
},
|
||||
onRender: function() {
|
||||
this.showChildView('icons', new SocialBlockSettingsIconCollectionView({
|
||||
collection: this.model.get('icons')
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
SocialBlockSettingsStylesView = Marionette.ItemView.extend({
|
||||
SocialBlockSettingsStylesView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.socialSettingsStyles; },
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
@ -359,7 +245,7 @@ define([
|
||||
initialize: function() {
|
||||
this.listenTo(this.model.get('icons'), 'add remove change', this.render);
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
var allIconSets = App.getAvailableStyles().get('socialIconSets');
|
||||
return {
|
||||
activeSet: this.model.get('iconSet'),
|
||||
@ -411,7 +297,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('social', {
|
||||
blockModel: Module.SocialBlockModel,
|
||||
blockView: Module.SocialBlockView,
|
||||
|
@ -50,7 +50,7 @@ define([
|
||||
},
|
||||
onRender: function() {
|
||||
this.toolsView = new Module.SpacerBlockToolsView({ model: this.model });
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
changeHeight: function() {
|
||||
this.$('.mailpoet_spacer').css('height', this.model.get('styles.block.height'));
|
||||
@ -87,7 +87,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('spacer', {
|
||||
blockModel: Module.SpacerBlockModel,
|
||||
blockView: Module.SpacerBlockView,
|
||||
|
@ -32,7 +32,7 @@ define([
|
||||
validElements: "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]",
|
||||
invalidElements: "script",
|
||||
blockFormats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
|
||||
plugins: "link code textcolor colorpicker mailpoet_shortcodes",
|
||||
plugins: "link lists code textcolor colorpicker mailpoet_shortcodes paste",
|
||||
configurationFilter: function(originalSettings) {
|
||||
return _.extend({}, originalSettings, {
|
||||
mailpoet_shortcodes: App.getConfig().get('shortcodes').toJSON(),
|
||||
@ -58,7 +58,7 @@ define([
|
||||
settings: false,
|
||||
},
|
||||
});
|
||||
this.toolsRegion.show(this.toolsView);
|
||||
this.showChildView('toolsRegion', this.toolsView);
|
||||
},
|
||||
onTextEditorChange: function(newContent) {
|
||||
this.model.set('text', newContent);
|
||||
@ -71,13 +71,6 @@ define([
|
||||
this.enableDragging();
|
||||
this.enableShowingTools();
|
||||
},
|
||||
|
||||
disableDragging: function() {
|
||||
this.$('.mailpoet_content').addClass('mailpoet_ignore_drag');
|
||||
},
|
||||
enableDragging: function() {
|
||||
this.$('.mailpoet_content').removeClass('mailpoet_ignore_drag');
|
||||
},
|
||||
});
|
||||
|
||||
Module.TextBlockToolsView = base.BlockToolsView.extend({
|
||||
@ -100,7 +93,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('before:start', function() {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerBlockType('text', {
|
||||
blockModel: Module.TextBlockModel,
|
||||
blockView: Module.TextBlockView,
|
||||
|
@ -9,6 +9,7 @@ define([
|
||||
|
||||
Module._query = function(args) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'automatedLatestContent',
|
||||
action: args.action,
|
||||
data: args.options || {}
|
||||
@ -81,6 +82,7 @@ define([
|
||||
|
||||
Module.saveNewsletter = function(options) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: options || {}
|
||||
@ -89,13 +91,14 @@ define([
|
||||
|
||||
Module.previewNewsletter = function(options) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'sendPreview',
|
||||
data: options || {}
|
||||
});
|
||||
};
|
||||
|
||||
App.on('start', function(options) {
|
||||
App.on('start', function(App, options) {
|
||||
// Prefetch post types
|
||||
Module.getPostTypes();
|
||||
});
|
||||
|
@ -24,7 +24,7 @@ define([
|
||||
return Module._config;
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.on('before:start', function(App, options) {
|
||||
// Expose config methods globally
|
||||
App.getConfig = Module.getConfig;
|
||||
App.setConfig = Module.setConfig;
|
||||
|
@ -67,7 +67,7 @@ define([
|
||||
return _.filter(blocks, predicate);
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.on('before:start', function(App, options) {
|
||||
// Expose block methods globally
|
||||
App.registerBlockType = Module.registerBlockType;
|
||||
App.getBlockTypeModel = Module.getBlockTypeModel;
|
||||
@ -80,7 +80,7 @@ define([
|
||||
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
App.on('start', function(App, options) {
|
||||
var body = options.newsletter.body;
|
||||
var content = (_.has(body, 'content')) ? body.content : {};
|
||||
|
||||
@ -97,7 +97,7 @@ define([
|
||||
renderOptions: { depth: 0 },
|
||||
});
|
||||
|
||||
App._appView.contentRegion.show(App._contentContainerView);
|
||||
App._appView.showChildView('contentRegion', App._contentContainerView);
|
||||
});
|
||||
|
||||
|
||||
|
@ -10,9 +10,9 @@ define([
|
||||
|
||||
var Module = {};
|
||||
|
||||
Module.HeadingView = Marionette.ItemView.extend({
|
||||
Module.HeadingView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.heading; },
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
};
|
||||
@ -28,8 +28,8 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
App._appView.headingRegion.show(new Module.HeadingView({ model: App.getNewsletter() }));
|
||||
App.on('start', function(App, options) {
|
||||
App._appView.showChildView('headingRegion', new Module.HeadingView({ model: App.getNewsletter() }));
|
||||
});
|
||||
|
||||
return Module;
|
||||
|
@ -109,6 +109,7 @@ define([
|
||||
});
|
||||
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: data
|
||||
@ -142,7 +143,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
Module.SaveView = Marionette.LayoutView.extend({
|
||||
Module.SaveView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.save; },
|
||||
events: {
|
||||
'click .mailpoet_save_button': 'save',
|
||||
@ -340,7 +341,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.save = Module.saveAndProvidePromise;
|
||||
App.getChannel().on('autoSave', Module.autoSave);
|
||||
|
||||
@ -349,9 +350,9 @@ define([
|
||||
App.getChannel().on('save', function(saveResult) { App.save(saveResult); });
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
App.on('start', function(App, options) {
|
||||
var saveView = new Module.SaveView();
|
||||
App._appView.bottomRegion.show(saveView);
|
||||
App._appView.showChildView('bottomRegion', saveView);
|
||||
});
|
||||
|
||||
return Module;
|
||||
|
@ -52,7 +52,7 @@ define([
|
||||
Module.registerLayoutWidget = function(widget) { return Module._layoutWidgets.add(widget); };
|
||||
Module.getLayoutWidgets = function() { return Module._layoutWidgets; };
|
||||
|
||||
var SidebarView = Marionette.LayoutView.extend({
|
||||
var SidebarView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.sidebar; },
|
||||
regions: {
|
||||
contentRegion: '.mailpoet_content_region',
|
||||
@ -96,17 +96,17 @@ define([
|
||||
.on('scroll', this.updateHorizontalScroll.bind(this));
|
||||
},
|
||||
onRender: function() {
|
||||
this.contentRegion.show(new Module.SidebarWidgetsView({
|
||||
collection: App.getWidgets(),
|
||||
}));
|
||||
this.layoutRegion.show(new Module.SidebarLayoutWidgetsView({
|
||||
collection: App.getLayoutWidgets(),
|
||||
}));
|
||||
this.stylesRegion.show(new Module.SidebarStylesView({
|
||||
this.showChildView('contentRegion', new Module.SidebarWidgetsView(
|
||||
App.getWidgets()
|
||||
));
|
||||
this.showChildView('layoutRegion', new Module.SidebarLayoutWidgetsView(
|
||||
App.getLayoutWidgets()
|
||||
));
|
||||
this.showChildView('stylesRegion', new Module.SidebarStylesView({
|
||||
model: App.getGlobalStyles(),
|
||||
availableStyles: App.getAvailableStyles(),
|
||||
}));
|
||||
this.previewRegion.show(new Module.SidebarPreviewView());
|
||||
this.showChildView('previewRegion', new Module.SidebarPreviewView());
|
||||
},
|
||||
updateHorizontalScroll: function() {
|
||||
// Fixes the sidebar so that on narrower screens the horizontal
|
||||
@ -136,15 +136,31 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Draggable widget collection view
|
||||
*/
|
||||
Module.SidebarWidgetsCollectionView = Marionette.CollectionView.extend({
|
||||
childView: function(item) { return item.get('widgetView'); }
|
||||
});
|
||||
|
||||
/**
|
||||
* Responsible for rendering draggable content widgets
|
||||
*/
|
||||
Module.SidebarWidgetsView = Marionette.CompositeView.extend({
|
||||
Module.SidebarWidgetsView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.sidebarContent; },
|
||||
getChildView: function(model) {
|
||||
return model.get('widgetView');
|
||||
regions: {
|
||||
widgets: '.mailpoet_region_content'
|
||||
},
|
||||
childViewContainer: '.mailpoet_region_content',
|
||||
|
||||
initialize: function(widgets) {
|
||||
this.widgets = widgets;
|
||||
},
|
||||
|
||||
onRender: function() {
|
||||
this.showChildView('widgets', new Module.SidebarWidgetsCollectionView({
|
||||
collection: this.widgets
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@ -153,10 +169,11 @@ define([
|
||||
Module.SidebarLayoutWidgetsView = Module.SidebarWidgetsView.extend({
|
||||
getTemplate: function() { return templates.sidebarLayout; },
|
||||
});
|
||||
|
||||
/**
|
||||
* Responsible for managing global styles
|
||||
*/
|
||||
Module.SidebarStylesView = Marionette.LayoutView.extend({
|
||||
Module.SidebarStylesView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.sidebarStyles; },
|
||||
behaviors: {
|
||||
ColorPickerBehavior: {},
|
||||
@ -199,7 +216,7 @@ define([
|
||||
"change #mailpoet_background_color": _.partial(this.changeColorField, 'body.backgroundColor'),
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
availableStyles: this.availableStyles.toJSON(),
|
||||
@ -220,7 +237,7 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.SidebarPreviewView = Marionette.LayoutView.extend({
|
||||
Module.SidebarPreviewView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.sidebarPreview; },
|
||||
events: {
|
||||
'click .mailpoet_show_preview': 'showPreview',
|
||||
@ -243,6 +260,7 @@ define([
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'showPreview',
|
||||
data: json,
|
||||
@ -318,14 +336,14 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.NewsletterPreviewView = Marionette.ItemView.extend({
|
||||
Module.NewsletterPreviewView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.newsletterPreview; },
|
||||
initialize: function(options) {
|
||||
this.previewUrl = options.previewUrl;
|
||||
this.width = App.getConfig().get('newsletterPreview.width');
|
||||
this.height = App.getConfig().get('newsletterPreview.height')
|
||||
},
|
||||
templateHelpers: function() {
|
||||
templateContext: function() {
|
||||
return {
|
||||
previewUrl: this.previewUrl,
|
||||
width: this.width,
|
||||
@ -334,18 +352,18 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.on('before:start', function(App, options) {
|
||||
App.registerWidget = Module.registerWidget;
|
||||
App.getWidgets = Module.getWidgets;
|
||||
App.registerLayoutWidget = Module.registerLayoutWidget;
|
||||
App.getLayoutWidgets = Module.getLayoutWidgets;
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
App.on('start', function(App, options) {
|
||||
var stylesModel = App.getGlobalStyles(),
|
||||
sidebarView = new SidebarView();
|
||||
|
||||
App._appView.sidebarRegion.show(sidebarView);
|
||||
App._appView.showChildView('sidebarRegion', sidebarView);
|
||||
});
|
||||
|
||||
return Module;
|
||||
|
@ -46,11 +46,14 @@ define([
|
||||
},
|
||||
});
|
||||
|
||||
Module.StylesView = Marionette.ItemView.extend({
|
||||
Module.StylesView = Marionette.View.extend({
|
||||
getTemplate: function() { return templates.styles; },
|
||||
modelEvents: {
|
||||
'change': 'render',
|
||||
},
|
||||
serializeData: function() {
|
||||
return this.model.toJSON();
|
||||
}
|
||||
});
|
||||
|
||||
Module._globalStyles = new SuperModel();
|
||||
@ -65,7 +68,7 @@ define([
|
||||
return App.getConfig().get('availableStyles');
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.on('before:start', function(App, options) {
|
||||
// Expose style methods to global application
|
||||
App.getGlobalStyles = Module.getGlobalStyles;
|
||||
App.setGlobalStyles = Module.setGlobalStyles;
|
||||
@ -76,9 +79,9 @@ define([
|
||||
this.setGlobalStyles(globalStyles);
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
App.on('start', function(App, options) {
|
||||
var stylesView = new Module.StylesView({ model: App.getGlobalStyles() });
|
||||
App._appView.stylesRegion.show(stylesView);
|
||||
App._appView.showChildView('stylesRegion', stylesView);
|
||||
});
|
||||
|
||||
return Module;
|
||||
|
@ -11,13 +11,13 @@
|
||||
/*jshint unused:false */
|
||||
/*global tinymce:true */
|
||||
tinymce.PluginManager.add('mailpoet_shortcodes', function(editor, url) {
|
||||
var appendLabelAndClose = function(text) {
|
||||
editor.insertContent('[' + text + ']');
|
||||
var appendLabelAndClose = function(shortcode) {
|
||||
editor.insertContent(shortcode);
|
||||
editor.windowManager.close();
|
||||
},
|
||||
generateOnClickFunc = function(id) {
|
||||
generateOnClickFunc = function(shortcode) {
|
||||
return function() {
|
||||
appendLabelAndClose(id);
|
||||
appendLabelAndClose(shortcode);
|
||||
};
|
||||
};
|
||||
|
||||
|
37
assets/js/src/newsletters/badges/badge.jsx
Normal file
37
assets/js/src/newsletters/badges/badge.jsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
class Badge extends React.Component {
|
||||
render() {
|
||||
const badgeClasses = classNames(
|
||||
'mailpoet_badge',
|
||||
this.props.type ? `mailpoet_badge_${this.props.type}` : ''
|
||||
);
|
||||
|
||||
const tooltip = this.props.tooltip ? this.props.tooltip.replace(/\n/g, '<br />') : false;
|
||||
// tooltip ID must be unique, defaults to tooltip text
|
||||
const tooltipId = this.props.tooltipId || tooltip;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span
|
||||
className={badgeClasses}
|
||||
data-tip={tooltip}
|
||||
data-for={tooltipId}
|
||||
>
|
||||
{this.props.name}
|
||||
</span>
|
||||
{ tooltip && (
|
||||
<ReactTooltip
|
||||
place="right"
|
||||
multiline={true}
|
||||
id={tooltipId}
|
||||
/>
|
||||
) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Badge;
|
105
assets/js/src/newsletters/badges/stats.jsx
Normal file
105
assets/js/src/newsletters/badges/stats.jsx
Normal file
@ -0,0 +1,105 @@
|
||||
import MailPoet from 'mailpoet'
|
||||
import React from 'react'
|
||||
|
||||
import Badge from './badge.jsx'
|
||||
|
||||
const badges = {
|
||||
excellent: {
|
||||
name: MailPoet.I18n.t('excellentBadgeName'),
|
||||
tooltipTitle: MailPoet.I18n.t('excellentBadgeTooltip')
|
||||
},
|
||||
good: {
|
||||
name: MailPoet.I18n.t('goodBadgeName'),
|
||||
tooltipTitle: MailPoet.I18n.t('goodBadgeTooltip')
|
||||
},
|
||||
bad: {
|
||||
name: MailPoet.I18n.t('badBadgeName'),
|
||||
tooltipTitle: MailPoet.I18n.t('badBadgeTooltip')
|
||||
}
|
||||
};
|
||||
|
||||
const stats = {
|
||||
opened: {
|
||||
badgeRanges: [30, 10, 0],
|
||||
badgeTypes: [
|
||||
'excellent',
|
||||
'good',
|
||||
'bad'
|
||||
],
|
||||
tooltipText: MailPoet.I18n.t('openedStatTooltip'),
|
||||
},
|
||||
clicked: {
|
||||
badgeRanges: [3, 1, 0],
|
||||
badgeTypes: [
|
||||
'excellent',
|
||||
'good',
|
||||
'bad'
|
||||
],
|
||||
tooltipText: MailPoet.I18n.t('clickedStatTooltip')
|
||||
},
|
||||
unsubscribed: {
|
||||
badgeRanges: [3, 1, 0],
|
||||
badgeTypes: [
|
||||
'bad',
|
||||
'good',
|
||||
'excellent'
|
||||
],
|
||||
tooltipText: MailPoet.I18n.t('unsubscribedStatTooltip')
|
||||
},
|
||||
};
|
||||
|
||||
class StatsBadge extends React.Component {
|
||||
getBadgeType(stat, rate) {
|
||||
const len = stat.badgeRanges.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (rate > stat.badgeRanges[i]) {
|
||||
return stat.badgeTypes[i];
|
||||
}
|
||||
}
|
||||
// rate must be zero at this point
|
||||
return stat.badgeTypes[len - 1];
|
||||
}
|
||||
render() {
|
||||
const stat = stats[this.props.stat] || null;
|
||||
if (!stat) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rate = this.props.rate;
|
||||
if (rate < 0 || rate > 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const badgeType = this.getBadgeType(stat, rate);
|
||||
const badge = badges[badgeType] || null;
|
||||
if (!badge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tooltipText = `${badge.tooltipTitle}\n\n${stat.tooltipText}`;
|
||||
const tooltipId = this.props.tooltipId || null;
|
||||
|
||||
const content = (
|
||||
<Badge
|
||||
type={badgeType}
|
||||
name={badge.name}
|
||||
tooltip={tooltipText}
|
||||
tooltipId={tooltipId}
|
||||
/>
|
||||
);
|
||||
|
||||
if (this.props.headline) {
|
||||
return (
|
||||
<div>
|
||||
<span className={`mailpoet_stat_${badgeType}`}>
|
||||
{this.props.headline}
|
||||
</span> {content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
export default StatsBadge;
|
@ -1,13 +1,18 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ReactStringReplace from 'react-string-replace'
|
||||
import { Link } from 'react-router'
|
||||
import MailPoet from 'mailpoet'
|
||||
import classNames from 'classnames'
|
||||
import moment from 'moment'
|
||||
import jQuery from 'jquery'
|
||||
import Hooks from 'wp-js-hooks'
|
||||
import StatsBadge from 'newsletters/badges/stats.jsx'
|
||||
|
||||
const _QueueMixin = {
|
||||
pauseSending: function(newsletter) {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'pause',
|
||||
data: {
|
||||
@ -27,6 +32,7 @@ const _QueueMixin = {
|
||||
},
|
||||
resumeSending: function(newsletter) {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'resume',
|
||||
data: {
|
||||
@ -138,40 +144,162 @@ const _QueueMixin = {
|
||||
};
|
||||
|
||||
const _StatisticsMixin = {
|
||||
renderStatistics: function(newsletter) {
|
||||
if (
|
||||
newsletter.statistics
|
||||
&& newsletter.queue
|
||||
&& newsletter.queue.status !== 'scheduled'
|
||||
) {
|
||||
const total_sent = ~~(newsletter.queue.count_processed);
|
||||
|
||||
let percentage_clicked = 0;
|
||||
let percentage_opened = 0;
|
||||
let percentage_unsubscribed = 0;
|
||||
|
||||
if (total_sent > 0) {
|
||||
percentage_clicked = Math.round(
|
||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
||||
);
|
||||
percentage_opened = Math.round(
|
||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
||||
);
|
||||
percentage_unsubscribed = Math.round(
|
||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
renderStatistics: function(newsletter, is_sent, current_time) {
|
||||
if (is_sent === undefined) {
|
||||
// condition for standard and post notification listings
|
||||
is_sent = newsletter.statistics
|
||||
&& newsletter.queue
|
||||
&& newsletter.queue.status !== 'scheduled'
|
||||
}
|
||||
if (!is_sent) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
let params = {};
|
||||
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
|
||||
|
||||
// welcome emails provide explicit total_sent value
|
||||
const total_sent = ~~(newsletter.total_sent || newsletter.queue.count_processed);
|
||||
|
||||
let percentage_clicked = 0;
|
||||
let percentage_opened = 0;
|
||||
let percentage_unsubscribed = 0;
|
||||
|
||||
if (total_sent > 0) {
|
||||
percentage_clicked = (newsletter.statistics.clicked * 100) / total_sent;
|
||||
percentage_opened = (newsletter.statistics.opened * 100) / total_sent;
|
||||
percentage_unsubscribed = (newsletter.statistics.unsubscribed * 100) / total_sent;
|
||||
}
|
||||
|
||||
// format to 1 decimal place
|
||||
const percentage_clicked_display = MailPoet.Num.toLocaleFixed(percentage_clicked, 1);
|
||||
const percentage_opened_display = MailPoet.Num.toLocaleFixed(percentage_opened, 1);
|
||||
const percentage_unsubscribed_display = MailPoet.Num.toLocaleFixed(percentage_unsubscribed, 1);
|
||||
|
||||
let show_stats_timeout,
|
||||
newsletter_date,
|
||||
sent_hours_ago,
|
||||
too_early_for_stats,
|
||||
show_kb_link;
|
||||
if (current_time !== undefined) {
|
||||
// standard emails and post notifications:
|
||||
// display green box for newsletters that were just sent
|
||||
show_stats_timeout = 6; // in hours
|
||||
newsletter_date = newsletter.queue.scheduled_at || newsletter.queue.created_at;
|
||||
sent_hours_ago = moment(current_time).diff(moment(newsletter_date), 'hours');
|
||||
too_early_for_stats = sent_hours_ago < show_stats_timeout;
|
||||
show_kb_link = true;
|
||||
} else {
|
||||
// welcome emails: no green box and KB link
|
||||
too_early_for_stats = false;
|
||||
show_kb_link = false;
|
||||
}
|
||||
|
||||
const improveStatsKBLink = 'http://beta.docs.mailpoet.com/article/191-how-to-improve-my-open-and-click-rates';
|
||||
|
||||
// thresholds to display badges
|
||||
const min_newsletters_sent = 20;
|
||||
const min_newsletter_opens = 5;
|
||||
|
||||
let content;
|
||||
if (total_sent >= min_newsletters_sent
|
||||
&& newsletter.statistics.opened >= min_newsletter_opens
|
||||
&& !too_early_for_stats
|
||||
) {
|
||||
// display stats with badges
|
||||
content = (
|
||||
<div className="mailpoet_stats_text">
|
||||
<div>
|
||||
<span>{ percentage_opened_display }% </span>
|
||||
<StatsBadge
|
||||
stat="opened"
|
||||
rate={percentage_opened}
|
||||
tooltipId={`opened-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{ percentage_clicked_display }% </span>
|
||||
<StatsBadge
|
||||
stat="clicked"
|
||||
rate={percentage_clicked}
|
||||
tooltipId={`clicked-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mailpoet_stat_hidden">{ percentage_unsubscribed_display }%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// display simple stats
|
||||
content = (
|
||||
<div>
|
||||
<span className="mailpoet_stats_text">
|
||||
{ percentage_opened_display }%,
|
||||
{ " " }
|
||||
{ percentage_clicked_display }%
|
||||
<span className="mailpoet_stat_hidden">
|
||||
, { percentage_unsubscribed_display }%
|
||||
</span>
|
||||
</span>
|
||||
{ too_early_for_stats && (
|
||||
<div className="mailpoet_badge mailpoet_badge_green">
|
||||
{MailPoet.I18n.t('checkBackInHours')
|
||||
.replace('%$1d', show_stats_timeout - sent_hours_ago)}
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// thresholds to display bad open rate help
|
||||
const max_percentage_opened = 5;
|
||||
const min_sent_hours_ago = 24;
|
||||
const min_total_sent = 10;
|
||||
|
||||
let after_content;
|
||||
if (show_kb_link
|
||||
&& percentage_opened < max_percentage_opened
|
||||
&& sent_hours_ago >= min_sent_hours_ago
|
||||
&& total_sent >= min_total_sent
|
||||
) {
|
||||
// help link for bad open rate
|
||||
after_content = (
|
||||
<div>
|
||||
<a
|
||||
href={improveStatsKBLink}
|
||||
target="_blank"
|
||||
className="mailpoet_stat_link_small"
|
||||
>
|
||||
{MailPoet.I18n.t('improveThisLinkText')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (total_sent > 0 && params.link) {
|
||||
// wrap content in a link
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
key={ `stats-${newsletter.id}` }
|
||||
to={ params.link }
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
{after_content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{content}
|
||||
{after_content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,6 +353,7 @@ const _MailerMixin = {
|
||||
},
|
||||
resumeMailerSending() {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'mailer',
|
||||
action: 'resumeSending'
|
||||
}).done(function() {
|
||||
|
@ -129,6 +129,7 @@ const newsletter_actions = [
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
@ -164,6 +165,7 @@ const NewsletterListNotification = React.createClass({
|
||||
e.persist();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'setStatus',
|
||||
data: {
|
||||
|
@ -3,6 +3,7 @@ import { Router, Link } from 'react-router'
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
import Hooks from 'wp-js-hooks'
|
||||
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
@ -40,7 +41,7 @@ const columns = [
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
let newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
@ -53,8 +54,15 @@ const newsletter_actions = [
|
||||
}
|
||||
];
|
||||
|
||||
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletter_actions);
|
||||
|
||||
const NewsletterListNotificationHistory = React.createClass({
|
||||
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ],
|
||||
renderSentDate: function(newsletter) {
|
||||
return (newsletter.queue.status === 'completed')
|
||||
? ( <abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr> )
|
||||
: MailPoet.I18n.t('notSentYet')
|
||||
},
|
||||
renderItem: function(newsletter, actions, meta) {
|
||||
const rowClasses = classNames(
|
||||
'manage-column',
|
||||
@ -75,7 +83,7 @@ const NewsletterListNotificationHistory = React.createClass({
|
||||
<a
|
||||
href={ newsletter.preview_url }
|
||||
target="_blank"
|
||||
>{ newsletter.queue.newsletter_rendered_subject }</a>
|
||||
>{ newsletter.queue.newsletter_rendered_subject || newsletter.subject }</a>
|
||||
</strong>
|
||||
{ actions }
|
||||
</td>
|
||||
@ -87,11 +95,11 @@ const NewsletterListNotificationHistory = React.createClass({
|
||||
</td>
|
||||
{ (mailpoet_tracking_enabled === true) ? (
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
{ this.renderStatistics(newsletter, undefined, meta.current_time) }
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
|
||||
{ this.renderSentDate(newsletter) }
|
||||
</td>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { Router, Link } from 'react-router'
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
import Hooks from 'wp-js-hooks'
|
||||
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
@ -98,7 +99,7 @@ const bulk_actions = [
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
let newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
@ -124,6 +125,7 @@ const newsletter_actions = [
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
@ -151,6 +153,8 @@ const newsletter_actions = [
|
||||
}
|
||||
];
|
||||
|
||||
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletter_actions);
|
||||
|
||||
const NewsletterListStandard = React.createClass({
|
||||
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ],
|
||||
renderItem: function(newsletter, actions, meta) {
|
||||
@ -183,7 +187,7 @@ const NewsletterListStandard = React.createClass({
|
||||
</td>
|
||||
{ (mailpoet_tracking_enabled === true) ? (
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
{ this.renderStatistics(newsletter, undefined, meta.current_time) }
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
|
@ -5,12 +5,13 @@ import { createHashHistory } from 'history'
|
||||
import Listing from 'listing/listing.jsx'
|
||||
import ListingTabs from 'newsletters/listings/tabs.jsx'
|
||||
|
||||
import { MailerMixin } from 'newsletters/listings/mixins.jsx'
|
||||
import { StatisticsMixin, MailerMixin } from 'newsletters/listings/mixins.jsx'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import jQuery from 'jquery'
|
||||
import MailPoet from 'mailpoet'
|
||||
import _ from 'underscore'
|
||||
import Hooks from 'wp-js-hooks'
|
||||
|
||||
const mailpoet_roles = window.mailpoet_roles || {};
|
||||
const mailpoet_segments = window.mailpoet_segments || {};
|
||||
@ -100,7 +101,7 @@ const bulk_actions = [
|
||||
}
|
||||
];
|
||||
|
||||
const newsletter_actions = [
|
||||
let newsletter_actions = [
|
||||
{
|
||||
name: 'view',
|
||||
link: function(newsletter) {
|
||||
@ -121,46 +122,22 @@ const newsletter_actions = [
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: function(newsletter, refresh) {
|
||||
return MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
id: newsletter.id
|
||||
}
|
||||
}).done((response) => {
|
||||
MailPoet.Notice.success(
|
||||
(MailPoet.I18n.t('newsletterDuplicated')).replace(
|
||||
'%$1s', response.data.subject
|
||||
)
|
||||
);
|
||||
refresh();
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function(error) { return error.message; }),
|
||||
{ scroll: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'trash'
|
||||
}
|
||||
];
|
||||
|
||||
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletter_actions);
|
||||
|
||||
const NewsletterListWelcome = React.createClass({
|
||||
mixins: [ MailerMixin ],
|
||||
mixins: [ StatisticsMixin, MailerMixin ],
|
||||
updateStatus: function(e) {
|
||||
// make the event persist so that we can still override the selected value
|
||||
// in the ajax callback
|
||||
e.persist();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'setStatus',
|
||||
data: {
|
||||
@ -274,35 +251,6 @@ const NewsletterListWelcome = React.createClass({
|
||||
</span>
|
||||
);
|
||||
},
|
||||
renderStatistics: function(newsletter) {
|
||||
if (mailpoet_tracking_enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newsletter.total_sent > 0 && newsletter.statistics) {
|
||||
const total_sent = ~~(newsletter.total_sent);
|
||||
|
||||
const percentage_clicked = Math.round(
|
||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
||||
);
|
||||
const percentage_opened = Math.round(
|
||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
||||
);
|
||||
const percentage_unsubscribed = Math.round(
|
||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
||||
);
|
||||
|
||||
return (
|
||||
<span>
|
||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
renderItem: function(newsletter, actions) {
|
||||
const rowClasses = classNames(
|
||||
'manage-column',
|
||||
@ -329,7 +277,10 @@ const NewsletterListWelcome = React.createClass({
|
||||
</td>
|
||||
{ (mailpoet_tracking_enabled === true) ? (
|
||||
<td className="column" data-colname={ MailPoet.I18n.t('statistics') }>
|
||||
{ this.renderStatistics(newsletter) }
|
||||
{ this.renderStatistics(
|
||||
newsletter,
|
||||
newsletter.total_sent > 0 && newsletter.statistics
|
||||
) }
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
|
||||
|
@ -2,13 +2,13 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
|
||||
import { createHashHistory } from 'history'
|
||||
import Hooks from 'wp-js-hooks'
|
||||
|
||||
import NewsletterTypes from 'newsletters/types.jsx'
|
||||
import NewsletterTemplates from 'newsletters/templates.jsx'
|
||||
import NewsletterSend from 'newsletters/send.jsx'
|
||||
|
||||
import NewsletterTypeStandard from 'newsletters/types/standard.jsx'
|
||||
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx'
|
||||
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx'
|
||||
|
||||
import NewsletterListStandard from 'newsletters/listings/standard.jsx'
|
||||
@ -27,6 +27,9 @@ const App = React.createClass({
|
||||
const container = document.getElementById('newsletters_container');
|
||||
|
||||
if(container) {
|
||||
let extra_routes = [];
|
||||
extra_routes = Hooks.applyFilters('mailpoet_newsletters_before_router', extra_routes);
|
||||
|
||||
const mailpoet_listing = ReactDOM.render((
|
||||
<Router history={ history }>
|
||||
<Route path="/" component={ App }>
|
||||
@ -40,12 +43,13 @@ if(container) {
|
||||
<Route path="new" component={ NewsletterTypes } />
|
||||
{/* New newsletter: types */}
|
||||
<Route path="new/standard" component={ NewsletterTypeStandard } />
|
||||
<Route path="new/welcome" component={ NewsletterTypeWelcome } />
|
||||
<Route path="new/notification" component={ NewsletterTypeNotification } />
|
||||
{/* Template selection */}
|
||||
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
||||
{/* Sending options */}
|
||||
<Route path="send/:id" component={ NewsletterSend } />
|
||||
{/* Extra routes */}
|
||||
{ extra_routes.map(rt => <Route key={rt.path} path={rt.path} component={rt.component} />) }
|
||||
</Route>
|
||||
</Router>
|
||||
), container);
|
||||
|
@ -64,6 +64,7 @@ define(
|
||||
this.setState({ loading: true });
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'get',
|
||||
data: {
|
||||
@ -97,6 +98,7 @@ define(
|
||||
case 'notification':
|
||||
case 'welcome':
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'setStatus',
|
||||
data: {
|
||||
@ -120,6 +122,7 @@ define(
|
||||
}).fail(this._showError);
|
||||
default:
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'sendingQueue',
|
||||
action: 'add',
|
||||
data: {
|
||||
@ -184,6 +187,7 @@ define(
|
||||
);
|
||||
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: newsletterData,
|
||||
|
@ -1,11 +1,13 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet',
|
||||
'wp-js-hooks',
|
||||
'newsletters/types/notification/scheduling.jsx',
|
||||
'underscore'
|
||||
],
|
||||
function(
|
||||
MailPoet,
|
||||
Hooks,
|
||||
Scheduling,
|
||||
_
|
||||
) {
|
||||
@ -35,8 +37,9 @@ define(
|
||||
tip: MailPoet.I18n.t('segmentsTip'),
|
||||
type: 'selection',
|
||||
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
id: 'mailpoet_segments',
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
multiple: true,
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
@ -101,6 +104,8 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
fields = Hooks.applyFilters('mailpoet_newsletters_3rd_step_fields', fields);
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
|
@ -4,6 +4,7 @@ define(
|
||||
'jquery',
|
||||
'underscore',
|
||||
'mailpoet',
|
||||
'wp-js-hooks',
|
||||
'form/fields/checkbox.jsx',
|
||||
'form/fields/select.jsx',
|
||||
'form/fields/text.jsx',
|
||||
@ -13,6 +14,7 @@ define(
|
||||
jQuery,
|
||||
_,
|
||||
MailPoet,
|
||||
Hooks,
|
||||
Checkbox,
|
||||
Select,
|
||||
Text
|
||||
@ -339,8 +341,9 @@ define(
|
||||
tip: MailPoet.I18n.t('segmentsTip'),
|
||||
type: 'selection',
|
||||
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
|
||||
id: "mailpoet_segments",
|
||||
endpoint: "segments",
|
||||
id: 'mailpoet_segments',
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
multiple: true,
|
||||
filter: function(segment) {
|
||||
return !!(!segment.deleted_at);
|
||||
@ -411,6 +414,8 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
fields = Hooks.applyFilters('mailpoet_newsletters_3rd_step_fields', fields);
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
|
@ -1,10 +1,12 @@
|
||||
define(
|
||||
[
|
||||
'mailpoet',
|
||||
'wp-js-hooks',
|
||||
'newsletters/types/welcome/scheduling.jsx'
|
||||
],
|
||||
function(
|
||||
MailPoet,
|
||||
Hooks,
|
||||
Scheduling
|
||||
) {
|
||||
|
||||
@ -71,6 +73,8 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
fields = Hooks.applyFilters('mailpoet_newsletters_3rd_step_fields', fields);
|
||||
|
||||
return {
|
||||
getFields: function(newsletter) {
|
||||
return fields;
|
||||
@ -83,4 +87,3 @@ define(
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -27,6 +27,7 @@ define(
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'save',
|
||||
data: template
|
||||
@ -97,6 +98,7 @@ define(
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'getAll',
|
||||
}).always(() => {
|
||||
@ -130,6 +132,7 @@ define(
|
||||
}
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'save',
|
||||
data: {
|
||||
@ -158,6 +161,7 @@ define(
|
||||
)
|
||||
) {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletterTemplates',
|
||||
action: 'delete',
|
||||
data: {
|
||||
|
@ -2,12 +2,14 @@ define(
|
||||
[
|
||||
'react',
|
||||
'mailpoet',
|
||||
'wp-js-hooks',
|
||||
'react-router',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
React,
|
||||
MailPoet,
|
||||
Hooks,
|
||||
Router,
|
||||
Breadcrumb
|
||||
) {
|
||||
@ -22,6 +24,7 @@ define(
|
||||
},
|
||||
createNewsletter: function(type) {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: {
|
||||
@ -40,6 +43,49 @@ define(
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var types = [
|
||||
{
|
||||
'id': 'standard',
|
||||
'title': MailPoet.I18n.t('regularNewsletterTypeTitle'),
|
||||
'description': MailPoet.I18n.t('regularNewsletterTypeDescription'),
|
||||
'action': function() {
|
||||
return (
|
||||
<a className="button button-primary" onClick={ this.createNewsletter.bind(null, 'standard') }>
|
||||
{MailPoet.I18n.t('create')}
|
||||
</a>
|
||||
)
|
||||
}.bind(this)()
|
||||
},
|
||||
{
|
||||
'id': 'welcome',
|
||||
'title': MailPoet.I18n.t('welcomeNewsletterTypeTitle'),
|
||||
'description': MailPoet.I18n.t('welcomeNewsletterTypeDescription'),
|
||||
'action': function() {
|
||||
return (
|
||||
<div>
|
||||
<a href="?page=mailpoet-premium" target="_blank">
|
||||
{MailPoet.I18n.t('getPremiumVersion')}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}()
|
||||
},
|
||||
{
|
||||
'id': 'notification',
|
||||
'title': MailPoet.I18n.t('postNotificationNewsletterTypeTitle'),
|
||||
'description': MailPoet.I18n.t('postNotificationNewsletterTypeDescription'),
|
||||
'action': function() {
|
||||
return (
|
||||
<a className="button button-primary" onClick={ this.setupNewsletter.bind(null, 'notification') }>
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
)
|
||||
}.bind(this)()
|
||||
}
|
||||
];
|
||||
|
||||
types = Hooks.applyFilters('mailpoet_newsletters_types', types, this);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{MailPoet.I18n.t('pickCampaignType')}</h1>
|
||||
@ -47,65 +93,24 @@ define(
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<ul className="mailpoet_boxes clearfix">
|
||||
<li data-type="standard">
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
{types.map(function(type, index) {
|
||||
return (
|
||||
<li key={index} data-type={type.id}>
|
||||
<div>
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
{MailPoet.I18n.t('regularNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mailpoet_description">
|
||||
<h3>{type.title}</h3>
|
||||
<p>{type.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="mailpoet_actions">
|
||||
<a
|
||||
className="button button-primary"
|
||||
onClick={ this.createNewsletter.bind(null, 'standard') }
|
||||
>
|
||||
{MailPoet.I18n.t('create')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li data-type="welcome">
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
{MailPoet.I18n.t('welcomeNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mailpoet_actions">
|
||||
<a
|
||||
className="button button-primary"
|
||||
onClick={ this.setupNewsletter.bind(null, 'welcome') }
|
||||
>
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li data-type="notification">
|
||||
<div className="mailpoet_thumbnail"></div>
|
||||
|
||||
<div className="mailpoet_description">
|
||||
<h3>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h3>
|
||||
<p>
|
||||
{MailPoet.I18n.t('postNotificationsNewsletterTypeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mailpoet_actions">
|
||||
<a
|
||||
className="button button-primary"
|
||||
onClick={ this.setupNewsletter.bind(null, 'notification') }
|
||||
>
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<div className="mailpoet_actions">
|
||||
{type.action}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}, this)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
@ -44,6 +44,7 @@ define(
|
||||
},
|
||||
handleNext: function() {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: _.extend({}, this.state, {
|
||||
|
@ -22,6 +22,7 @@ define(
|
||||
componentDidMount: function() {
|
||||
// No options for this type, create a newsletter upon mounting
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: {
|
||||
|
@ -104,6 +104,7 @@ const WelcomeScheduling = React.createClass({
|
||||
},
|
||||
handleNext: function() {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: {
|
||||
|
@ -1,102 +0,0 @@
|
||||
define(
|
||||
[
|
||||
'underscore',
|
||||
'react',
|
||||
'react-router',
|
||||
'mailpoet',
|
||||
'newsletters/types/welcome/scheduling.jsx',
|
||||
'newsletters/breadcrumb.jsx'
|
||||
],
|
||||
function(
|
||||
_,
|
||||
React,
|
||||
Router,
|
||||
MailPoet,
|
||||
Scheduling,
|
||||
Breadcrumb
|
||||
) {
|
||||
|
||||
var field = {
|
||||
name: 'options',
|
||||
label: 'Event',
|
||||
type: 'reactComponent',
|
||||
component: Scheduling,
|
||||
};
|
||||
|
||||
var availableSegments = window.mailpoet_segments || {},
|
||||
defaultSegment = 1;
|
||||
|
||||
if (_.size(availableSegments) > 0) {
|
||||
defaultSegment = _.first(availableSegments).id;
|
||||
}
|
||||
|
||||
var NewsletterWelcome = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object.isRequired
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
options: {
|
||||
event: 'segment',
|
||||
segment: defaultSegment,
|
||||
role: 'subscriber',
|
||||
afterTimeNumber: 1,
|
||||
afterTimeType: 'immediate',
|
||||
}
|
||||
};
|
||||
},
|
||||
handleValueChange: function(event) {
|
||||
var state = this.state;
|
||||
state[event.target.name] = event.target.value;
|
||||
this.setState(state);
|
||||
},
|
||||
handleNext: function() {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: _.extend({}, this.state, {
|
||||
type: 'welcome',
|
||||
subject: MailPoet.I18n.t('draftNewsletterTitle'),
|
||||
})
|
||||
}).done((response) => {
|
||||
this.showTemplateSelection(response.data.id);
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function(error) { return error.message; }),
|
||||
{ scroll: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
showTemplateSelection: function(newsletterId) {
|
||||
this.context.router.push(`/template/${newsletterId}`);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
|
||||
|
||||
<Scheduling
|
||||
item={this.state}
|
||||
field={field}
|
||||
onValueChange={this.handleValueChange} />
|
||||
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={ this.handleNext }
|
||||
value={MailPoet.I18n.t('next')} />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
return NewsletterWelcome;
|
||||
}
|
||||
);
|
@ -102,22 +102,11 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
'setMessage', this.options.message
|
||||
);
|
||||
},
|
||||
isHTML: function(str) {
|
||||
var a = document.createElement('div');
|
||||
a.innerHTML = str;
|
||||
for (var c = a.childNodes, i = c.length; i--;) {
|
||||
if (c[i].nodeType == 1) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
setMessage: function(message) {
|
||||
message = this.formatMessage(message);
|
||||
|
||||
// if it's not an html message
|
||||
// let's sugar coat the message with a fancy <p>
|
||||
if (this.isHTML(message) === false) {
|
||||
message = '<p>'+message+'</p>';
|
||||
}
|
||||
message = '<p>'+message+'</p>';
|
||||
// set message
|
||||
return this.element.html(message);
|
||||
},
|
||||
@ -153,13 +142,13 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
// set class name
|
||||
switch (this.options.type) {
|
||||
case 'success':
|
||||
this.element.addClass('updated');
|
||||
this.element.addClass('notice notice-success');
|
||||
break;
|
||||
case 'system':
|
||||
this.element.addClass('update-nag');
|
||||
this.element.addClass('notice notice-warning');
|
||||
break;
|
||||
case 'error':
|
||||
this.element.addClass('error');
|
||||
this.element.addClass('notice notice-error');
|
||||
break;
|
||||
}
|
||||
|
||||
@ -199,7 +188,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
|
||||
// single id
|
||||
jQuery('[data-id="' + all + '"]').trigger('close');
|
||||
} else {
|
||||
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
|
||||
jQuery('.mailpoet_notice.notice-success:not([id]), .mailpoet_notice.notice-error:not([id])')
|
||||
.trigger('close');
|
||||
}
|
||||
},
|
||||
|
21
assets/js/src/num.js
Normal file
21
assets/js/src/num.js
Normal file
@ -0,0 +1,21 @@
|
||||
define('num',
|
||||
[
|
||||
'mailpoet'
|
||||
], function(
|
||||
MailPoet
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
MailPoet.Num = {
|
||||
toLocaleFixed: function (num, precision) {
|
||||
precision = precision || 0;
|
||||
var factor = Math.pow(10, precision);
|
||||
return (Math.round(num * factor) / factor)
|
||||
.toLocaleString(
|
||||
undefined,
|
||||
{minimumFractionDigits: precision, maximumFractionDigits: precision}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
});
|
@ -31,7 +31,7 @@ function(
|
||||
});
|
||||
|
||||
form.parsley().on('form:submit', function(parsley) {
|
||||
var data = form.serializeObject() || {};
|
||||
var form_data = form.serializeObject() || {};
|
||||
// check if we're on the same domain
|
||||
if(isSameDomain(MailPoetForm.ajax_url) === false) {
|
||||
// non ajax post request
|
||||
@ -40,10 +40,11 @@ function(
|
||||
// ajax request
|
||||
MailPoet.Ajax.post({
|
||||
url: MailPoetForm.ajax_url,
|
||||
token: data.token,
|
||||
token: form_data.token,
|
||||
api_version: form_data.api_version,
|
||||
endpoint: 'subscribers',
|
||||
action: 'subscribe',
|
||||
data: data
|
||||
data: form_data.data
|
||||
}).fail(function(response) {
|
||||
form.find('.mailpoet_validate_error').html(
|
||||
response.errors.map(function(error) {
|
||||
|
@ -112,6 +112,7 @@ const item_actions = [
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: (item, refresh) => {
|
||||
return MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
@ -153,6 +154,7 @@ const item_actions = [
|
||||
onClick: function(item, refresh) {
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
action: 'synchronize'
|
||||
}).done(function(response) {
|
||||
|
@ -15,6 +15,7 @@ define(
|
||||
|
||||
MailPoet.Router = new (Backbone.Router.extend({
|
||||
routes: {
|
||||
'': 'sendingMethodGroup', // the default tab is currently mta, needs its own method
|
||||
'mta(/:group)': 'sendingMethodGroup',
|
||||
'(:tab)': 'tabs',
|
||||
},
|
||||
@ -32,7 +33,7 @@ define(
|
||||
|
||||
if(group === null) {
|
||||
// show sending methods
|
||||
jQuery('.mailpoet_sending_methods').fadeIn();
|
||||
jQuery('.mailpoet_sending_methods, .mailpoet_sending_methods_help').fadeIn();
|
||||
} else {
|
||||
// toggle SPF (hidden if the sending method is MailPoet)
|
||||
jQuery('#mailpoet_mta_spf')[
|
||||
@ -42,7 +43,7 @@ define(
|
||||
]();
|
||||
|
||||
// hide sending methods
|
||||
jQuery('.mailpoet_sending_methods').hide();
|
||||
jQuery('.mailpoet_sending_methods, .mailpoet_sending_methods_help').hide();
|
||||
|
||||
// display selected sending method's settings
|
||||
jQuery('.mailpoet_sending_method[data-group="'+ group +'"]').show();
|
||||
@ -51,7 +52,7 @@ define(
|
||||
},
|
||||
tabs: function(tab, section) {
|
||||
// set default tab
|
||||
tab = tab || 'basics';
|
||||
tab = tab || 'mta';
|
||||
|
||||
// reset all active tabs
|
||||
jQuery('.nav-tab-wrapper a').removeClass('nav-tab-active');
|
||||
|
@ -60,7 +60,8 @@ define(
|
||||
label: MailPoet.I18n.t('lists'),
|
||||
type: 'selection',
|
||||
placeholder: MailPoet.I18n.t('selectList'),
|
||||
endpoint: "segments",
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
multiple: true,
|
||||
selected: function(subscriber) {
|
||||
if (Array.isArray(subscriber.subscriptions) === false) {
|
||||
|
@ -138,6 +138,7 @@ define(
|
||||
}
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'ImportExport',
|
||||
action: 'processExport',
|
||||
data: JSON.stringify({
|
||||
|
@ -190,6 +190,7 @@ define(
|
||||
mailChimpKeyVerifyButtonElement.click(function () {
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'importExport',
|
||||
action: 'getMailChimpLists',
|
||||
data: {
|
||||
@ -225,6 +226,7 @@ define(
|
||||
}
|
||||
MailPoet.Modal.loading(true);
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'importExport',
|
||||
action: 'getMailChimpSubscribers',
|
||||
data: {
|
||||
@ -321,6 +323,10 @@ define(
|
||||
// is the email in 'mailto:email' format?
|
||||
email = test[1].trim();
|
||||
}
|
||||
// test for valid characters using WP's rule (https://core.trac.wordpress.org/browser/tags/4.7.3/src/wp-includes/formatting.php#L2902)
|
||||
if (!/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.\-@]+$/.test(email) ) {
|
||||
return false;
|
||||
}
|
||||
return email;
|
||||
};
|
||||
|
||||
@ -543,7 +549,7 @@ define(
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('segmentSelectionRequired'), {
|
||||
static: true,
|
||||
scroll: true,
|
||||
id: 'segmentSelection',
|
||||
id: 'notice_segmentSelection',
|
||||
hideClose: true
|
||||
});
|
||||
}
|
||||
@ -572,6 +578,7 @@ define(
|
||||
var segmentDescription = jQuery('#new_segment_description').val().trim();
|
||||
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'ImportExport',
|
||||
action: 'addSegment',
|
||||
data: {
|
||||
@ -633,12 +640,12 @@ define(
|
||||
columnId = 'email';
|
||||
} else if (subscribers.header) {
|
||||
var headerName = subscribers.header[i],
|
||||
header_name_match = mailpoetColumns.map(function (el) {
|
||||
return el.id;
|
||||
headerNameMatch = mailpoetColumns.map(function (el) {
|
||||
return el.name;
|
||||
}).indexOf(headerName);
|
||||
// set column type using header
|
||||
if (header_name_match !== -1) {
|
||||
columnId = headerName;
|
||||
if (headerNameMatch !== -1) {
|
||||
columnId = mailpoetColumns[headerNameMatch].id;
|
||||
}// set column type using header name
|
||||
else if (headerName) {
|
||||
if (/first|first name|given name/i.test(headerName)) {
|
||||
@ -648,11 +655,6 @@ define(
|
||||
} else if (/status/i.test(headerName)) {
|
||||
columnId = 'status';
|
||||
}
|
||||
/*else if (/subscribed|subscription/i.test(headerName)) {
|
||||
columnId = 'confirmed_at';
|
||||
} else if (/ip/i.test(headerName)) {
|
||||
columnId = 'confirmed_ip';
|
||||
}*/
|
||||
}
|
||||
}
|
||||
// make sure the column id has not been previously selected
|
||||
@ -734,6 +736,7 @@ define(
|
||||
|
||||
// save custom field
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'customFields',
|
||||
action: 'save',
|
||||
data: data
|
||||
@ -995,6 +998,7 @@ define(
|
||||
queue.add(function(queue) {
|
||||
queue.pause();
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'ImportExport',
|
||||
action: 'processImport',
|
||||
data: JSON.stringify({
|
||||
@ -1005,8 +1009,8 @@ define(
|
||||
updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false
|
||||
})
|
||||
}).done(function(response) {
|
||||
importResults.created = response.data.created;
|
||||
importResults.updated = response.data.updated;
|
||||
importResults.created += response.data.created;
|
||||
importResults.updated += response.data.updated;
|
||||
importResults.segments = response.data.segments;
|
||||
importResults.added_to_segment_with_welcome_notification = response.data.added_to_segment_with_welcome_notification;
|
||||
queue.run();
|
||||
|
@ -81,6 +81,22 @@ const messages = {
|
||||
).replace('%$1d', count.toLocaleString());
|
||||
}
|
||||
MailPoet.Notice.success(message);
|
||||
},
|
||||
onNoItemsFound: (group) => {
|
||||
if (group === 'bounced' && !mailpoet_premium_active) {
|
||||
return (
|
||||
<div>
|
||||
<p>{MailPoet.I18n.t('bouncedSubscribersHelp')}</p>
|
||||
<p>
|
||||
<a href={ `admin.php?page=mailpoet-premium` } className="button-primary">
|
||||
{MailPoet.I18n.t('bouncedSubscribersPremiumButtonText')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// use default message
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -91,6 +107,7 @@ const bulk_actions = [
|
||||
onSelect: function() {
|
||||
let field = {
|
||||
id: 'move_to_segment',
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
filter: function(segment) {
|
||||
return !!(
|
||||
@ -122,6 +139,7 @@ const bulk_actions = [
|
||||
onSelect: function() {
|
||||
let field = {
|
||||
id: 'add_to_segment',
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
filter: function(segment) {
|
||||
return !!(
|
||||
@ -153,6 +171,7 @@ const bulk_actions = [
|
||||
onSelect: function() {
|
||||
let field = {
|
||||
id: 'remove_from_segment',
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'segments',
|
||||
filter: function(segment) {
|
||||
return !!(
|
||||
@ -255,7 +274,7 @@ const SubscriberList = React.createClass({
|
||||
case 'unsubscribed':
|
||||
status = MailPoet.I18n.t('unsubscribed');
|
||||
break;
|
||||
|
||||
|
||||
case 'bounced':
|
||||
status = MailPoet.I18n.t('bounced');
|
||||
break;
|
||||
|
1
build.sh
1
build.sh
@ -35,6 +35,7 @@ cp -Rf vendor $plugin_name
|
||||
cp -Rf views $plugin_name
|
||||
rm -Rf $plugin_name/assets/css/src
|
||||
rm -Rf $plugin_name/assets/js/src
|
||||
rm -Rf $plugin_name/lang/*.po
|
||||
|
||||
# Remove extra files (docs, examples,...) from 3rd party extensions
|
||||
echo '[BUILD] Removing obsolete files from vendor libraries'
|
||||
|
@ -15,7 +15,8 @@
|
||||
"mtdowling/cron-expression": "^1.1",
|
||||
"nesbot/carbon": "^1.21",
|
||||
"soundasleep/html2text": "^0.3.4",
|
||||
"sabberworm/php-css-parser": "^8.1"
|
||||
"sabberworm/php-css-parser": "^8.1",
|
||||
"symfony/polyfill-xml": "^1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.2.9",
|
||||
@ -36,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-update-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rpd vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility",
|
||||
"post-install-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rpd vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility"
|
||||
"post-update-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rp vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility",
|
||||
"post-install-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rp vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility"
|
||||
}
|
||||
}
|
||||
|
70
composer.lock
generated
70
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "236065951cf739888fa21e0b8653c5fd",
|
||||
"content-hash": "c8c2fdb08b7ab624890703dde640c5d1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cerdic/css-tidy",
|
||||
@ -306,7 +306,7 @@
|
||||
"version": "0.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:mailpoet/html2text.git",
|
||||
"url": "https://github.com/mailpoet/html2text.git",
|
||||
"reference": "a1f77b8f340c8425b746bef1d1040189e89be334"
|
||||
},
|
||||
"dist": {
|
||||
@ -349,7 +349,8 @@
|
||||
"text"
|
||||
],
|
||||
"support": {
|
||||
"email": "support@jevon.org"
|
||||
"email": "support@jevon.org",
|
||||
"source": "https://github.com/mailpoet/html2text/tree/0.3.4"
|
||||
},
|
||||
"time": "2016-06-09T04:56:16+00:00"
|
||||
},
|
||||
@ -466,6 +467,64 @@
|
||||
],
|
||||
"time": "2016-11-14T01:06:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-xml",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-xml.git",
|
||||
"reference": "64b6a864f18ab4fddad49f5025f805f6781dfabd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/64b6a864f18ab4fddad49f5025f805f6781dfabd",
|
||||
"reference": "64b6a864f18ab4fddad49f5025f805f6781dfabd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-xml": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Xml\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for xml's utf8_encode and utf8_decode functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2016-11-14T01:06:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v2.8.18",
|
||||
@ -571,7 +630,7 @@
|
||||
{
|
||||
"name": "Todd Burry",
|
||||
"email": "todd@vanillaforums.com",
|
||||
"role": "Developer"
|
||||
"role": "developer"
|
||||
}
|
||||
],
|
||||
"description": "A jQuery like html dom parser written in php.",
|
||||
@ -2330,6 +2389,7 @@
|
||||
"kint",
|
||||
"php"
|
||||
],
|
||||
"abandoned": "kint-php/kint",
|
||||
"time": "2017-01-15T14:23:43+00:00"
|
||||
},
|
||||
{
|
||||
@ -2896,7 +2956,7 @@
|
||||
"typo3"
|
||||
],
|
||||
"abandoned": true,
|
||||
"time": "2016-05-12 11:58:38"
|
||||
"time": "2016-05-12T11:58:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
|
175
lib/API/API.php
175
lib/API/API.php
@ -1,177 +1,18 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
private $_endpoint;
|
||||
private $_method;
|
||||
private $_token;
|
||||
|
||||
private $_endpoint_namespaces = array();
|
||||
private $_endpoint_class;
|
||||
private $_data = array();
|
||||
|
||||
function __construct() {
|
||||
$this->addEndpointNamespace(__NAMESPACE__ . "\\Endpoints");
|
||||
static function JSON() {
|
||||
return new \MailPoet\API\JSON\API();
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Admin Security token
|
||||
add_action(
|
||||
'admin_head',
|
||||
array($this, 'setToken')
|
||||
);
|
||||
|
||||
// ajax (logged in users)
|
||||
add_action(
|
||||
'wp_ajax_mailpoet',
|
||||
array($this, 'setupAjax')
|
||||
);
|
||||
|
||||
// ajax (logged out users)
|
||||
add_action(
|
||||
'wp_ajax_nopriv_mailpoet',
|
||||
array($this, 'setupAjax')
|
||||
);
|
||||
}
|
||||
|
||||
function setupAjax() {
|
||||
do_action('mailpoet_api_setup', array($this));
|
||||
|
||||
$this->getRequestData($_POST);
|
||||
|
||||
if($this->checkToken() === false) {
|
||||
$error_response = new ErrorResponse(
|
||||
array(
|
||||
Error::UNAUTHORIZED => __('Invalid request', 'mailpoet')
|
||||
),
|
||||
array(),
|
||||
Response::STATUS_UNAUTHORIZED
|
||||
);
|
||||
$error_response->send();
|
||||
static function MP($version) {
|
||||
$api_class = sprintf('%s\MP\%s\API', __NAMESPACE__, $version);
|
||||
if(class_exists($api_class)) {
|
||||
return new $api_class();
|
||||
}
|
||||
|
||||
$response = $this->processRoute();
|
||||
$response->send();
|
||||
throw new \Exception(__('Invalid API version.', 'mailpoet'));
|
||||
}
|
||||
|
||||
function getRequestData($data) {
|
||||
$this->_endpoint = isset($data['endpoint'])
|
||||
? Helpers::underscoreToCamelCase(trim($data['endpoint']))
|
||||
: null;
|
||||
$this->_method = isset($data['method'])
|
||||
? Helpers::underscoreToCamelCase(trim($data['method']))
|
||||
: null;
|
||||
$this->_token = isset($data['token'])
|
||||
? trim($data['token'])
|
||||
: null;
|
||||
|
||||
if(!$this->_endpoint || !$this->_method) {
|
||||
// throw exception bad request
|
||||
$error_response = new ErrorResponse(
|
||||
array(
|
||||
Error::BAD_REQUEST => __('Invalid request', 'mailpoet')
|
||||
),
|
||||
array(),
|
||||
Response::STATUS_BAD_REQUEST
|
||||
);
|
||||
$error_response->send();
|
||||
} else {
|
||||
foreach($this->_endpoint_namespaces as $namespace) {
|
||||
$class_name = $namespace . "\\" . ucfirst($this->_endpoint);
|
||||
if(class_exists($class_name)) {
|
||||
$this->_endpoint_class = $class_name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_data = isset($data['data'])
|
||||
? stripslashes_deep($data['data'])
|
||||
: array();
|
||||
|
||||
// remove reserved keywords from data
|
||||
if(is_array($this->_data) && !empty($this->_data)) {
|
||||
// filter out reserved keywords from data
|
||||
$reserved_keywords = array(
|
||||
'token',
|
||||
'endpoint',
|
||||
'method',
|
||||
'mailpoet_redirect'
|
||||
);
|
||||
$this->_data = array_diff_key(
|
||||
$this->_data,
|
||||
array_flip($reserved_keywords)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processRoute() {
|
||||
try {
|
||||
if(empty($this->_endpoint_class)) {
|
||||
throw new \Exception('Invalid endpoint');
|
||||
}
|
||||
|
||||
$endpoint = new $this->_endpoint_class();
|
||||
|
||||
// check the accessibility of the requested endpoint's action
|
||||
// by default, an endpoint's action is considered "private"
|
||||
$permissions = $endpoint->permissions;
|
||||
if(
|
||||
array_key_exists($this->_method, $permissions) === false
|
||||
||
|
||||
$permissions[$this->_method] !== Access::ALL
|
||||
) {
|
||||
if($this->checkPermissions() === false) {
|
||||
$error_response = new ErrorResponse(
|
||||
array(
|
||||
Error::FORBIDDEN => __(
|
||||
'You do not have the required permissions.',
|
||||
'mailpoet'
|
||||
)
|
||||
),
|
||||
array(),
|
||||
Response::STATUS_FORBIDDEN
|
||||
);
|
||||
return $error_response;
|
||||
}
|
||||
}
|
||||
|
||||
$response = $endpoint->{$this->_method}($this->_data);
|
||||
return $response;
|
||||
} catch(\Exception $e) {
|
||||
$error_response = new ErrorResponse(
|
||||
array($e->getCode() => $e->getMessage())
|
||||
);
|
||||
return $error_response;
|
||||
}
|
||||
}
|
||||
|
||||
function checkPermissions() {
|
||||
return current_user_can('manage_options');
|
||||
}
|
||||
|
||||
function checkToken() {
|
||||
return wp_verify_nonce($this->_token, 'mailpoet_token');
|
||||
}
|
||||
|
||||
function setToken() {
|
||||
$global = '<script type="text/javascript">';
|
||||
$global .= 'var mailpoet_token = "';
|
||||
$global .= Security::generateToken();
|
||||
$global .= '";';
|
||||
$global .= '</script>';
|
||||
echo $global;
|
||||
}
|
||||
|
||||
function addEndpointNamespace($namespace) {
|
||||
$this->_endpoint_namespaces[] = $namespace;
|
||||
}
|
||||
|
||||
function getEndpointNamespaces() {
|
||||
return $this->_endpoint_namespaces;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Services extends APIEndpoint {
|
||||
public $bridge;
|
||||
|
||||
function __construct() {
|
||||
$this->bridge = new Bridge();
|
||||
}
|
||||
|
||||
function verifyMailPoetKey($data = array()) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if(!$key) {
|
||||
return $this->badRequest(array(
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkKey($key);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$success_message = null;
|
||||
if($state == Bridge::MAILPOET_KEY_VALID) {
|
||||
$success_message = __('Your MailPoet API key is valid!', 'mailpoet');
|
||||
} elseif($state == Bridge::MAILPOET_KEY_EXPIRING) {
|
||||
$success_message = sprintf(
|
||||
__('Your MailPoet key expires on %s!', 'mailpoet'),
|
||||
Carbon::createFromTimestamp(strtotime($result['data']['expire_at']))
|
||||
->format('Y-m-d')
|
||||
);
|
||||
}
|
||||
|
||||
if($success_message) {
|
||||
return $this->successResponse(array('message' => $success_message));
|
||||
}
|
||||
|
||||
switch($state) {
|
||||
case Bridge::MAILPOET_KEY_INVALID:
|
||||
$error = __('Your MailPoet key is invalid!', 'mailpoet');
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
$error = sprintf(
|
||||
__('Error validating API key, please try again later (code: %s)', 'mailpoet'),
|
||||
$code
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
|
||||
}
|
||||
}
|
200
lib/API/JSON/API.php
Normal file
200
lib/API/JSON/API.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WP\Hooks;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
private $_request_api_version;
|
||||
private $_request_endpoint;
|
||||
private $_request_method;
|
||||
private $_request_token;
|
||||
private $_request_endpoint_class;
|
||||
private $_request_data = array();
|
||||
private $_endpoint_namespaces = array();
|
||||
private $_available_api_versions = array(
|
||||
'v1'
|
||||
);
|
||||
const CURRENT_VERSION = 'v1';
|
||||
|
||||
function __construct() {
|
||||
foreach($this->_available_api_versions as $available_api_version) {
|
||||
$this->addEndpointNamespace(
|
||||
sprintf('%s\%s', __NAMESPACE__, $available_api_version),
|
||||
$available_api_version
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// admin security token and API version
|
||||
add_action(
|
||||
'admin_head',
|
||||
array($this, 'setTokenAndAPIVersion')
|
||||
);
|
||||
|
||||
// ajax (logged in users)
|
||||
add_action(
|
||||
'wp_ajax_mailpoet',
|
||||
array($this, 'setupAjax')
|
||||
);
|
||||
|
||||
// ajax (logged out users)
|
||||
add_action(
|
||||
'wp_ajax_nopriv_mailpoet',
|
||||
array($this, 'setupAjax')
|
||||
);
|
||||
}
|
||||
|
||||
function setupAjax() {
|
||||
Hooks::doAction('mailpoet_api_setup', array($this));
|
||||
$this->setRequestData($_POST);
|
||||
|
||||
if($this->checkToken() === false) {
|
||||
$error_message = __('Invalid API request.', 'mailpoet');
|
||||
$error_response = $this->createErrorResponse(Error::UNAUTHORIZED, $error_message, Response::STATUS_UNAUTHORIZED);
|
||||
return $error_response->send();
|
||||
}
|
||||
|
||||
$response = $this->processRoute();
|
||||
$response->send();
|
||||
}
|
||||
|
||||
function setRequestData($data) {
|
||||
$this->_request_api_version = !empty($data['api_version']) ? $data['api_version']: false;
|
||||
|
||||
$this->_request_endpoint = isset($data['endpoint'])
|
||||
? Helpers::underscoreToCamelCase(trim($data['endpoint']))
|
||||
: null;
|
||||
|
||||
// JS part of /wp-admin/customize.php does not like a 'method' field in a form widget
|
||||
$method_param_name = isset($data['mailpoet_method']) ? 'mailpoet_method' : 'method';
|
||||
$this->_request_method = isset($data[$method_param_name])
|
||||
? Helpers::underscoreToCamelCase(trim($data[$method_param_name]))
|
||||
: null;
|
||||
|
||||
$this->_request_token = isset($data['token'])
|
||||
? trim($data['token'])
|
||||
: null;
|
||||
|
||||
if(!$this->_request_endpoint || !$this->_request_method || !$this->_request_api_version) {
|
||||
$error_message = __('Invalid API request.', 'mailpoet');
|
||||
$error_response = $this->createErrorResponse(Error::BAD_REQUEST, $error_message, Response::STATUS_BAD_REQUEST);
|
||||
return $error_response;
|
||||
} else if(!empty($this->_endpoint_namespaces[$this->_request_api_version])) {
|
||||
foreach($this->_endpoint_namespaces[$this->_request_api_version] as $namespace) {
|
||||
$endpoint_class = sprintf(
|
||||
'%s\%s',
|
||||
$namespace,
|
||||
ucfirst($this->_request_endpoint)
|
||||
);
|
||||
if(class_exists($endpoint_class)) {
|
||||
$this->_request_endpoint_class = $endpoint_class;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->_request_data = isset($data['data'])
|
||||
? stripslashes_deep($data['data'])
|
||||
: array();
|
||||
|
||||
// remove reserved keywords from data
|
||||
if(is_array($this->_request_data) && !empty($this->_request_data)) {
|
||||
// filter out reserved keywords from data
|
||||
$reserved_keywords = array(
|
||||
'token',
|
||||
'endpoint',
|
||||
'method',
|
||||
'api_version',
|
||||
'mailpoet_method', // alias of 'method'
|
||||
'mailpoet_redirect'
|
||||
);
|
||||
$this->_request_data = array_diff_key(
|
||||
$this->_request_data,
|
||||
array_flip($reserved_keywords)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processRoute() {
|
||||
try {
|
||||
if(empty($this->_request_endpoint_class)) {
|
||||
throw new \Exception(__('Invalid API endpoint.', 'mailpoet'));
|
||||
}
|
||||
|
||||
$endpoint = new $this->_request_endpoint_class();
|
||||
|
||||
// check the accessibility of the requested endpoint's action
|
||||
// by default, an endpoint's action is considered "private"
|
||||
$permissions = $endpoint->permissions;
|
||||
if(array_key_exists($this->_request_method, $permissions) === false ||
|
||||
$permissions[$this->_request_method] !== Access::ALL
|
||||
) {
|
||||
if($this->checkPermissions() === false) {
|
||||
$error_message = __('You do not have the required permissions.', 'mailpoet');
|
||||
$error_response = $this->createErrorResponse(Error::FORBIDDEN, $error_message, Response::STATUS_FORBIDDEN);
|
||||
return $error_response;
|
||||
}
|
||||
}
|
||||
|
||||
$response = $endpoint->{$this->_request_method}($this->_request_data);
|
||||
return $response;
|
||||
} catch(\Exception $e) {
|
||||
$error_message = $e->getMessage();
|
||||
$error_response = $this->createErrorResponse(Error::BAD_REQUEST, $error_message, Response::STATUS_BAD_REQUEST);
|
||||
return $error_response;
|
||||
}
|
||||
}
|
||||
|
||||
function checkPermissions() {
|
||||
return current_user_can(Env::$required_permission);
|
||||
}
|
||||
|
||||
function checkToken() {
|
||||
return wp_verify_nonce($this->_request_token, 'mailpoet_token');
|
||||
}
|
||||
|
||||
function setTokenAndAPIVersion() {
|
||||
$global = '<script type="text/javascript">';
|
||||
$global .= 'var mailpoet_token = "%s";';
|
||||
$global .= 'var mailpoet_api_version = "%s";';
|
||||
$global .= '</script>';
|
||||
echo sprintf(
|
||||
$global,
|
||||
Security::generateToken(),
|
||||
self::CURRENT_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
function addEndpointNamespace($namespace, $version) {
|
||||
if(!empty($this->_endpoint_namespaces[$version][$namespace])) return;
|
||||
$this->_endpoint_namespaces[$version][] = $namespace;
|
||||
}
|
||||
|
||||
function getEndpointNamespaces() {
|
||||
return $this->_endpoint_namespaces;
|
||||
}
|
||||
|
||||
function getRequestedEndpointClass() {
|
||||
return $this->_request_endpoint_class;
|
||||
}
|
||||
|
||||
function getRequestedAPIVersion() {
|
||||
return $this->_request_api_version;
|
||||
}
|
||||
|
||||
function createErrorResponse($error_type, $error_message, $response_status) {
|
||||
$error_response = new ErrorResponse(
|
||||
array(
|
||||
$error_type => $error_message
|
||||
),
|
||||
array(),
|
||||
$response_status
|
||||
);
|
||||
return $error_response;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
namespace MailPoet\API\JSON;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\WP\Posts as WPPosts;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -27,17 +28,19 @@ class AutomatedLatestContent extends APIEndpoint {
|
||||
function getTerms($data = array()) {
|
||||
$taxonomies = (isset($data['taxonomies'])) ? $data['taxonomies'] : array();
|
||||
$search = (isset($data['search'])) ? $data['search'] : '';
|
||||
$limit = (isset($data['limit'])) ? (int)$data['limit'] : 10;
|
||||
$limit = (isset($data['limit'])) ? (int)$data['limit'] : 50;
|
||||
$page = (isset($data['page'])) ? (int)$data['page'] : 1;
|
||||
|
||||
return $this->successResponse(
|
||||
get_terms(
|
||||
$taxonomies,
|
||||
WPPosts::getTerms(
|
||||
array(
|
||||
'taxonomy' => $taxonomies,
|
||||
'hide_empty' => false,
|
||||
'search' => $search,
|
||||
'number' => $limit,
|
||||
'offset' => $limit * ($page - 1)
|
||||
'offset' => $limit * ($page - 1),
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC'
|
||||
)
|
||||
)
|
||||
);
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Models\CustomField;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
|
||||
use MailPoet\Models\Form;
|
||||
use MailPoet\Models\StatisticsForms;
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
|
||||
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
|
||||
use MailPoet\Models\Segment;
|
64
lib/API/JSON/v1/MP2Migrator.php
Normal file
64
lib/API/JSON/v1/MP2Migrator.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class MP2Migrator extends APIEndpoint {
|
||||
|
||||
public function __construct() {
|
||||
$this->MP2Migrator = new \MailPoet\Config\MP2Migrator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Import end point
|
||||
*
|
||||
* @param object $data
|
||||
* @return object
|
||||
*/
|
||||
public function import($data) {
|
||||
try {
|
||||
$process = $this->MP2Migrator->import($data);
|
||||
return $this->successResponse($process);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop import end point
|
||||
*
|
||||
* @param object $data
|
||||
* @return object
|
||||
*/
|
||||
public function stopImport($data) {
|
||||
try {
|
||||
$process = $this->MP2Migrator->stopImport();
|
||||
return $this->successResponse($process);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip import end point
|
||||
*
|
||||
* @param object $data
|
||||
* @return object
|
||||
*/
|
||||
public function skipImport($data) {
|
||||
try {
|
||||
$process = $this->MP2Migrator->skipImport();
|
||||
return $this->successResponse($process);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
|
||||
use MailPoet\Models\NewsletterTemplate;
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Setting;
|
||||
@ -15,6 +15,7 @@ use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
use MailPoet\WP\Hooks;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -29,12 +30,12 @@ class Newsletters extends APIEndpoint {
|
||||
APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet')
|
||||
));
|
||||
} else {
|
||||
return $this->successResponse(
|
||||
$newsletter
|
||||
$newsletter = $newsletter
|
||||
->withSegments()
|
||||
->withOptions()
|
||||
->asArray()
|
||||
);
|
||||
->asArray();
|
||||
$newsletter = Hooks::applyFilters('mailpoet_api_newsletters_get_after', $newsletter);
|
||||
return $this->successResponse($newsletter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +52,8 @@ class Newsletters extends APIEndpoint {
|
||||
unset($data['options']);
|
||||
}
|
||||
|
||||
$data = Hooks::applyFilters('mailpoet_api_newsletters_save_before', $data);
|
||||
|
||||
$newsletter = Newsletter::createOrUpdate($data);
|
||||
$errors = $newsletter->getErrors();
|
||||
|
||||
@ -107,6 +110,8 @@ class Newsletters extends APIEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
Hooks::doAction('mailpoet_api_newsletters_save_after', $newsletter);
|
||||
|
||||
return $this->successResponse($newsletter->asArray());
|
||||
}
|
||||
}
|
||||
@ -204,6 +209,7 @@ class Newsletters extends APIEndpoint {
|
||||
if(!empty($errors)) {
|
||||
return $this->errorResponse($errors);
|
||||
} else {
|
||||
Hooks::doAction('mailpoet_api_newsletters_duplicate_after', $newsletter, $duplicate);
|
||||
return $this->successResponse(
|
||||
Newsletter::findOne($duplicate->id)->asArray(),
|
||||
array('count' => 1)
|
||||
@ -366,7 +372,8 @@ class Newsletters extends APIEndpoint {
|
||||
'filters' => $listing_data['filters'],
|
||||
'groups' => $listing_data['groups'],
|
||||
'mta_log' => Setting::getValue('mta_log'),
|
||||
'mta_method' => Setting::getValue('mta.method')
|
||||
'mta_method' => Setting::getValue('mta.method'),
|
||||
'current_time' => current_time('mysql')
|
||||
));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Listing;
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\Newsletter;
|
125
lib/API/JSON/v1/Services.php
Normal file
125
lib/API/JSON/v1/Services.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Config\Installer;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Util\License\License;
|
||||
use MailPoet\WP\DateTime;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Services extends APIEndpoint {
|
||||
public $bridge;
|
||||
public $date_time;
|
||||
|
||||
function __construct() {
|
||||
$this->bridge = new Bridge();
|
||||
$this->date_time = new DateTime();
|
||||
}
|
||||
|
||||
function checkMSSKey($data = array()) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if(!$key) {
|
||||
return $this->badRequest(array(
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkMSSKey($key);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$success_message = null;
|
||||
if($state == Bridge::MAILPOET_KEY_VALID) {
|
||||
$success_message = __('Your MailPoet Sending Service key has been successfully validated.', 'mailpoet');
|
||||
} elseif($state == Bridge::MAILPOET_KEY_EXPIRING) {
|
||||
$success_message = sprintf(
|
||||
__('Your MailPoet Sending Service key expires on %s!', 'mailpoet'),
|
||||
$this->date_time->formatDate(strtotime($result['data']['expire_at']))
|
||||
);
|
||||
}
|
||||
|
||||
if($success_message) {
|
||||
return $this->successResponse(array('message' => $success_message));
|
||||
}
|
||||
|
||||
switch($state) {
|
||||
case Bridge::MAILPOET_KEY_INVALID:
|
||||
$error = __('Your MailPoet Sending Service key is invalid.', 'mailpoet');
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
$error = sprintf(
|
||||
__('Error validating MailPoet Sending Service key, please try again later (code: %s)', 'mailpoet'),
|
||||
$code
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
|
||||
}
|
||||
|
||||
function checkPremiumKey($data = array()) {
|
||||
$key = isset($data['key']) ? trim($data['key']) : null;
|
||||
|
||||
if(!$key) {
|
||||
return $this->badRequest(array(
|
||||
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->bridge->checkPremiumKey($key);
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
||||
$e->getCode() => $e->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
$state = !empty($result['state']) ? $result['state'] : null;
|
||||
|
||||
$success_message = null;
|
||||
if($state == Bridge::PREMIUM_KEY_VALID) {
|
||||
$success_message = __('Your Premium key has been successfully validated.', 'mailpoet');
|
||||
} elseif($state == Bridge::PREMIUM_KEY_EXPIRING) {
|
||||
$success_message = sprintf(
|
||||
__('Your Premium key expires on %s.', 'mailpoet'),
|
||||
$this->date_time->formatDate(strtotime($result['data']['expire_at']))
|
||||
);
|
||||
}
|
||||
|
||||
if($success_message) {
|
||||
return $this->successResponse(
|
||||
array('message' => $success_message),
|
||||
Installer::getPremiumStatus()
|
||||
);
|
||||
}
|
||||
|
||||
switch($state) {
|
||||
case Bridge::PREMIUM_KEY_INVALID:
|
||||
$error = __('Your Premium key is invalid.', 'mailpoet');
|
||||
break;
|
||||
case Bridge::PREMIUM_KEY_ALREADY_USED:
|
||||
$error = __('Your Premium key is already used on another site.', 'mailpoet');
|
||||
break;
|
||||
default:
|
||||
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
|
||||
$error = sprintf(
|
||||
__('Error validating Premium key, please try again later (code: %s)', 'mailpoet'),
|
||||
$code
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
@ -16,20 +17,15 @@ class Settings extends APIEndpoint {
|
||||
if(empty($settings)) {
|
||||
return $this->badRequest(array(
|
||||
APIError::BAD_REQUEST =>
|
||||
__("You have not specified any settings to be saved.", 'mailpoet')
|
||||
__('You have not specified any settings to be saved.', 'mailpoet')
|
||||
));
|
||||
} else {
|
||||
foreach($settings as $name => $value) {
|
||||
Setting::setValue($name, $value);
|
||||
}
|
||||
if(!empty($settings['mta']['mailpoet_api_key'])
|
||||
&& Bridge::isMPSendingServiceEnabled()
|
||||
) {
|
||||
$bridge = new Bridge();
|
||||
$result = $bridge->checkKey($settings['mta']['mailpoet_api_key']);
|
||||
$bridge->updateSubscriberCount($result);
|
||||
}
|
||||
$bridge = new Bridge();
|
||||
$bridge->onSettingsSave($settings);
|
||||
return $this->successResponse(Setting::getAll());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\Activator;
|
||||
use MailPoet\WP\Hooks;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -11,6 +13,7 @@ class Setup extends APIEndpoint {
|
||||
$activator = new Activator();
|
||||
$activator->deactivate();
|
||||
$activator->activate();
|
||||
Hooks::doAction('mailpoet_setup_reset');
|
||||
return $this->successResponse();
|
||||
} catch(\Exception $e) {
|
||||
return $this->errorResponse(array(
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\Error as APIError;
|
||||
use MailPoet\API\Access as APIAccess;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Error as APIError;
|
||||
use MailPoet\API\JSON\Access as APIAccess;
|
||||
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Models\Subscriber;
|
143
lib/API/MP/v1/API.php
Normal file
143
lib/API/MP/v1/API.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
namespace MailPoet\API\MP\v1;
|
||||
|
||||
use MailPoet\Models\CustomField;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
function getSubscriberFields() {
|
||||
$data = array(
|
||||
array(
|
||||
'id' => 'email',
|
||||
'name' => __('Email', 'mailpoet')
|
||||
),
|
||||
array(
|
||||
'id' => 'first_name',
|
||||
'name' => __('First name', 'mailpoet')
|
||||
),
|
||||
array(
|
||||
'id' => 'last_name',
|
||||
'name' => __('Last name', 'mailpoet')
|
||||
)
|
||||
);
|
||||
|
||||
$custom_fields = CustomField::selectMany(array('id', 'name'))->findMany();
|
||||
foreach($custom_fields as $custom_field) {
|
||||
$data[] = array(
|
||||
'id' => 'cf_' . $custom_field->id,
|
||||
'name' => $custom_field->name
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function subscribeToList($subscriber_id, $segment_id) {
|
||||
return $this->subscribeToLists($subscriber_id, array($segment_id));
|
||||
}
|
||||
|
||||
function subscribeToLists($subscriber_id, array $segments_ids) {
|
||||
$subscriber = Subscriber::findOne($subscriber_id);
|
||||
// throw exception when subscriber does not exist
|
||||
if(!$subscriber) {
|
||||
throw new \Exception(__('This subscriber does not exist.', 'mailpoet'));
|
||||
}
|
||||
|
||||
// throw exception when none of the segments exist
|
||||
$found_segments = Segment::whereIn('id', $segments_ids)->findMany();
|
||||
if(!$found_segments) {
|
||||
throw new \Exception(__('These lists do not exist.', 'mailpoet'));
|
||||
}
|
||||
|
||||
// throw exception when trying to subscribe to a WP Users segment
|
||||
$found_segments_ids = array();
|
||||
foreach($found_segments as $found_segment) {
|
||||
if($found_segment->type === Segment::TYPE_WP_USERS) {
|
||||
throw new \Exception(__(sprintf("Can't subscribe to a WordPress Users list with ID %d.", $found_segment->id), 'mailpoet'));
|
||||
}
|
||||
$found_segments_ids[] = $found_segment->id;
|
||||
}
|
||||
|
||||
// throw an exception when one or more segments do not exist
|
||||
if(count($found_segments_ids) !== count($segments_ids)) {
|
||||
$missing_ids = array_values(array_diff($segments_ids, $found_segments_ids));
|
||||
throw new \Exception(__(sprintf('Lists with ID %s do not exist.', implode(', ', $missing_ids)), 'mailpoet'));
|
||||
}
|
||||
|
||||
SubscriberSegment::subscribeToSegments($subscriber, $found_segments_ids);
|
||||
return $subscriber->withCustomFields()->withSubscriptions()->asArray();
|
||||
}
|
||||
|
||||
function getLists() {
|
||||
return Segment::whereNotEqual('type', Segment::TYPE_WP_USERS)->findArray();
|
||||
}
|
||||
|
||||
function addSubscriber(array $subscriber, $segments = array(), $options = array()) {
|
||||
$send_confirmation_email = (isset($options['send_confirmation_email']) && $options['send_confirmation_email'] === false) ? false : true;
|
||||
$schedule_welcome_email = (isset($options['schedule_welcome_email']) && $options['schedule_welcome_email'] === false) ? false : true;
|
||||
|
||||
// throw exception when subscriber email is missing
|
||||
if(empty($subscriber['email'])) {
|
||||
throw new \Exception(
|
||||
__('Subscriber email address is required.', 'mailpoet')
|
||||
);
|
||||
}
|
||||
|
||||
// throw exception when subscriber already exists
|
||||
if(Subscriber::findOne($subscriber['email'])) {
|
||||
throw new \Exception(
|
||||
__('This subscriber already exists.', 'mailpoet')
|
||||
);
|
||||
}
|
||||
|
||||
// separate data into default and custom fields
|
||||
list($default_fields, $custom_fields) = Subscriber::extractCustomFieldsFromFromObject($subscriber);
|
||||
// if some required default fields are missing, set their values
|
||||
$default_fields = Subscriber::setRequiredFieldsDefaultValues($default_fields);
|
||||
|
||||
// add subscriber
|
||||
$new_subscriber = Subscriber::create();
|
||||
$new_subscriber->hydrate($default_fields);
|
||||
$new_subscriber->save();
|
||||
if($new_subscriber->getErrors() !== false) {
|
||||
throw new \Exception(
|
||||
__(sprintf('Failed to add subscriber: %s', strtolower(implode(', ', $new_subscriber->getErrors()))), 'mailpoet')
|
||||
);
|
||||
}
|
||||
if(!empty($custom_fields)) {
|
||||
$new_subscriber->saveCustomFields($custom_fields);
|
||||
}
|
||||
|
||||
// reload subscriber to get the saved status/created|updated|delete dates/other fields
|
||||
$new_subscriber = Subscriber::findOne($new_subscriber->id);
|
||||
|
||||
// subscribe to segments and optionally: 1) send confirmation email, 2) schedule welcome email(s)
|
||||
if(!empty($segments)) {
|
||||
$this->subscribeToLists($new_subscriber->id, $segments);
|
||||
|
||||
// send confirmation email
|
||||
if($send_confirmation_email && $new_subscriber->status === Subscriber::STATUS_UNCONFIRMED) {
|
||||
$this->_sendConfirmationEmail($new_subscriber);
|
||||
}
|
||||
|
||||
// schedule welcome email(s)
|
||||
if($schedule_welcome_email && $new_subscriber->status === Subscriber::STATUS_SUBSCRIBED) {
|
||||
$this->_scheduleWelcomeNotification($new_subscriber, $segments);
|
||||
}
|
||||
}
|
||||
return $new_subscriber->withCustomFields()->withSubscriptions()->asArray();
|
||||
}
|
||||
|
||||
protected function _sendConfirmationEmail(Subscriber $subscriber) {
|
||||
return $subscriber->sendConfirmationEmail();
|
||||
}
|
||||
|
||||
protected function _scheduleWelcomeNotification(Subscriber $subscriber, array $segments) {
|
||||
return Scheduler::scheduleSubscriberWelcomeNotification($subscriber->id, $segments);
|
||||
}
|
||||
}
|
@ -34,12 +34,23 @@ class Changelog {
|
||||
$version = Setting::getValue('version', null);
|
||||
$redirect_url = null;
|
||||
|
||||
if($version === null) {
|
||||
// new install
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-welcome');
|
||||
} else if($version !== Env::$version) {
|
||||
// update
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-update');
|
||||
$mp2_migrator = new MP2Migrator();
|
||||
if(!in_array($_GET['page'], array('mailpoet-migration', 'mailpoet-settings')) && $mp2_migrator->isMigrationStartedAndNotCompleted()) {
|
||||
// Force the redirection if the migration has started but is not completed
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-migration');
|
||||
} else {
|
||||
if($version === null) {
|
||||
// new install
|
||||
if($mp2_migrator->isMigrationNeeded()) {
|
||||
// Migration from MP2
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-migration');
|
||||
} else {
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-welcome');
|
||||
}
|
||||
} else if($version !== Env::$version) {
|
||||
// update
|
||||
$redirect_url = admin_url('admin.php?page=mailpoet-update');
|
||||
}
|
||||
}
|
||||
|
||||
if($redirect_url !== null) {
|
||||
|
@ -33,6 +33,15 @@ class Database {
|
||||
'TIME_ZONE = "' . Env::$db_timezone_offset . '"',
|
||||
'sql_mode=(SELECT REPLACE(@@sql_mode,"ONLY_FULL_GROUP_BY",""))',
|
||||
);
|
||||
|
||||
if(!empty(Env::$db_charset)) {
|
||||
$character_set = 'NAMES ' . Env::$db_charset;
|
||||
if(!empty(Env::$db_collation)) {
|
||||
$character_set .= ' COLLATE ' . Env::$db_collation;
|
||||
}
|
||||
$driver_options[] = $character_set;
|
||||
}
|
||||
|
||||
$current_options = ORM::for_table("")
|
||||
->raw_query('SELECT @@session.wait_timeout as wait_timeout')
|
||||
->findOne();
|
||||
@ -68,6 +77,7 @@ class Database {
|
||||
$statistics_opens = Env::$db_prefix . 'statistics_opens';
|
||||
$statistics_unsubscribes = Env::$db_prefix . 'statistics_unsubscribes';
|
||||
$statistics_forms = Env::$db_prefix . 'statistics_forms';
|
||||
$mapping_to_external_entities = Env::$db_prefix . 'mapping_to_external_entities';
|
||||
|
||||
define('MP_SETTINGS_TABLE', $settings);
|
||||
define('MP_SEGMENTS_TABLE', $segments);
|
||||
@ -89,6 +99,7 @@ class Database {
|
||||
define('MP_STATISTICS_OPENS_TABLE', $statistics_opens);
|
||||
define('MP_STATISTICS_UNSUBSCRIBES_TABLE', $statistics_unsubscribes);
|
||||
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
|
||||
define('MP_MAPPING_TO_EXTERNAL_ENTITIES_TABLE', $mapping_to_external_entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ class Env {
|
||||
static $db_username;
|
||||
static $db_password;
|
||||
static $db_charset;
|
||||
static $db_collation;
|
||||
static $db_charset_collate;
|
||||
static $db_timezone_offset;
|
||||
static $required_permission = 'manage_options';
|
||||
|
||||
@ -62,12 +64,14 @@ class Env {
|
||||
self::$db_name = DB_NAME;
|
||||
self::$db_username = DB_USER;
|
||||
self::$db_password = DB_PASSWORD;
|
||||
self::$db_charset = $wpdb->get_charset_collate();
|
||||
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
|
||||
self::$db_charset = $wpdb->charset;
|
||||
self::$db_collation = $wpdb->collate;
|
||||
self::$db_charset_collate = $wpdb->get_charset_collate();
|
||||
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port, self::$db_charset);
|
||||
self::$db_timezone_offset = self::getDbTimezoneOffset();
|
||||
}
|
||||
|
||||
private static function dbSourceName($host, $socket, $port) {
|
||||
private static function dbSourceName($host, $socket, $port, $charset) {
|
||||
$source_name = array(
|
||||
(!$socket) ? 'mysql:host=' : 'mysql:unix_socket=',
|
||||
$host,
|
||||
@ -78,6 +82,9 @@ class Env {
|
||||
'dbname=',
|
||||
DB_NAME
|
||||
);
|
||||
if(!empty($charset)) {
|
||||
$source_name[] = ';charset=' . $charset;
|
||||
}
|
||||
return implode('', $source_name);
|
||||
}
|
||||
|
||||
@ -90,4 +97,4 @@ class Env {
|
||||
$mins -= $hrs * 60;
|
||||
return sprintf('%+03d:%02d', $hrs * $sgn, $mins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ class Initializer {
|
||||
try {
|
||||
$this->maybeDbUpdate();
|
||||
$this->setupRenderer();
|
||||
$this->setupInstaller();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
$this->setupAnalytics();
|
||||
@ -85,6 +86,7 @@ class Initializer {
|
||||
$this->setupConflictResolver();
|
||||
|
||||
$this->plugin_initialized = true;
|
||||
do_action('mailpoet_initialized', MAILPOET_VERSION);
|
||||
} catch(\Exception $e) {
|
||||
$this->handleFailedInitialization($e);
|
||||
}
|
||||
@ -97,10 +99,9 @@ class Initializer {
|
||||
}
|
||||
|
||||
try {
|
||||
$this->setupAPI();
|
||||
$this->setupJSONAPI();
|
||||
$this->setupRouter();
|
||||
$this->setupPages();
|
||||
do_action('mailpoet_initialized', MAILPOET_VERSION);
|
||||
} catch(\Exception $e) {
|
||||
$this->handleFailedInitialization($e);
|
||||
}
|
||||
@ -136,6 +137,13 @@ class Initializer {
|
||||
$this->renderer = new Renderer($caching, $debugging);
|
||||
}
|
||||
|
||||
function setupInstaller() {
|
||||
$installer = new Installer(
|
||||
Installer::PREMIUM_PLUGIN_SLUG
|
||||
);
|
||||
$installer->init();
|
||||
}
|
||||
|
||||
function setupLocalizer() {
|
||||
$localizer = new Localizer($this->renderer);
|
||||
$localizer->init();
|
||||
@ -171,9 +179,8 @@ class Initializer {
|
||||
$hooks->init();
|
||||
}
|
||||
|
||||
function setupAPI() {
|
||||
$api = new API\API();
|
||||
$api->init();
|
||||
function setupJSONAPI() {
|
||||
API\API::JSON()->init();
|
||||
}
|
||||
|
||||
function setupRouter() {
|
||||
|
116
lib/Config/Installer.php
Normal file
116
lib/Config/Installer.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Services\Release\API;
|
||||
use MailPoet\Util\License\License;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Installer {
|
||||
const PREMIUM_PLUGIN_SLUG = 'mailpoet-premium';
|
||||
|
||||
private $slug;
|
||||
|
||||
function __construct($slug) {
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
function init() {
|
||||
add_filter('plugins_api', array($this, 'getPluginInformation'), 10, 3);
|
||||
}
|
||||
|
||||
function getPluginInformation($data, $action = '', $args = null) {
|
||||
if($action === 'plugin_information'
|
||||
&& isset($args->slug)
|
||||
&& $args->slug === $this->slug
|
||||
) {
|
||||
$data = $this->retrievePluginInformation();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
static function getPremiumStatus() {
|
||||
$slug = self::PREMIUM_PLUGIN_SLUG;
|
||||
|
||||
$premium_plugin_active = License::getLicense();
|
||||
$premium_plugin_installed = $premium_plugin_active || self::isPluginInstalled($slug);
|
||||
$premium_install_url = $premium_plugin_installed ? '' : self::getPluginInstallationUrl($slug);
|
||||
$premium_activate_url = $premium_plugin_active ? '' : self::getPluginActivationUrl($slug);
|
||||
|
||||
return compact(
|
||||
'premium_plugin_active',
|
||||
'premium_plugin_installed',
|
||||
'premium_install_url',
|
||||
'premium_activate_url'
|
||||
);
|
||||
}
|
||||
|
||||
static function isPluginInstalled($slug) {
|
||||
$installed_plugin = self::getInstalledPlugin($slug);
|
||||
return !empty($installed_plugin);
|
||||
}
|
||||
|
||||
static function getPluginInstallationUrl($slug) {
|
||||
$install_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'install-plugin',
|
||||
'plugin' => $slug,
|
||||
'_wpnonce' => wp_create_nonce('install-plugin_' . $slug),
|
||||
),
|
||||
self_admin_url('update.php')
|
||||
);
|
||||
return $install_url;
|
||||
}
|
||||
|
||||
static function getPluginActivationUrl($slug) {
|
||||
$plugin_file = self::getPluginFile($slug);
|
||||
if(empty($plugin_file)) {
|
||||
return false;
|
||||
}
|
||||
$activate_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'activate',
|
||||
'plugin' => $plugin_file,
|
||||
'_wpnonce' => wp_create_nonce('activate-plugin_' . $plugin_file),
|
||||
),
|
||||
self_admin_url('plugins.php')
|
||||
);
|
||||
return $activate_url;
|
||||
}
|
||||
|
||||
private static function getInstalledPlugin($slug) {
|
||||
$installed_plugin = array();
|
||||
if(is_dir(WP_PLUGIN_DIR . '/' . $slug)) {
|
||||
$installed_plugin = get_plugins('/' . $slug);
|
||||
}
|
||||
return $installed_plugin;
|
||||
}
|
||||
|
||||
private static function getPluginFile($slug) {
|
||||
$plugin_file = false;
|
||||
$installed_plugin = self::getInstalledPlugin($slug);
|
||||
if(!empty($installed_plugin)) {
|
||||
$plugin_file = $slug . '/' . key($installed_plugin);
|
||||
}
|
||||
return $plugin_file;
|
||||
}
|
||||
|
||||
function retrievePluginInformation() {
|
||||
$key = Setting::getValue(Bridge::PREMIUM_KEY_SETTING_NAME);
|
||||
$api = new API($key);
|
||||
$info = $api->getPluginInformation($this->slug);
|
||||
$info = $this->formatInformation($info);
|
||||
return $info;
|
||||
}
|
||||
|
||||
private function formatInformation($info) {
|
||||
// cast sections object to array for WP to understand
|
||||
if(isset($info->sections)) {
|
||||
$info->sections = (array)$info->sections;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
}
|
742
lib/Config/MP2Migrator.php
Normal file
742
lib/Config/MP2Migrator.php
Normal file
@ -0,0 +1,742 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Util\ProgressBar;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\CustomField;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
use MailPoet\Models\SubscriberCustomField;
|
||||
use MailPoet\Models\MappingToExternalEntities;
|
||||
use MailPoet\Config\Activator;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class MP2Migrator {
|
||||
|
||||
const IMPORT_TIMEOUT_IN_SECONDS = 7200; // Timeout = 2 hours
|
||||
const CHUNK_SIZE = 10; // To import the data by batch
|
||||
|
||||
private $log_file;
|
||||
public $log_file_url;
|
||||
public $progressbar;
|
||||
private $segments_mapping = array(); // Mapping between old and new segment IDs
|
||||
private $wp_users_segment;
|
||||
|
||||
public function __construct() {
|
||||
$this->defineMP2Tables();
|
||||
$log_filename = 'mp2migration.log';
|
||||
$this->log_file = Env::$temp_path . '/' . $log_filename;
|
||||
$this->log_file_url = Env::$temp_url . '/' . $log_filename;
|
||||
$this->progressbar = new ProgressBar('mp2migration');
|
||||
}
|
||||
|
||||
private function defineMP2Tables() {
|
||||
global $wpdb;
|
||||
|
||||
if(!defined('MP2_CAMPAIGN_TABLE')) {
|
||||
define('MP2_CAMPAIGN_TABLE', $wpdb->prefix . 'wysija_campaign');
|
||||
}
|
||||
if(!defined('MP2_CUSTOM_FIELD_TABLE')) {
|
||||
define('MP2_CUSTOM_FIELD_TABLE', $wpdb->prefix . 'wysija_custom_field');
|
||||
}
|
||||
if(!defined('MP2_EMAIL_TABLE')) {
|
||||
define('MP2_EMAIL_TABLE', $wpdb->prefix . 'wysija_email');
|
||||
}
|
||||
if(!defined('MP2_FORM_TABLE')) {
|
||||
define('MP2_FORM_TABLE', $wpdb->prefix . 'wysija_form');
|
||||
}
|
||||
if(!defined('MP2_LIST_TABLE')) {
|
||||
define('MP2_LIST_TABLE', $wpdb->prefix . 'wysija_list');
|
||||
}
|
||||
if(!defined('MP2_USER_TABLE')) {
|
||||
define('MP2_USER_TABLE', $wpdb->prefix . 'wysija_user');
|
||||
}
|
||||
if(!defined('MP2_USER_LIST_TABLE')) {
|
||||
define('MP2_USER_LIST_TABLE', $wpdb->prefix . 'wysija_user_list');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the migration is already started but is not completed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isMigrationStartedAndNotCompleted() {
|
||||
return Setting::getValue('mailpoet_migration_started', false) && !Setting::getValue('mailpoet_migration_complete', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the migration is needed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isMigrationNeeded() {
|
||||
if(Setting::getValue('mailpoet_migration_complete')) {
|
||||
return false;
|
||||
} else {
|
||||
return $this->tableExists(MP2_CAMPAIGN_TABLE); // Check if the MailPoet 2 tables exist
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the "Skip import" choice
|
||||
*
|
||||
*/
|
||||
public function skipImport() {
|
||||
Setting::setValue('mailpoet_migration_complete', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a table exists
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @return boolean
|
||||
*/
|
||||
private function tableExists($table) {
|
||||
global $wpdb;
|
||||
|
||||
try {
|
||||
$sql = "SHOW TABLES LIKE '{$table}'";
|
||||
$result = $wpdb->query($sql);
|
||||
return !empty($result);
|
||||
} catch (Exception $e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the migration page
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
if(!Setting::getValue('mailpoet_migration_started', false)) {
|
||||
$this->emptyLog();
|
||||
$this->progressbar->setTotalCount(0);
|
||||
}
|
||||
$this->enqueueScripts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the JavaScript for the admin area.
|
||||
*
|
||||
*/
|
||||
private function enqueueScripts() {
|
||||
wp_enqueue_script('jquery-ui-progressbar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a message in the log file
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
private function log($message) {
|
||||
file_put_contents($this->log_file, "$message\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the data from MailPoet 2
|
||||
*
|
||||
* @return string Result
|
||||
*/
|
||||
public function import() {
|
||||
set_time_limit(self::IMPORT_TIMEOUT_IN_SECONDS);
|
||||
ob_start();
|
||||
$datetime = new \MailPoet\WP\DateTime();
|
||||
$this->log(sprintf('=== ' . __('START IMPORT', 'mailpoet') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
|
||||
Setting::setValue('import_stopped', false); // Reset the stop import action
|
||||
|
||||
if(!Setting::getValue('mailpoet_migration_started', false)) {
|
||||
$this->eraseMP3Data();
|
||||
Setting::setValue('mailpoet_migration_started', true);
|
||||
$this->displayDataToMigrate();
|
||||
}
|
||||
|
||||
$this->importSegments();
|
||||
$this->importCustomFields();
|
||||
$this->importSubscribers();
|
||||
|
||||
if(!$this->importStopped()) {
|
||||
Setting::setValue('mailpoet_migration_complete', true);
|
||||
$this->log(__('IMPORT COMPLETE', 'mailpoet'));
|
||||
}
|
||||
|
||||
$this->log(sprintf('=== ' . __('END IMPORT', 'mailpoet') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
|
||||
$result = ob_get_contents();
|
||||
ob_clean();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the log file
|
||||
*
|
||||
*/
|
||||
private function emptyLog() {
|
||||
file_put_contents($this->log_file, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase all the MailPoet 3 data
|
||||
*
|
||||
*/
|
||||
private function eraseMP3Data() {
|
||||
Activator::deactivate();
|
||||
Activator::activate();
|
||||
|
||||
$this->deleteSegments();
|
||||
$this->resetMigrationCounters();
|
||||
$this->log(__("MailPoet data erased", 'mailpoet'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the migration counters
|
||||
*
|
||||
*/
|
||||
private function resetMigrationCounters() {
|
||||
Setting::setValue('last_imported_user_id', 0);
|
||||
Setting::setValue('last_imported_list_id', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the existing segments except the wp_users segment
|
||||
*
|
||||
*/
|
||||
private function deleteSegments() {
|
||||
global $wpdb;
|
||||
|
||||
$table = MP_SEGMENTS_TABLE;
|
||||
$wpdb->query("DELETE FROM {$table} WHERE type != '" . Segment::TYPE_WP_USERS . "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the import
|
||||
*
|
||||
*/
|
||||
public function stopImport() {
|
||||
Setting::setValue('import_stopped', true);
|
||||
$this->log(__('IMPORT STOPPED BY USER', 'mailpoet'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the import must stop
|
||||
*
|
||||
* @return boolean Import must stop or not
|
||||
*/
|
||||
private function importStopped() {
|
||||
return Setting::getValue('import_stopped', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the number of data to migrate
|
||||
*
|
||||
*/
|
||||
private function displayDataToMigrate() {
|
||||
$data = $this->getDataToMigrateAndResetProgressBar();
|
||||
$this->log($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to migrate
|
||||
*
|
||||
* @return string Data to migrate
|
||||
*/
|
||||
private function getDataToMigrateAndResetProgressBar() {
|
||||
$result = '';
|
||||
$total_count = 0;
|
||||
|
||||
$this->progressbar->setTotalCount(0);
|
||||
|
||||
$result .= __('MailPoet 2 data found:', 'mailpoet') . "\n";
|
||||
|
||||
// User Lists
|
||||
$users_lists_count = \ORM::for_table(MP2_LIST_TABLE)->count();
|
||||
$total_count += $users_lists_count;
|
||||
$result .= sprintf(_n('%d subscribers list', '%d subscribers lists', $users_lists_count, 'mailpoet'), $users_lists_count) . "\n";
|
||||
|
||||
// Users
|
||||
$users_count = \ORM::for_table(MP2_USER_TABLE)->count();
|
||||
$total_count += $users_count;
|
||||
$result .= sprintf(_n('%d subscriber', '%d subscribers', $users_count, 'mailpoet'), $users_count) . "\n";
|
||||
|
||||
// TODO to reactivate during the next phases
|
||||
/*
|
||||
// Emails
|
||||
$emails_count = \ORM::for_table(MP2_EMAIL_TABLE)->count();
|
||||
$total_count += $emails_count;
|
||||
$result .= sprintf(_n('%d newsletter', '%d newsletters', $emails_count, 'mailpoet'), $emails_count) . "\n";
|
||||
|
||||
// Forms
|
||||
$forms_count = \ORM::for_table(MP2_FORM_TABLE)->count();
|
||||
$total_count += $forms_count;
|
||||
$result .= sprintf(_n('%d form', '%d forms', $forms_count, 'mailpoet'), $forms_count) . "\n";
|
||||
*/
|
||||
|
||||
$this->progressbar->setTotalCount($total_count);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the subscribers segments
|
||||
*
|
||||
*/
|
||||
private function importSegments() {
|
||||
$imported_segments_count = 0;
|
||||
if($this->importStopped()) {
|
||||
$this->segments_mapping = $this->getImportedMapping('segments');
|
||||
return;
|
||||
}
|
||||
$this->log(__("Importing segments...", 'mailpoet'));
|
||||
do {
|
||||
if($this->importStopped()) {
|
||||
break;
|
||||
}
|
||||
$lists = $this->getLists(self::CHUNK_SIZE);
|
||||
$lists_count = count($lists);
|
||||
|
||||
if(is_array($lists)) {
|
||||
foreach($lists as $list) {
|
||||
$segment = $this->importSegment($list);
|
||||
if(!empty($segment)) {
|
||||
$imported_segments_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->progressbar->incrementCurrentCount($lists_count);
|
||||
} while(($lists != null) && ($lists_count > 0));
|
||||
|
||||
$this->segments_mapping = $this->getImportedMapping('segments');
|
||||
|
||||
$this->log(sprintf(_n("%d segment imported", "%d segments imported", $imported_segments_count, 'mailpoet'), $imported_segments_count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Mailpoet 2 users lists
|
||||
*
|
||||
* @global object $wpdb
|
||||
* @param int $limit Number of users max
|
||||
* @return array Users Lists
|
||||
*/
|
||||
private function getLists($limit) {
|
||||
global $wpdb;
|
||||
$lists = array();
|
||||
|
||||
$last_id = Setting::getValue('last_imported_list_id', 0);
|
||||
$table = MP2_LIST_TABLE;
|
||||
$sql = "
|
||||
SELECT l.list_id, l.name, l.description, l.is_enabled, l.created_at
|
||||
FROM `$table` l
|
||||
WHERE l.list_id > '$last_id'
|
||||
ORDER BY l.list_id
|
||||
LIMIT $limit
|
||||
";
|
||||
$lists = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a segment
|
||||
*
|
||||
* @param array $list_data List data
|
||||
* @return Segment
|
||||
*/
|
||||
private function importSegment($list_data) {
|
||||
$datetime = new \MailPoet\WP\DateTime();
|
||||
if($list_data['is_enabled']) {
|
||||
$segment = Segment::createOrUpdate(array(
|
||||
'name' => $list_data['name'],
|
||||
'type' => 'default',
|
||||
'description' => !empty($list_data['description']) ? $list_data['description'] : '',
|
||||
'created_at' => $datetime->formatTime($list_data['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
));
|
||||
} else {
|
||||
$segment = Segment::getWPSegment();
|
||||
}
|
||||
if(!empty($segment)) {
|
||||
// Map the segment with its old ID
|
||||
$mapping = new MappingToExternalEntities();
|
||||
$mapping->create(array(
|
||||
'old_id' => $list_data['list_id'],
|
||||
'type' => 'segments',
|
||||
'new_id' => $segment->id,
|
||||
'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
));
|
||||
}
|
||||
Setting::setValue('last_imported_list_id', $list_data['list_id']);
|
||||
return $segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the custom fields
|
||||
*
|
||||
*/
|
||||
private function importCustomFields() {
|
||||
$imported_custom_fields_count = 0;
|
||||
if($this->importStopped()) {
|
||||
return;
|
||||
}
|
||||
$this->log(__("Importing custom fields...", 'mailpoet'));
|
||||
$custom_fields = $this->getCustomFields();
|
||||
|
||||
foreach($custom_fields as $custom_field) {
|
||||
$result = $this->importCustomField($custom_field);
|
||||
if(!empty($result)) {
|
||||
$imported_custom_fields_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log(sprintf(_n("%d custom field imported", "%d custom fields imported", $imported_custom_fields_count, 'mailpoet'), $imported_custom_fields_count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Mailpoet 2 custom fields
|
||||
*
|
||||
* @global object $wpdb
|
||||
* @return array Custom fields
|
||||
*/
|
||||
private function getCustomFields() {
|
||||
global $wpdb;
|
||||
$custom_fields = array();
|
||||
|
||||
$table = MP2_CUSTOM_FIELD_TABLE;
|
||||
$sql = "
|
||||
SELECT cf.id, cf.name, cf.type, cf.required, cf.settings
|
||||
FROM `$table` cf
|
||||
";
|
||||
$custom_fields = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
return $custom_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a custom field
|
||||
*
|
||||
* @param array $custom_field MP2 custom field
|
||||
* @return CustomField
|
||||
*/
|
||||
private function importCustomField($custom_field) {
|
||||
$data = array(
|
||||
'id' => $custom_field['id'],
|
||||
'name' => $custom_field['name'],
|
||||
'type' => $this->mapCustomFieldType($custom_field['type']),
|
||||
'params' => $this->mapCustomFieldParams($custom_field),
|
||||
);
|
||||
$custom_field = new CustomField();
|
||||
$custom_field->createOrUpdate($data);
|
||||
return $custom_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the MailPoet 2 custom field type with the MailPoet custom field type
|
||||
*
|
||||
* @param string $mp2_type MP2 custom field type
|
||||
* @return string MP3 custom field type
|
||||
*/
|
||||
private function mapCustomFieldType($mp2_type) {
|
||||
$type = '';
|
||||
switch($mp2_type) {
|
||||
case 'input':
|
||||
$type = 'text';
|
||||
break;
|
||||
default:
|
||||
$type = $mp2_type;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the MailPoet 2 custom field settings with the MailPoet custom field params
|
||||
*
|
||||
* @param array $custom_field MP2 custom field
|
||||
* @return string serialized MP3 custom field params
|
||||
*/
|
||||
private function mapCustomFieldParams($custom_field) {
|
||||
$params = unserialize($custom_field['settings']);
|
||||
$params['label'] = $custom_field['name'];
|
||||
if(isset($params['validate'])) {
|
||||
$params['validate'] = $this->mapCustomFieldValidateValue($params['validate']);
|
||||
}
|
||||
if(isset($params['date_order'])) { // Convert the date_order field
|
||||
$params['date_format'] = strtoupper($params['date_order']);
|
||||
unset($params['date_order']);
|
||||
}
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the validate value
|
||||
*
|
||||
* @param string $mp2_value MP2 value
|
||||
* @return string MP3 value
|
||||
*/
|
||||
private function mapCustomFieldValidateValue($mp2_value) {
|
||||
$value = '';
|
||||
switch($mp2_value) {
|
||||
case 'onlyLetterSp':
|
||||
case 'onlyLetterNumber':
|
||||
$value = 'alphanum';
|
||||
break;
|
||||
case 'onlyNumberSp':
|
||||
$value = 'number';
|
||||
break;
|
||||
case 'phone':
|
||||
$value = 'phone';
|
||||
break;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the subscribers
|
||||
*
|
||||
*/
|
||||
private function importSubscribers() {
|
||||
$imported_subscribers_count = 0;
|
||||
if($this->importStopped()) {
|
||||
return;
|
||||
}
|
||||
$this->log(__("Importing subscribers...", 'mailpoet'));
|
||||
$this->wp_users_segment = Segment::getWPSegment();
|
||||
do {
|
||||
if($this->importStopped()) {
|
||||
break;
|
||||
}
|
||||
$users = $this->getUsers(self::CHUNK_SIZE);
|
||||
$users_count = count($users);
|
||||
|
||||
if(is_array($users)) {
|
||||
foreach($users as $user) {
|
||||
$subscriber = $this->importSubscriber($user);
|
||||
if(!empty($subscriber)) {
|
||||
$imported_subscribers_count++;
|
||||
$this->importSubscriberSegments($subscriber, $user['user_id']);
|
||||
$this->importSubscriberCustomFields($subscriber, $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->progressbar->incrementCurrentCount($users_count);
|
||||
} while(($users != null) && ($users_count > 0));
|
||||
|
||||
$this->log(sprintf(_n("%d subscriber imported", "%d subscribers imported", $imported_subscribers_count, 'mailpoet'), $imported_subscribers_count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Mailpoet 2 users
|
||||
*
|
||||
* @global object $wpdb
|
||||
* @param int $limit Number of users max
|
||||
* @return array Users
|
||||
*/
|
||||
private function getUsers($limit) {
|
||||
global $wpdb;
|
||||
$users = array();
|
||||
|
||||
$last_id = Setting::getValue('last_imported_user_id', 0);
|
||||
$table = MP2_USER_TABLE;
|
||||
$sql = "
|
||||
SELECT u.*
|
||||
FROM `$table` u
|
||||
WHERE u.user_id > '$last_id'
|
||||
ORDER BY u.user_id
|
||||
LIMIT $limit
|
||||
";
|
||||
$users = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a subscriber
|
||||
*
|
||||
* @param array $user_data User data
|
||||
* @return Subscriber
|
||||
*/
|
||||
private function importSubscriber($user_data) {
|
||||
$datetime = new \MailPoet\WP\DateTime();
|
||||
$subscriber = Subscriber::createOrUpdate(array(
|
||||
'wp_user_id' => !empty($user_data['wpuser_id']) ? $user_data['wpuser_id'] : null,
|
||||
'email' => $user_data['email'],
|
||||
'first_name' => $user_data['firstname'],
|
||||
'last_name' => $user_data['lastname'],
|
||||
'status' => $this->mapUserStatus($user_data['status']),
|
||||
'created_at' => $datetime->formatTime($user_data['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
'subscribed_ip' => !empty($user_data['ip']) ? $user_data['ip'] : null,
|
||||
'confirmed_ip' => !empty($user_data['confirmed_ip']) ? $user_data['confirmed_ip'] : null,
|
||||
'confirmed_at' => !empty($user_data['confirmed_at']) ? $datetime->formatTime($user_data['confirmed_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : null,
|
||||
));
|
||||
Setting::setValue('last_imported_user_id', $user_data['user_id']);
|
||||
if(!empty($subscriber)) {
|
||||
// Map the subscriber with its old ID
|
||||
$mapping = new MappingToExternalEntities();
|
||||
$mapping->create(array(
|
||||
'old_id' => $user_data['user_id'],
|
||||
'type' => 'subscribers',
|
||||
'new_id' => $subscriber->id,
|
||||
'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
));
|
||||
}
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the MailPoet 2 user status with MailPoet 3
|
||||
*
|
||||
* @param int $mp2_user_status MP2 user status
|
||||
* @return string MP3 user status
|
||||
*/
|
||||
private function mapUserStatus($mp2_user_status) {
|
||||
switch($mp2_user_status) {
|
||||
case 1:
|
||||
$status = 'subscribed';
|
||||
break;
|
||||
case -1:
|
||||
$status = 'unsubscribed';
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
$status = 'unconfirmed';
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the segments for a subscriber
|
||||
*
|
||||
* @param Subscriber $subscriber MP3 subscriber
|
||||
* @param int $user_id MP2 user ID
|
||||
*/
|
||||
private function importSubscriberSegments($subscriber, $user_id) {
|
||||
$user_lists = $this->getUserLists($user_id);
|
||||
foreach($user_lists as $user_list) {
|
||||
$this->importSubscriberSegment($subscriber->id, $user_list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lists for a user
|
||||
*
|
||||
* @global object $wpdb
|
||||
* @param int $user_id User ID
|
||||
* @return array Users Lists
|
||||
*/
|
||||
private function getUserLists($user_id) {
|
||||
global $wpdb;
|
||||
$user_lists = array();
|
||||
|
||||
$table = MP2_USER_LIST_TABLE;
|
||||
$sql = "
|
||||
SELECT ul.list_id, ul.sub_date, ul.unsub_date
|
||||
FROM `$table` ul
|
||||
WHERE ul.user_id = '$user_id'
|
||||
";
|
||||
$user_lists = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
return $user_lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a subscriber segment
|
||||
*
|
||||
* @param int $subscriber_id
|
||||
* @param array $user_list
|
||||
* @return SubscriberSegment
|
||||
*/
|
||||
private function importSubscriberSegment($subscriber_id, $user_list) {
|
||||
$subscriber_segment = null;
|
||||
$datetime = new \MailPoet\WP\DateTime();
|
||||
if(isset($this->segments_mapping[$user_list['list_id']])) {
|
||||
$segment_id = $this->segments_mapping[$user_list['list_id']];
|
||||
$status = (($segment_id == $this->wp_users_segment->id) || empty($user_list['unsub_date'])) ? 'subscribed' : 'unsubscribed'; // the users belonging to the wp_users segment are always subscribed
|
||||
$data = array(
|
||||
'subscriber_id' => $subscriber_id,
|
||||
'segment_id' => $segment_id,
|
||||
'status' => $status,
|
||||
'created_at' => $datetime->formatTime($user_list['sub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
|
||||
'updated_at' => !empty($user_list['unsub_date']) ? $datetime->formatTime($user_list['unsub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : null,
|
||||
);
|
||||
$subscriber_segment = new SubscriberSegment();
|
||||
$subscriber_segment->createOrUpdate($data);
|
||||
}
|
||||
return $subscriber_segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the custom fields values for a subscriber
|
||||
*
|
||||
* @param Subscriber $subscriber MP3 subscriber
|
||||
* @param array $user MP2 user
|
||||
*/
|
||||
private function importSubscriberCustomFields($subscriber, $user) {
|
||||
$imported_custom_fields = $this->getImportedCustomFields();
|
||||
foreach($imported_custom_fields as $custom_field) {
|
||||
$custom_field_column = 'cf_' . $custom_field['id'];
|
||||
if(isset($custom_field_column)) {
|
||||
$this->importSubscriberCustomField($subscriber->id, $custom_field, $user[$custom_field_column]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the imported custom fields
|
||||
*
|
||||
* @global object $wpdb
|
||||
* @return array Imported custom fields
|
||||
*
|
||||
*/
|
||||
private function getImportedCustomFields() {
|
||||
global $wpdb;
|
||||
$table = MP_CUSTOM_FIELDS_TABLE;
|
||||
$sql = "
|
||||
SELECT cf.id, cf.name, cf.type
|
||||
FROM `$table` cf
|
||||
";
|
||||
$custom_fields = $wpdb->get_results($sql, ARRAY_A);
|
||||
return $custom_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a subscriber custom field
|
||||
*
|
||||
* @param int $subscriber_id Subscriber ID
|
||||
* @param int $custom_field Custom field
|
||||
* @param string $custom_field_value Custom field value
|
||||
* @return SubscriberCustomField
|
||||
*/
|
||||
private function importSubscriberCustomField($subscriber_id, $custom_field, $custom_field_value) {
|
||||
if($custom_field['type'] == 'date') {
|
||||
$datetime = new \MailPoet\WP\DateTime();
|
||||
$value = $datetime->formatTime($custom_field_value, \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT); // Convert the date field
|
||||
} else {
|
||||
$value = $custom_field_value;
|
||||
}
|
||||
$data = array(
|
||||
'subscriber_id' => $subscriber_id,
|
||||
'custom_field_id' => $custom_field['id'],
|
||||
'value' => isset($value) ? $value : '',
|
||||
);
|
||||
$subscriber_custom_field = new SubscriberCustomField();
|
||||
$subscriber_custom_field->createOrUpdate($data);
|
||||
return $subscriber_custom_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mapping between the MP2 and the imported MP3 IDs
|
||||
*
|
||||
* @param string $model Model (segment,...)
|
||||
* @return array Mapping
|
||||
*/
|
||||
public function getImportedMapping($model) {
|
||||
$mappings = array();
|
||||
$mapping_relations = MappingToExternalEntities::where('type', $model)->findArray();
|
||||
foreach($mapping_relations as $relation) {
|
||||
$mappings[$relation['old_id']] = $relation['new_id'];
|
||||
}
|
||||
return $mappings;
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,7 @@ use MailPoet\Settings\Hosts;
|
||||
use MailPoet\Settings\Pages;
|
||||
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
|
||||
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
|
||||
use MailPoet\Util\License\License;
|
||||
use MailPoet\WP\DateTime;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
use MailPoet\WP\Readme;
|
||||
@ -28,6 +29,7 @@ class Menu {
|
||||
$subscribers_feature = new SubscribersFeature();
|
||||
$this->subscribers_over_limit = $subscribers_feature->check();
|
||||
$this->checkMailPoetAPIKey();
|
||||
$this->checkPremiumKey();
|
||||
}
|
||||
|
||||
function init() {
|
||||
@ -163,6 +165,20 @@ class Menu {
|
||||
'settings'
|
||||
)
|
||||
);
|
||||
|
||||
// Only show this page in menu if the Premium plugin is not activated
|
||||
add_submenu_page(
|
||||
License::getLicense() ? true : $main_page_slug,
|
||||
$this->setPageTitle(__('Premium', 'mailpoet')),
|
||||
__('Premium', 'mailpoet'),
|
||||
Env::$required_permission,
|
||||
'mailpoet-premium',
|
||||
array(
|
||||
$this,
|
||||
'premium'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
'admin.php?page=mailpoet-subscribers',
|
||||
$this->setPageTitle(__('Import', 'mailpoet')),
|
||||
@ -199,6 +215,18 @@ class Menu {
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Migration', 'mailpoet')),
|
||||
'',
|
||||
Env::$required_permission,
|
||||
'mailpoet-migration',
|
||||
array(
|
||||
$this,
|
||||
'migration'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Update', 'mailpoet')),
|
||||
@ -263,6 +291,16 @@ class Menu {
|
||||
$this->displayPage('welcome.html', $data);
|
||||
}
|
||||
|
||||
function migration() {
|
||||
$mp2_migrator = new MP2Migrator();
|
||||
$mp2_migrator->init();
|
||||
$data = array(
|
||||
'log_file_url' => $mp2_migrator->log_file_url,
|
||||
'progress_url' => $mp2_migrator->progressbar->url,
|
||||
);
|
||||
$this->displayPage('mp2migration.html', $data);
|
||||
}
|
||||
|
||||
function update() {
|
||||
global $wp;
|
||||
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
|
||||
@ -297,17 +335,34 @@ class Menu {
|
||||
$this->displayPage('update.html', $data);
|
||||
}
|
||||
|
||||
function premium() {
|
||||
$data = array(
|
||||
'subscriber_count' => Subscriber::getTotalSubscribers(),
|
||||
'sub_menu' => 'mailpoet-newsletters'
|
||||
);
|
||||
|
||||
$this->displayPage('premium.html', $data);
|
||||
}
|
||||
|
||||
|
||||
function settings() {
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
|
||||
$settings = Setting::getAll();
|
||||
$flags = $this->_getFlags();
|
||||
|
||||
// force MSS key check even if the method isn't active
|
||||
$checker = new ServicesChecker();
|
||||
$mp_api_key_valid = $checker->isMailPoetAPIKeyValid(false, true);
|
||||
|
||||
$data = array(
|
||||
'settings' => $settings,
|
||||
'segments' => Segment::getSegmentsWithSubscriberCount(),
|
||||
'cron_trigger' => CronTrigger::getAvailableMethods(),
|
||||
'total_subscribers' => Subscriber::getTotalSubscribers(),
|
||||
'premium_plugin_active' => License::getLicense(),
|
||||
'premium_key_valid' => !empty($this->premium_key_valid),
|
||||
'mss_key_valid' => !empty($mp_api_key_valid),
|
||||
'pages' => Pages::getAll(),
|
||||
'flags' => $flags,
|
||||
'current_user' => wp_get_current_user(),
|
||||
@ -317,6 +372,8 @@ class Menu {
|
||||
)
|
||||
);
|
||||
|
||||
$data = array_merge($data, Installer::getPremiumStatus());
|
||||
|
||||
$this->displayPage('settings.html', $data);
|
||||
}
|
||||
|
||||
@ -369,6 +426,8 @@ class Menu {
|
||||
$data['date_formats'] = Block\Date::getDateFormats();
|
||||
$data['month_names'] = Block\Date::getMonthNames();
|
||||
|
||||
$data['premium_plugin_active'] = License::getLicense();
|
||||
|
||||
$this->displayPage('subscribers/subscribers.html', $data);
|
||||
}
|
||||
|
||||
@ -434,6 +493,7 @@ class Menu {
|
||||
wp_enqueue_media();
|
||||
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
|
||||
wp_enqueue_style('editor', includes_url('css/editor.css'));
|
||||
|
||||
$this->displayPage('newsletter/editor.html', $data);
|
||||
}
|
||||
|
||||
@ -521,7 +581,16 @@ class Menu {
|
||||
$show_notices = isset($_REQUEST['page'])
|
||||
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
|
||||
$checker = $checker ?: new ServicesChecker();
|
||||
$this->mp_api_key_valid = $checker->checkMailPoetAPIKeyValid($show_notices);
|
||||
$this->mp_api_key_valid = $checker->isMailPoetAPIKeyValid($show_notices);
|
||||
}
|
||||
}
|
||||
|
||||
function checkPremiumKey(ServicesChecker $checker = null) {
|
||||
if(self::isOnMailPoetAdminPage()) {
|
||||
$show_notices = isset($_REQUEST['page'])
|
||||
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
|
||||
$checker = $checker ?: new ServicesChecker();
|
||||
$this->premium_key_valid = $checker->isPremiumKeyValid($show_notices);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user