Compare commits
259 Commits
3.0.0-beta
...
3.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
cbedd5ff40 | |||
bfcd6f10fc | |||
79362e9955 | |||
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 | |||
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 |
@ -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.
|
||||
|
11
README.md
11
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
|
||||
|
@ -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
|
||||
|
||||
.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
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -151,8 +151,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 +189,6 @@ define([
|
||||
"click .mailpoet_done_editing": "close",
|
||||
};
|
||||
},
|
||||
templateHelpers: function() {
|
||||
return {
|
||||
model: this.model.toJSON(),
|
||||
};
|
||||
},
|
||||
onRender: function() {
|
||||
var that = this;
|
||||
|
||||
@ -377,7 +372,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 +385,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,
|
||||
|
@ -37,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);
|
||||
|
@ -341,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);
|
||||
|
@ -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 !!(
|
||||
|
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'
|
||||
|
174
lib/API/API.php
174
lib/API/API.php
@ -1,178 +1,18 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WP\Hooks;
|
||||
|
||||
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() {
|
||||
Hooks::doAction('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();
|
||||
}
|
||||
|
||||
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;
|
||||
throw new \Exception(__('Invalid API version.', 'mailpoet'));
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
@ -372,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,19 +17,14 @@ 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,7 @@
|
||||
<?php
|
||||
namespace MailPoet\API\Endpoints;
|
||||
namespace MailPoet\API\JSON\v1;
|
||||
|
||||
use MailPoet\API\Endpoint as APIEndpoint;
|
||||
use MailPoet\API\JSON\Endpoint as APIEndpoint;
|
||||
use MailPoet\Config\Activator;
|
||||
use MailPoet\WP\Hooks;
|
||||
|
@ -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;
|
132
lib/API/MP/v1/API.php
Normal file
132
lib/API/MP/v1/API.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Scheduler::scheduleSubscriberWelcomeNotification($new_subscriber->id, $segments);
|
||||
}
|
||||
}
|
||||
return $new_subscriber->withCustomFields()->withSubscriptions()->asArray();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,7 @@ class Initializer {
|
||||
try {
|
||||
$this->maybeDbUpdate();
|
||||
$this->setupRenderer();
|
||||
$this->setupInstaller();
|
||||
$this->setupLocalizer();
|
||||
$this->setupMenu();
|
||||
$this->setupAnalytics();
|
||||
@ -98,7 +99,7 @@ class Initializer {
|
||||
}
|
||||
|
||||
try {
|
||||
$this->setupAPI();
|
||||
$this->setupJSONAPI();
|
||||
$this->setupRouter();
|
||||
$this->setupPages();
|
||||
} catch(\Exception $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;
|
||||
}
|
||||
}
|
@ -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')),
|
||||
@ -297,17 +313,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 +350,8 @@ class Menu {
|
||||
)
|
||||
);
|
||||
|
||||
$data = array_merge($data, Installer::getPremiumStatus());
|
||||
|
||||
$this->displayPage('settings.html', $data);
|
||||
}
|
||||
|
||||
@ -369,6 +404,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 +471,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 +559,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
class Migrator {
|
||||
function __construct() {
|
||||
$this->prefix = Env::$db_prefix;
|
||||
$this->charset = Env::$db_charset;
|
||||
$this->charset_collate = Env::$db_charset_collate;
|
||||
$this->models = array(
|
||||
'segments',
|
||||
'settings',
|
||||
@ -300,7 +300,8 @@ class Migrator {
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'sent_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY newsletter_id (newsletter_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
@ -316,6 +317,7 @@ class Migrator {
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY newsletter_id (newsletter_id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
@ -329,6 +331,7 @@ class Migrator {
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY newsletter_id (newsletter_id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
@ -342,6 +345,7 @@ class Migrator {
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY newsletter_id (newsletter_id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
@ -365,7 +369,7 @@ class Migrator {
|
||||
$sql = array();
|
||||
$sql[] = "CREATE TABLE " . $table . " (";
|
||||
$sql = array_merge($sql, $attributes);
|
||||
$sql[] = ") " . $this->charset . ";";
|
||||
$sql[] = ") " . $this->charset_collate . ";";
|
||||
|
||||
return implode("\n", $sql);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class Populator {
|
||||
}
|
||||
|
||||
function up() {
|
||||
global $wpdb;
|
||||
$this->convertExistingDataToUTF8();
|
||||
|
||||
array_map(array($this, 'populate'), $this->models);
|
||||
|
||||
@ -308,4 +308,69 @@ class Populator {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* MailPoet versions 3.0.0-beta.32 and older used the default MySQL connection
|
||||
* character set, which usually defaults to latin1, but stored UTF-8 data.
|
||||
* This method converts existing incorrectly stored data that uses the
|
||||
* default character set, into a new character set that is used by WordPress.
|
||||
*/
|
||||
public function convertExistingDataToUTF8() {
|
||||
global $wpdb;
|
||||
|
||||
if(!version_compare(get_option('mailpoet_db_version'), '3.0.0-beta.32', '<=')) {
|
||||
// Data conversion should only be performed only once, when migrating from
|
||||
// older version
|
||||
return;
|
||||
}
|
||||
|
||||
$source_charset = $wpdb->get_var('SELECT @@GLOBAL.character_set_connection');
|
||||
$destination_charset = $wpdb->get_var('SELECT @@SESSION.character_set_connection');
|
||||
|
||||
if($source_charset === $destination_charset) return;
|
||||
// UTF8MB4 is a superset of UTF8, thus a conversion is not necessary
|
||||
if(substr($source_charset, 0, 4) === 'utf8' && $destination_charset === 'utf8mb4') return;
|
||||
|
||||
$tables = array(
|
||||
'segments' => array('name', 'type', 'description'),
|
||||
'settings' => array('name', 'value'),
|
||||
'custom_fields' => array('name', 'type', 'params'),
|
||||
'sending_queues' => array('type', 'newsletter_rendered_body', 'newsletter_rendered_subject', 'subscribers', 'status'),
|
||||
'subscribers' => array('first_name', 'last_name', 'email', 'status', 'subscribed_ip', 'confirmed_ip', 'unconfirmed_data'),
|
||||
'subscriber_segment' => array('status'),
|
||||
'subscriber_custom_field' => array('value'),
|
||||
'newsletters' => array('hash', 'subject', 'type', 'sender_address', 'sender_name', 'status', 'reply_to_address', 'reply_to_name', 'preheader', 'body'),
|
||||
'newsletter_templates' => array('name', 'description', 'body', 'thumbnail'),
|
||||
'newsletter_option_fields' => array('name', 'newsletter_type'),
|
||||
'newsletter_option' => array('value'),
|
||||
'newsletter_links' => array('url', 'hash'),
|
||||
'forms' => array('name', 'body', 'settings', 'styles'),
|
||||
);
|
||||
|
||||
|
||||
foreach($tables as $table => $columns) {
|
||||
$query = "UPDATE `%s` SET %s WHERE %s";
|
||||
$columns_query = array();
|
||||
$where_query = array();
|
||||
foreach($columns as $column) {
|
||||
$columns_query[] = sprintf(
|
||||
'`%1$s` = @%1$s',
|
||||
$column
|
||||
);
|
||||
$where_query[] = sprintf(
|
||||
'char_length(%1$s) = length(@%1$s := convert(binary convert(%1$s using %2$s) using %3$s))',
|
||||
$column,
|
||||
$source_charset,
|
||||
$destination_charset
|
||||
);
|
||||
}
|
||||
$wpdb->query(sprintf(
|
||||
$query,
|
||||
$this->prefix . $table,
|
||||
implode(', ', $columns_query),
|
||||
implode(' AND ', $where_query)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class Renderer {
|
||||
$this->setupDebug();
|
||||
$this->setupTranslations();
|
||||
$this->setupFunctions();
|
||||
$this->setupFilters();
|
||||
$this->setupHandlebars();
|
||||
$this->setupHelpscout();
|
||||
$this->setupGlobalVariables();
|
||||
@ -45,6 +46,10 @@ class Renderer {
|
||||
$this->renderer->addExtension(new Twig\Functions());
|
||||
}
|
||||
|
||||
function setupFilters() {
|
||||
$this->renderer->addExtension(new Twig\Filters());
|
||||
}
|
||||
|
||||
function setupHandlebars() {
|
||||
$this->renderer->addExtension(new Twig\Handlebars());
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ class RequirementsChecker {
|
||||
const TEST_FOLDER_PERMISSIONS = 'TempAndCacheFolderCreation';
|
||||
const TEST_PDO_EXTENSION = 'PDOExtension';
|
||||
const TEST_MBSTRING_EXTENSION = 'MbstringExtension';
|
||||
const TEST_XML_EXTENSION = 'XmlExtension';
|
||||
const TEST_ZIP_EXTENSION = 'ZipExtension';
|
||||
const TEST_VENDOR_SOURCE = 'VendorSource';
|
||||
|
||||
public $display_error_notice;
|
||||
@ -45,6 +47,8 @@ class RequirementsChecker {
|
||||
self::TEST_PDO_EXTENSION,
|
||||
self::TEST_FOLDER_PERMISSIONS,
|
||||
self::TEST_MBSTRING_EXTENSION,
|
||||
self::TEST_XML_EXTENSION,
|
||||
self::TEST_ZIP_EXTENSION,
|
||||
self::TEST_VENDOR_SOURCE
|
||||
);
|
||||
$results = array();
|
||||
@ -61,7 +65,7 @@ class RequirementsChecker {
|
||||
);
|
||||
if(!is_dir($paths['cache_path']) && !wp_mkdir_p($paths['cache_path'])) {
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('This plugin requires write permissions inside the /wp-content/uploads folder. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
__('MailPoet requires write permissions inside the /wp-content/uploads folder. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#folder_permissions'
|
||||
);
|
||||
return $this->processError($error);
|
||||
@ -81,7 +85,7 @@ class RequirementsChecker {
|
||||
function checkPDOExtension() {
|
||||
if(extension_loaded('pdo') && extension_loaded('pdo_mysql')) return true;
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('This plugin requires the PDO_MYSQL PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
__('MailPoet requires a PDO_MYSQL PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_extension'
|
||||
);
|
||||
return $this->processError($error);
|
||||
@ -94,6 +98,24 @@ class RequirementsChecker {
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkXmlExtension() {
|
||||
if(extension_loaded('xml')) return true;
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('MailPoet requires an XML PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_extension'
|
||||
);
|
||||
return $this->processError($error);
|
||||
}
|
||||
|
||||
function checkZipExtension() {
|
||||
if(extension_loaded('zip')) return true;
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('MailPoet requires a ZIP PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
|
||||
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_extension'
|
||||
);
|
||||
return $this->processError($error);
|
||||
}
|
||||
|
||||
function checkVendorSource() {
|
||||
foreach($this->vendor_classes as $dependency) {
|
||||
$dependency_path = $this->getDependencyPath($dependency);
|
||||
|
@ -5,45 +5,94 @@ use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Services\Bridge;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\License\License;
|
||||
use MailPoet\WP\DateTime;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class ServicesChecker {
|
||||
function checkMailPoetAPIKeyValid($display_error_notice = true) {
|
||||
if(!Bridge::isMPSendingServiceEnabled()) {
|
||||
function isMailPoetAPIKeyValid($display_error_notice = true, $force_check = false) {
|
||||
if(!$force_check && !Bridge::isMPSendingServiceEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = Setting::getValue(Bridge::API_KEY_STATE_SETTING_NAME);
|
||||
if(empty($result['state']) || $result['state'] == Bridge::MAILPOET_KEY_VALID) {
|
||||
return true;
|
||||
}
|
||||
$mss_key_specified = Bridge::isMSSKeySpecified();
|
||||
$mss_key = Setting::getValue(Bridge::API_KEY_STATE_SETTING_NAME);
|
||||
|
||||
if($result['state'] == Bridge::MAILPOET_KEY_INVALID) {
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('All sending is currently paused! Your key to send with MailPoet is invalid. [link]Visit MailPoet.com to purchase a key[/link]', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
if(!$mss_key_specified
|
||||
|| empty($mss_key['state'])
|
||||
|| $mss_key['state'] == Bridge::MAILPOET_KEY_INVALID
|
||||
) {
|
||||
if($display_error_notice) {
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('All sending is currently paused! Your key to send with MailPoet is invalid. [link]Visit MailPoet.com to purchase a key[/link]', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
WPNotice::displayError($error);
|
||||
}
|
||||
return false;
|
||||
} elseif($result['state'] == Bridge::MAILPOET_KEY_EXPIRING
|
||||
&& !empty($result['data']['expire_at'])
|
||||
} elseif($mss_key['state'] == Bridge::MAILPOET_KEY_EXPIRING
|
||||
&& !empty($mss_key['data']['expire_at'])
|
||||
) {
|
||||
$date = date('Y-m-d', strtotime($result['data']['expire_at']));
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('Your newsletters are awesome! Don\'t forget to [link]upgrade your MailPoet email plan[/link] by %s to keep sending them to your subscribers.', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
$error = sprintf($error, $date);
|
||||
if($display_error_notice) {
|
||||
$date_time = new DateTime();
|
||||
$date = $date_time->formatDate(strtotime($mss_key['data']['expire_at']));
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('Your newsletters are awesome! Don\'t forget to [link]upgrade your MailPoet email plan[/link] by %s to keep sending them to your subscribers.', 'mailpoet'),
|
||||
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
|
||||
);
|
||||
$error = sprintf($error, $date);
|
||||
WPNotice::displayWarning($error);
|
||||
}
|
||||
return true;
|
||||
} elseif($mss_key['state'] == Bridge::MAILPOET_KEY_VALID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPremiumKeyValid($display_error_notice = true) {
|
||||
$premium_key_specified = Bridge::isPremiumKeySpecified();
|
||||
$premium_plugin_active = License::getLicense();
|
||||
$premium_key = Setting::getValue(Bridge::PREMIUM_KEY_STATE_SETTING_NAME);
|
||||
|
||||
if(!$premium_plugin_active) {
|
||||
$display_error_notice = false;
|
||||
}
|
||||
|
||||
if(!$premium_key_specified
|
||||
|| empty($premium_key['state'])
|
||||
|| $premium_key['state'] === Bridge::PREMIUM_KEY_INVALID
|
||||
|| $premium_key['state'] === Bridge::PREMIUM_KEY_ALREADY_USED
|
||||
) {
|
||||
if($display_error_notice) {
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('Warning! Your License Key is either invalid or expired. [link]Renew your License now[/link] to enjoy automatic updates and Premium support.', 'mailpoet'),
|
||||
'https://account.mailpoet.com'
|
||||
);
|
||||
WPNotice::displayError($error);
|
||||
}
|
||||
return false;
|
||||
} elseif($premium_key['state'] === Bridge::PREMIUM_KEY_EXPIRING
|
||||
&& !empty($premium_key['data']['expire_at'])
|
||||
) {
|
||||
if($display_error_notice) {
|
||||
$date_time = new DateTime();
|
||||
$date = $date_time->formatDate(strtotime($premium_key['data']['expire_at']));
|
||||
$error = Helpers::replaceLinkTags(
|
||||
__('Your License Key is expiring! Don\'t forget to [link]renew your license[/link] by %s to keep enjoying automatic updates and Premium support.', 'mailpoet'),
|
||||
'https://account.mailpoet.com'
|
||||
);
|
||||
$error = sprintf($error, $date);
|
||||
WPNotice::displayWarning($error);
|
||||
}
|
||||
return true;
|
||||
} elseif($premium_key['state'] === Bridge::PREMIUM_KEY_VALID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ namespace MailPoet\Cron;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
@ -50,6 +51,7 @@ class Daemon {
|
||||
$this->executeScheduleWorker();
|
||||
$this->executeQueueWorker();
|
||||
$this->executeSendingServiceKeyCheckWorker();
|
||||
$this->executePremiumKeyCheckWorker();
|
||||
$this->executeBounceWorker();
|
||||
} catch(\Exception $e) {
|
||||
// continue processing, no need to handle errors
|
||||
@ -87,6 +89,11 @@ class Daemon {
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executePremiumKeyCheckWorker() {
|
||||
$worker = new PremiumKeyCheckWorker($this->timer);
|
||||
return $worker->process();
|
||||
}
|
||||
|
||||
function executeBounceWorker() {
|
||||
$bounce = new BounceWorker($this->timer);
|
||||
return $bounce->process();
|
||||
|
@ -5,7 +5,8 @@ use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck as PremiumKeyCheckWorker;
|
||||
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
@ -32,12 +33,17 @@ class WordPress {
|
||||
// sending service key check
|
||||
$sskeycheck_due_queues = SendingServiceKeyCheckWorker::getAllDueQueues();
|
||||
$sskeycheck_future_queues = SendingServiceKeyCheckWorker::getFutureQueues();
|
||||
// premium key check
|
||||
$premium_key_specified = Bridge::isPremiumKeySpecified();
|
||||
$premium_keycheck_due_queues = PremiumKeyCheckWorker::getAllDueQueues();
|
||||
$premium_keycheck_future_queues = PremiumKeyCheckWorker::getFutureQueues();
|
||||
// check requirements for each worker
|
||||
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached && !$sending_is_paused);
|
||||
$bounce_sync_active = ($mp_sending_enabled && ($bounce_due_queues || !$bounce_future_queues));
|
||||
$sending_service_key_check_active = ($mp_sending_enabled && ($sskeycheck_due_queues || !$sskeycheck_future_queues));
|
||||
$premium_key_check_active = ($premium_key_specified && ($premium_keycheck_due_queues || !$premium_keycheck_future_queues));
|
||||
|
||||
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active);
|
||||
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active || $premium_key_check_active);
|
||||
}
|
||||
|
||||
static function cleanup() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
@ -12,7 +11,7 @@ use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Bounce {
|
||||
class Bounce extends SimpleWorker {
|
||||
const TASK_TYPE = 'bounce';
|
||||
const BATCH_SIZE = 100;
|
||||
|
||||
@ -20,66 +19,20 @@ class Bounce {
|
||||
const BOUNCED_SOFT = 'soft';
|
||||
const NOT_BOUNCED = null;
|
||||
|
||||
public $timer;
|
||||
public $api;
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
}
|
||||
|
||||
function initApi() {
|
||||
function init() {
|
||||
if(!$this->api) {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
$this->api = new API($mailer_config['mailpoet_api_key']);
|
||||
}
|
||||
}
|
||||
|
||||
function process() {
|
||||
if(!Bridge::isMPSendingServiceEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->initApi();
|
||||
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
|
||||
if(!$scheduled_queues && !$running_queues) {
|
||||
self::scheduleBounceSync();
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($scheduled_queues as $i => $queue) {
|
||||
$this->prepareBounceQueue($queue);
|
||||
}
|
||||
foreach($running_queues as $i => $queue) {
|
||||
$this->processBounceQueue($queue);
|
||||
}
|
||||
|
||||
return true;
|
||||
function checkProcessingRequirements() {
|
||||
return Bridge::isMPSendingServiceEnabled();
|
||||
}
|
||||
|
||||
static function scheduleBounceSync() {
|
||||
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
if($already_scheduled) {
|
||||
return false;
|
||||
}
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = self::TASK_TYPE;
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->priority = SendingQueue::PRIORITY_LOW;
|
||||
$queue->scheduled_at = self::getNextRunDate();
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function prepareBounceQueue(SendingQueue $queue) {
|
||||
function prepareQueue(SendingQueue $queue) {
|
||||
$subscribers = Subscriber::select('id')
|
||||
->whereNull('deleted_at')
|
||||
->whereIn('status', array(
|
||||
@ -101,16 +54,11 @@ class Bounce {
|
||||
)
|
||||
);
|
||||
$queue->count_total = $queue->count_to_process = count($subscribers);
|
||||
$queue->status = null;
|
||||
$queue->save();
|
||||
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
|
||||
return true;
|
||||
return parent::prepareQueue($queue);
|
||||
}
|
||||
|
||||
function processBounceQueue(SendingQueue $queue) {
|
||||
function processQueue(SendingQueue $queue) {
|
||||
$queue->subscribers = $queue->getSubscribers();
|
||||
if(empty($queue->subscribers['to_process'])) {
|
||||
$queue->delete();
|
||||
@ -157,39 +105,4 @@ class Bounce {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function getNextRunDate() {
|
||||
$date = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
// Random day of the next week
|
||||
$date->setISODate($date->format('o'), $date->format('W') + 1, mt_rand(1, 7));
|
||||
$date->startOfDay();
|
||||
return $date;
|
||||
}
|
||||
|
||||
static function getScheduledQueues($future = false) {
|
||||
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getRunningQueues() {
|
||||
return SendingQueue::where('type', self::TASK_TYPE)
|
||||
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getAllDueQueues() {
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
return array_merge((array)$scheduled_queues, (array)$running_queues);
|
||||
}
|
||||
|
||||
static function getFutureQueues() {
|
||||
return self::getScheduledQueues(true);
|
||||
}
|
||||
}
|
||||
|
38
lib/Cron/Workers/KeyCheck/KeyCheckWorker.php
Normal file
38
lib/Cron/Workers/KeyCheck/KeyCheckWorker.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\KeyCheck;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\SimpleWorker;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
abstract class KeyCheckWorker extends SimpleWorker {
|
||||
const UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT = 60;
|
||||
|
||||
public $bridge;
|
||||
|
||||
function init() {
|
||||
if(!$this->bridge) {
|
||||
$this->bridge = new Bridge();
|
||||
}
|
||||
}
|
||||
|
||||
function processQueueStrategy(SendingQueue $queue) {
|
||||
try {
|
||||
$result = $this->checkKey();
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(empty($result['code']) || $result['code'] == Bridge::CHECK_ERROR_UNAVAILABLE) {
|
||||
$this->reschedule($queue, self::UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract function checkKey();
|
||||
}
|
21
lib/Cron/Workers/KeyCheck/PremiumKeyCheck.php
Normal file
21
lib/Cron/Workers/KeyCheck/PremiumKeyCheck.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\KeyCheck;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class PremiumKeyCheck extends KeyCheckWorker {
|
||||
const TASK_TYPE = 'premium_key_check';
|
||||
|
||||
function checkProcessingRequirements() {
|
||||
return Bridge::isPremiumKeySpecified();
|
||||
}
|
||||
|
||||
function checkKey() {
|
||||
$premium_key = Setting::getValue(Bridge::PREMIUM_KEY_SETTING_NAME);
|
||||
$result = $this->bridge->checkPremiumKey($premium_key);
|
||||
return $result;
|
||||
}
|
||||
}
|
22
lib/Cron/Workers/KeyCheck/SendingServiceKeyCheck.php
Normal file
22
lib/Cron/Workers/KeyCheck/SendingServiceKeyCheck.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\KeyCheck;
|
||||
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class SendingServiceKeyCheck extends KeyCheckWorker {
|
||||
const TASK_TYPE = 'sending_service_key_check';
|
||||
|
||||
function checkProcessingRequirements() {
|
||||
return Bridge::isMPSendingServiceEnabled();
|
||||
}
|
||||
|
||||
function checkKey() {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
$result = $this->bridge->checkMSSKey($mailer_config['mailpoet_api_key']);
|
||||
$this->bridge->updateSubscriberCount($result);
|
||||
return $result;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user