Compare commits

..

47 Commits
0.0.1 ... 0.0.3

Author SHA1 Message Date
0199e2c7e1 Version bump: 0.0.3 2015-11-06 21:49:28 +01:00
d1f407bf19 Merge branch 'master' of github.com:mailpoet/mailpoet 2015-11-06 21:47:49 +01:00
f18d2842b9 Version bump: 0.0.3. 2015-11-06 21:47:17 +01:00
f640cbb307 Merge pull request #203 from mailpoet/form_editor
Form editor
2015-11-06 21:39:03 +01:00
b20d92c9b1 fixed unit tests and added form model unit test 2015-11-06 18:43:56 +01:00
795485d42a fixed sortable segments in form editor 2015-11-06 16:09:56 +01:00
dfadda2d12 converted form renderer validation to Parsley 2015-11-06 16:09:56 +01:00
31305a04c0 form validation with Parsley 2015-11-06 16:09:56 +01:00
cfdb886e88 Date widget
- fixed date widget
- fixed default styles for radio inputs in form rendering
2015-11-06 16:09:09 +01:00
1ce4b16327 custom fields (create and update) 2015-11-06 16:09:09 +01:00
b12f7f29de Custom fields
- added listing of custom fields in form editor
- ability to delete a custom field
- mades changes to the form editor in order to accomodate for the new custom fields system
- fix form editor bugs
- cleanup
2015-11-06 16:09:09 +01:00
5473f94e24 List selection & subscribe
- fixed list selection widget (form editor & rendered form)
- ajax subscription works (minus sending the confirmation email)
- bug fixes / polishing / refactoring / cleanup
2015-11-06 16:09:09 +01:00
a31dce6226 fixed list selection widget + started form submission 2015-11-06 16:09:09 +01:00
d996b78561 fixed form_editor.js being ignored by git 2015-11-06 16:09:09 +01:00
c2e7513381 Form editor
- new form with default data
- load/save in form editor
- widgets -> settings form
- widgets -> shortcode for subscribers count
- widgets -> form rendering
- added useful filters to Subscribers (for status related search)
- refactor & cleanup
2015-11-06 16:09:09 +01:00
541696863e Form editor
- new/edit in forms listing goes to editor
- fixed editor dependencies (via Webpack)
- updated forms table schema
- saving/loading a form works
2015-11-06 16:09:09 +01:00
6c8d2be18c fix for selection field jsx 2015-11-06 16:08:16 +01:00
907fe585de add Form renderer and fixed Newsletter saving issue 2015-11-06 16:08:16 +01:00
e24f8c9653 forms listing complete 2015-11-06 16:08:16 +01:00
5df0475b1a Merge pull request #204 from mailpoet/newsletter_templates
Newsletter template import/export
2015-11-06 12:23:27 +01:00
cf154455e3 Add error reporting for newsletter template export fields 2015-11-05 19:11:32 +02:00
dcfe6357cf Switch to FileSaver lib for downloading Blob files, add Blob polyfill 2015-11-05 17:17:54 +02:00
983df216f3 Add basic template export 2015-11-04 18:02:55 +02:00
f750d2359f Base for template import 2015-11-03 16:33:13 +02:00
d85f51e9fc Update composer lockfile. 2015-10-30 22:26:40 +01:00
40a62687cf Update Composer lockfile. 2015-10-30 22:13:00 +01:00
136e09e9fb Merge pull request #202 from mailpoet/newsletter_width
Increase newsletter width to 660px from 600px
2015-10-30 12:37:16 +01:00
f509dc0d7e Increase newsletter width to 660px from 600px 2015-10-30 13:25:45 +02:00
c100130f39 Merge pull request #201 from mailpoet/forms
Listing/Model/Router refactoring + Forms
2015-10-30 11:45:34 +01:00
9922ecd93c Merge pull request #200 from mailpoet/notification_type
Add notification email type
2015-10-30 11:40:50 +01:00
a4cf2f9c76 Major refactoring of listing/router/model relation
- updated Subscribers listing
- udpated Segments listing
- added Forms router
2015-10-29 15:30:24 +01:00
576fbf2085 Add notification email type 2015-10-29 15:59:09 +02:00
5c63971314 Merge pull request #198 from mailpoet/editor_select2
Update select2 version to 4.0 for newsletter editor
2015-10-28 15:30:08 +01:00
7418923bbc Remove glow and margins from select2 input 2015-10-28 16:01:23 +02:00
a8f8134f67 Adapt select2 integration code to select2 4.0 2015-10-28 16:01:23 +02:00
103da61d45 basic listing files 2015-10-28 13:19:48 +01:00
01e6a5e6b2 forms table 2015-10-28 13:19:48 +01:00
f5ccf3b38a Merge pull request #195 from mailpoet/subscribers_round_1
Subscriber & Segment listings
2015-10-28 12:24:19 +01:00
c8929351ba Merge pull request #196 from mailpoet/openssl
use of Crypt\RSA library in order to generate dkim keys
2015-10-28 12:23:33 +01:00
6ca536e9ca use of Crypt\RSA library in order to generate dkim keys 2015-10-27 16:41:28 +01:00
89b04e8691 Subscriber & Segment listings
- fixed filters
- added load/save state from url
- added goBack on forms in order to get back listing states
wx# Please enter the commit message for your changes. Lines starting
2015-10-27 16:24:00 +01:00
588b441fb1 Merge pull request #194 from mailpoet/lists_round_1
Lists round 1
2015-10-27 10:30:45 +01:00
13dc3577f1 lotta fixes for filtering + listing 2015-10-26 18:23:32 +01:00
505b979ac5 Segment actions
- added duplicate
- added view subscribers
2015-10-23 17:34:35 +02:00
3b4c5c83e1 added segment stats in listing 2015-10-22 20:40:46 +02:00
056e79eeac bugfix 2015-10-22 19:24:58 +02:00
4bde705f04 listing modifications + added description to segments 2015-10-22 19:19:40 +02:00
110 changed files with 6176 additions and 2933 deletions

View File

@ -91,7 +91,7 @@ class RoboFile extends \Robo\Tasks {
function testUnit($file = null) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit '.(($file) ? $file : ''));
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
}
function testJavascript() {
@ -113,7 +113,6 @@ class RoboFile extends \Robo\Tasks {
function testFailed() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->startPhantomJS();
$this->_exec('vendor/bin/codecept run -g failed');
}

View File

@ -5,7 +5,7 @@
@require 'common'
@require 'modal'
@require 'notice'
@require 'validation_engine'
@require 'parsley'
@require 'form_editor'
@require 'listing'

View File

@ -19,8 +19,10 @@ a:focus
// select 2
.select2-container
// textareas
textarea.regular-text
width: 25em !important
@media screen and (max-width: 782px)
.select2-container
width: 100% !important
width: 100% !important

View File

@ -1,3 +1,6 @@
@require 'codemirror/lib/codemirror.css'
@require 'codemirror/theme/neo.css'
icons = '../img/form_editor_icons.png'
handle_icon = '../img/handle.png'

View File

@ -1,4 +1,4 @@
.mailpoet_listing_loading tbody tr,
.mailpoet_listing_loading tbody tr
.mailpoet_form_loading tbody tr
opacity: 0.2
@ -8,6 +8,20 @@
.mailpoet_select_all td
text-align: center
table.widefat thead .check-column,
table.widefat tfoot .check-column
padding: 10px 0 0 3px
.mailpoet_listing_table
th span
white-space: nowrap
thead .check-column
tfoot .check-column
padding: 10px 0 0 3px
thead th.column-primary
tfoot th.column-primary
width: 25em
// responsive
@media screen and (max-width: 782px)
thead th.column-primary
tfoot th.column-primary
width: 100%

View File

@ -45,7 +45,8 @@
&::before
content: '\f140'
.mailpoet_save_as_template_container
.mailpoet_save_as_template_container,
.mailpoet_export_template_container
border-radius(3px)
float: left
clear: both
@ -55,7 +56,8 @@
background-color: $white-color
border: 1px solid $structure-border-color
.mailpoet_save_as_template_title
.mailpoet_save_as_template_title,
.mailpoet_export_template_title
font-size: 1.1em
.mailpoet_editor_last_saved

View File

@ -1,3 +1,8 @@
$column-margin = 20px
$one-column-width = $newsletter-width - (2 * $column-margin)
$two-column-width = ($newsletter-width / 2) - (2 * $column-margin)
$three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
.mailpoet_container
width: 100%
min-height: 15px
@ -44,7 +49,7 @@
.mailpoet_container_horizontal > .mailpoet_container_block
margin-bottom: 0
width: 20px + 560px + 20px
width: $column-margin + $one-column-width + $column-margin
// More than one column
& > .mailpoet_container_block > .mailpoet_container > .mailpoet_container_block > .mailpoet_container_horizontal
@ -57,14 +62,14 @@
& > .mailpoet_block:first-child:nth-last-child(2) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 260px + 20px + 20px
width: $column-margin + $two-column-width + $column-margin
// Three columns
& > .mailpoet_block:first-child:nth-last-child(3)
& > .mailpoet_block:first-child:nth-last-child(3) ~ .mailpoet_block
//padding-left: 20px
//padding-right: 20px
width: 160px + 20px + 20px
width: $column-margin + $three-column-width + $column-margin
.mailpoet_container_empty
text-align: center

View File

@ -1,5 +1,5 @@
/* Fix select2 z-index to work with MailPoet.Modal */
.select2-drop
.select2-dropdown
z-index: 101000
/* Remove input field styles from select2 type input */
@ -7,6 +7,19 @@
border: none
padding: 0
/* Fix select2 input glow and margins that wordpress may insert */
.select2 input,
.select2 input:focus
border-color: none
box-shadow: none
margin: 0
padding: 0
/* Fix width overrides for select2 */
.mailpoet_editor_settings .select2-container
width: 100% !important
/* Fix inline TinyMCE toolbar to have minimal width instead of being close to 100% of the screen */
div.mce-toolbar-grp.mce-container
position: absolute

View File

@ -23,4 +23,4 @@ $warning-alternate-text-color = #f4c6c8
$error-text-color = #d54e21
// Dimensions
$newsletter-width = 600px
$newsletter-width = 660px

View File

@ -0,0 +1,27 @@
input.parsley-success,
select.parsley-success,
textarea.parsley-success
color #468847
background-color #DFF0D8
border 1px solid #D6E9C6
input.parsley-error,
select.parsley-error,
textarea.parsley-error
color #B94A48
background-color #F2DEDE
border 1px solid #EED3D7
.parsley-errors-list
margin 2px 0 3px
padding 0
list-style-type none
font-size 0.9em
line-height 0.9em
opacity 0
transition all .3s ease-in
-o-transition all .3s ease-in
-moz-transition all .3s ease-in
-webkit-transition all .3s ease-in
&.filled
opacity 1

View File

@ -0,0 +1,3 @@
@import 'nib'
@require 'parsley'

View File

@ -1,141 +0,0 @@
lesscss-percentage(n)
(n * 100)%
popupBg = rgb(0, 87, 154, 1)
popupTextColor = rgb(255, 255, 255, 1)
borderColor = rgb(255, 255, 255, 1)
borderWidth = 1px
popupFontSize = 12px
popupRadius = 0
popupShadowWidth = 2px
popupShadowColor = rgb(51, 51, 51, 1)
/* Z-INDEX */
.formError
z-index 990
.formError .formErrorContent
z-index 991
.formError .formErrorArrow
z-index 996
.ui-dialog .formError
z-index 5000
.ui-dialog .formError .formErrorContent
z-index 5001
.ui-dialog .formError .formErrorArrow
z-index 5006
.inputContainer
position relative
float left
.formError
position absolute
top 300px
left 300px
display block
cursor pointer
text-align left
.formError.inline
position relative
top 0
left 0
display inline-block
.ajaxSubmit
padding 20px
background #55ea55
border 1px solid #999
display none
.formError .formErrorContent
width 100%
background popupBg
position relative
color popupTextColor
min-width 120px
font-size popupFontSize
border borderWidth solid borderColor
box-shadow 0 0 popupShadowWidth popupShadowColor
-moz-box-shadow 0 0 popupShadowWidth popupShadowColor
-webkit-box-shadow 0 0 popupShadowWidth popupShadowColor
-o-box-shadow 0 0 popupShadowWidth popupShadowColor
padding 4px 10px 4px 10px
border-radius popupRadius
-moz-border-radius popupRadius
-webkit-border-radius popupRadius
-o-border-radius popupRadius
.formError.inline .formErrorContent
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
border none
border-radius 0
-moz-border-radius 0
-webkit-border-radius 0
-o-border-radius 0
.greenPopup .formErrorContent
background #33be40
.blackPopup .formErrorContent
background #393939
color #FFF
.formError .formErrorArrow
width 15px
margin -2px 0 0 13px
position relative
body[dir='rtl'] .formError .formErrorArrow, body.rtl .formError .formErrorArrow
margin -2px 13px 0 0
.formError .formErrorArrowBottom
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
margin 0px 0 0 12px
top 2px
.formError .formErrorArrow div
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-moz-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-webkit-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
-o-box-shadow 0 ceil((popupShadowWidth / 3)) ceil((popupShadowWidth / 2)) lighten(popupShadowColor, 10%)
font-size 0px
height 1px
background popupBg
margin 0 auto
line-height 0
font-size 0
display block
.formError .formErrorArrowBottom div
box-shadow none
-moz-box-shadow none
-webkit-box-shadow none
-o-box-shadow none
.greenPopup .formErrorArrow div
background #33be40
.blackPopup .formErrorArrow div
background #393939
color #FFF
.formError .formErrorArrow .line10
width 13px
border none
.formError .formErrorArrow .line9
width 11px
border none
.formError .formErrorArrow .line8
width 11px
.formError .formErrorArrow .line7
width 9px
.formError .formErrorArrow .line6
width 7px
.formError .formErrorArrow .line5
width 5px
.formError .formErrorArrow .line4
width 3px
.formError .formErrorArrow .line3
width ceil((borderWidth / 2))
border-left borderWidth solid borderColor
border-right borderWidth solid borderColor
border-bottom 0 solid borderColor
.formError .formErrorArrow .line2
width 3px
border none
background borderColor
.formError .formErrorArrow .line1
width 1px
border none
background borderColor

4
assets/js/lib/prototype.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
assets/js/lib/scriptaculous.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,12 @@
define([
'react',
'react-dom',
'jquery',
'select2'
],
function(
React,
ReactDOM,
jQuery
) {
var Selection = React.createClass({
@ -16,36 +18,38 @@ function(
},
componentDidMount: function() {
this.loadCachedItems();
this.setupSelect2();
},
componentDidUpdate: function() {
this.setupSelect2();
},
setupSelect2: function() {
if(this.state.initialized === true) {
if(
!this.props.field.multiple
|| this.state.initialized === true
|| this.refs.select === undefined
) {
return;
}
if(this.props.field.select2 && Object.keys(this.props.item).length > 0) {
var select2 = jQuery('#'+this.props.field.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if (item.element && item.element.selected) {
return null;
} else {
return item.text;
}
var select2 = jQuery('#'+this.refs.select.id).select2({
width: (this.props.width || ''),
templateResult: function(item) {
if(item.element && item.element.selected) {
return null;
} else {
return item.text;
}
});
}
});
select2.on('change', this.handleChange)
select2.on('change', this.handleChange);
select2.select2(
'val',
this.props.item[this.props.field.name]
);
select2.select2(
'val',
this.props.item[this.props.field.name]
);
this.setState({ initialized: true });
}
this.setState({ initialized: true });
},
loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
@ -55,11 +59,16 @@ function(
});
}
},
handleChange: function() {
handleChange: function(e) {
if(this.props.onValueChange !== undefined) {
if(this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).select2('val');
} else {
value = e.target.value;
}
this.props.onValueChange({
target: {
value: jQuery('#'+this.props.field.id).select2('val'),
value: value,
name: this.props.field.name
}
});
@ -89,7 +98,8 @@ function(
return (
<select
id={ this.props.field.id }
id={ this.props.field.id || this.props.field.name }
ref="select"
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
onChange={ this.handleChange }

View File

@ -70,15 +70,31 @@ define(
this.setState({ loading: true });
// only get values from displayed fields
item = {};
this.props.fields.map(function(field) {
item[field.name] = this.state.item[field.name];
}.bind(this));
// set id if specified
if(this.props.params.id !== undefined) {
item.id = this.props.params.id;
}
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'save',
data: this.state.item
data: item
}).done(function(response) {
this.setState({ loading: false });
if(response === true) {
this.history.pushState(null, '/');
if(this.props.onSuccess !== undefined) {
this.props.onSuccess()
} else {
this.history.pushState(null, '/')
}
if(this.props.params.id !== undefined) {
this.props.messages['updated']();
} else {

View File

@ -1,993 +0,0 @@
/*
* name: MailPoet Form Editor
* author: Jonathan Labreuille
* company: Wysija
* framework: prototype 1.7.2
*/
'use strict';
Event.cacheDelegated = {};
Object.extend(document, (function () {
var cache = Event.cacheDelegated;
function getCacheForSelector(selector) {
return cache[selector] = cache[selector] || {};
}
function getWrappersForSelector(selector, eventName) {
var c = getCacheForSelector(selector);
return c[eventName] = c[eventName] || [];
}
function findWrapper(selector, eventName, handler) {
var c = getWrappersForSelector(selector, eventName);
return c.find(function (wrapper) {
return wrapper.handler === handler
});
}
function destroyWrapper(selector, eventName, handler) {
var c = getCacheForSelector(selector);
if (!c[eventName]) return false;
var wrapper = findWrapper(selector, eventName, handler)
c[eventName] = c[eventName].without(wrapper);
return wrapper;
}
function createWrapper(selector, eventName, handler, context) {
var wrapper, c = getWrappersForSelector(selector, eventName);
if (c.pluck('handler').include(handler)) return false;
wrapper = function (event) {
var element = event.findElement(selector);
if (element) handler.call(context || element, event, element);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
return {
delegate: function (selector, eventName, handler, context) {
var wrapper = createWrapper.apply(null, arguments);
if (wrapper) document.observe(eventName, wrapper);
return document;
},
stopDelegating: function (selector, eventName, handler) {
var length = arguments.length;
switch (length) {
case 2:
getWrappersForSelector(selector, eventName).each(function (wrapper) {
document.stopDelegating(selector, eventName, wrapper.handler);
});
break;
case 1:
Object.keys(getCacheForSelector(selector)).each(function (eventName) {
document.stopDelegating(selector, eventName);
});
break;
case 0:
Object.keys(cache).each(function (selector) {
document.stopDelegating(selector);
});
break;
default:
var wrapper = destroyWrapper.apply(null, arguments);
if (wrapper) document.stopObserving(eventName, wrapper);
}
return document;
}
}
})());
var Observable = (function () {
function getEventName(name, namespace) {
name = name.substring(2);
if (namespace) name = namespace + ':' + name;
return name.underscore().split('_').join(':');
}
function getHandlers(klass) {
var proto = klass.prototype,
namespace = proto.namespace;
return Object.keys(proto).grep(/^on/).inject($H(), function (handlers, name) {
if (name === 'onDomLoaded') return handlers;
handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass));
return handlers;
});
}
function getWrapper(handler, klass) {
return function (event) {
return handler.call(new klass(this), event, event.memo);
}
}
function onDomLoad(selector, klass) {
$$(selector).each(function (element) {
new klass(element).onDomLoaded();
});
}
return {
observe: function (selector) {
if (!this.handlers) this.handlers = {};
if (this.handlers[selector]) return;
var klass = this;
if (this.prototype.onDomLoaded) document.loaded ? onDomLoad(selector, klass) : document.observe('dom:loaded', onDomLoad.curry(selector, klass));
this.handlers[selector] = getHandlers(klass).each(function (handler) {
document.delegate(selector, handler.key, handler.value);
});
},
stopObserving: function (selector) {
if (!this.handlers || !this.handlers[selector]) return;
this.handlers[selector].each(function (handler) {
document.stopDelegating(selector, handler.key, handler.value);
});
delete this.handlers[selector];
}
}
})();
// override droppables
Object.extend(Droppables, {
deactivate: Droppables.deactivate.wrap(function (proceed, drop, draggable) {
if (drop.onLeave) drop.onLeave(draggable, drop.element);
return proceed(drop);
}),
activate: Droppables.activate.wrap(function (proceed, drop, draggable) {
if (drop.onEnter) drop.onEnter(draggable, drop.element);
return proceed(drop);
}),
show: function (point, element) {
if (!this.drops.length) return;
var drop, affected = [];
this.drops.each(function (drop) {
if (Droppables.isAffected(point, element, drop)) affected.push(drop);
});
if (affected.length > 0) drop = Droppables.findDeepestChild(affected);
if (this.last_active && this.last_active !== drop) this.deactivate(this.last_active, element);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if (drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop !== this.last_active) Droppables.activate(drop, element);
}
},
displayArea: function(draggable) {
if(!this.drops.length) return;
// hide controls when displaying drop areas.
WysijaForm.hideBlockControls();
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.addClassName('active');
}
});
},
hideArea: function() {
if (!this.drops.length) return;
this.drops.each(function (drop, iterator) {
if(drop.element.hasClassName('block_placeholder')) {
drop.element.removeClassName('active');
} else if(drop.element.hasClassName('image_placeholder')) {
drop.element.removeClassName('active');
drop.element.up().removeClassName('active');
} else if(drop.element.hasClassName('text_placeholder')) {
drop.element.removeClassName('active');
}
});
},
reset: function (draggable) {
if (this.last_active) this.deactivate(this.last_active, draggable);
}
});
/*
Wysija History handling
POTENTIAL FEATURES:
- set a maximum number of items to be stored
*/
var WysijaHistory = {
container: 'mailpoet_form_history',
size: 30,
enqueue: function(element) {
// create deep clone (includes child elements) of passed element
var clone = element.clone(true);
// check if the field is unique
if(parseInt(clone.readAttribute('wysija_unique'), 10) === 1) {
// check if the field is already in the queue
$(WysijaHistory.container).select('[wysija_field="'+clone.readAttribute('wysija_field')+'"]').invoke('remove');
}
// check history size
if($(WysijaHistory.container).select('> div').length >= WysijaHistory.size) {
// remove oldest element (last in the list)
$(WysijaHistory.container).select('> div').last().remove();
}
// store block in history
$(WysijaHistory.container).insert({ top: clone });
},
dequeue: function() {
// pop last block off the history
var block = $(WysijaHistory.container).select('div').first();
if(block !== undefined) {
// insert block back into the editor
$(WysijaForm.options.body).insert({top: block});
}
},
clear: function() {
$(WysijaHistory.container).innerHTML = '';
},
remove: function(field) {
$(WysijaHistory.container).select('[wysija_field="'+field+'"]').invoke('remove');
}
};
/* MailPoet Form */
var WysijaForm = {
version: '0.6',
options: {
container: 'mailpoet_form_container',
editor: 'mailpoet_form_editor',
body: 'mailpoet_form_body',
toolbar: 'mailpoet_form_toolbar',
templates: 'wysija_widget_templates',
debug: false
},
toolbar: {
effect: null,
x: null,
y: null,
top: null,
left: null
},
scroll: {
top: 0,
left: 0
},
flags: {
doSave: false
},
locks: {
dragging: false,
selectingColor: false,
showingTools: false
},
encodeHtmlValue: function(str) {
return str.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
decodeHtmlValue: function(str) {
return str.replace(/&amp;/g, '&').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&quot;/g, '"');
// ": fix for FileMerge because the previous line fucks up its syntax coloring
},
loading: function(is_loading) {
if(is_loading) {
$(WysijaForm.options.editor).addClassName('loading');
$(WysijaForm.options.toolbar).addClassName('loading');
} else {
$(WysijaForm.options.editor).removeClassName('loading');
$(WysijaForm.options.toolbar).removeClassName('loading');
}
},
loadStatic: function(blocks) {
$A(blocks).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
},
load: function(form) {
if(form.data === undefined) return;
// load body
if(form.data.body !== undefined) {
$A(form.data.body).each(function(block) {
// create block
WysijaForm.Block.create(block, $('block_placeholder'));
});
// load settings
var settings_elements = $('mailpoet_form_settings').getElements();
settings_elements.each(function(setting) {
// skip lists
if(setting.name === 'lists') {
return true;
} else if(setting.name === 'on_success') {
// if the input value is equal to the one stored in the settings
if(setting.value === form.data.settings[setting.name]) {
// check selected value
$(setting).checked = true;
}
} else if(form.data.settings[setting.name] !== undefined) {
if(typeof form.data.settings[setting.name] === 'string') {
setting.setValue(WysijaForm.decodeHtmlValue(form.data.settings[setting.name]));
} else {
setting.setValue(form.data.settings[setting.name]);
}
}
});
}
},
save: function() {
var position = 1,
data = {
'version': WysijaForm.version,
'settings': $('mailpoet_form_settings').serialize(true),
'body': [],
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
};
// body
WysijaForm.getBlocks().each(function(b) {
var block_data = (typeof(b.block['save']) === 'function') ? b.block.save() : null;
if(block_data !== null) {
// set block position
block_data['position'] = position;
// increment position
position++;
// add block data to body
data['body'].push(block_data);
}
});
return data;
},
init: function() {
// set document scroll
info('init -> set scroll offsets');
WysijaForm.setScrollOffsets();
// position toolbar
info('init -> set toolbar position');
WysijaForm.setToolbarPosition();
// enable droppable targets
info('init -> make droppable');
WysijaForm.makeDroppable();
// enable sortable
info('init -> make sortable');
WysijaForm.makeSortable();
// hide controls
info('init -> hide controls');
WysijaForm.hideControls();
// hide settings
info('init -> hide settings');
WysijaForm.hideSettings();
// set settings buttons position
info('init -> init settings');
WysijaForm.setSettingsPosition();
// toggle widgets
info('init -> toggle widgets');
WysijaForm.toggleWidgets();
},
getFieldData: function(element) {
// get basic field data
var data = {
type: element.readAttribute('wysija_type'),
field: element.readAttribute('wysija_field'),
name: element.readAttribute('wysija_name'),
unique: parseInt(element.readAttribute('wysija_unique') || 0, 10),
static: parseInt(element.readAttribute('wysija_static') || 0, 10),
element: element,
params: ''
};
// get params (may be empty)
if(element.readAttribute('wysija_params') !== null && element.readAttribute('wysija_params').length > 0) {
data.params = JSON.parse(element.readAttribute('wysija_params'));
}
return data;
},
toggleWidgets: function() {
$$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled');
// loop through each unique field already inserted in the editor and disable its toolbar equivalent
$$('#'+WysijaForm.options.editor+' [wysija_unique="1"]').each(function(element) {
var field = $$('#'+WysijaForm.options.toolbar+' [wysija_field="'+element.readAttribute('wysija_field')+'"]').first();
if(field !== undefined) {
field.addClassName('disabled');
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_list_selection')[(($$('#'+WysijaForm.options.editor+' [wysija_field="list"]').length > 0) === true) ? 'hide': 'show']();
},
setBlockPositions: function(event, target) {
// release dragging lock
WysijaForm.locks.dragging = false;
var index = 1;
WysijaForm.getBlocks().each(function (container) {
container.setPosition(index++);
// remove z-index value to avoid issues when resizing images
if(container['block'] !== undefined) {
container.block.element.setStyle({zIndex: ''});
}
});
if(target !== undefined) {
// get placeholders (previous placeholder matches the placeholder linked to the next block)
var block_placeholder = $(target.element.readAttribute('wysija_placeholder')),
previous_placeholder = target.element.previous('.block_placeholder');
if(block_placeholder !== null) {
// put block placeholder before the current block
target.element.insert({before: block_placeholder});
// if the next block is a wysija_block, insert previous placeholder
if(target.element.next() !== undefined && target.element.next().hasClassName('mailpoet_form_block') && previous_placeholder !== undefined) {
target.element.insert({after: previous_placeholder});
}
}
}
},
setScrollOffsets: function() {
WysijaForm.scroll = document.viewport.getScrollOffsets();
},
hideSettings: function() {
$(WysijaForm.options.container).select('.wysija_settings').invoke('hide');
},
setSettingsPosition: function() {
// get viewport offsets and dimensions
var viewportHeight = document.viewport.getHeight(),
blockPadding = 5;
$(WysijaForm.options.container).select('.wysija_settings').each(function(element) {
// get parent dimensions and position
var parentDim = element.up('.mailpoet_form_block').getDimensions(),
parentPos = element.up('.mailpoet_form_block').cumulativeOffset(),
is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)) ? true : false,
buttonMargin = 5,
relativeTop = buttonMargin;
if(is_visible) {
// desired position is set to center of viewport
var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10),
parentTop = parseInt(parentPos.top - blockPadding, 10),
parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10);
// always center
relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10);
}
// set position for button
$(element).setStyle({
left: parseInt((parentDim.width / 2) - (element.getWidth() / 2)) + 'px',
top: relativeTop + 'px'
});
});
},
initToolbarPosition: function() {
if(WysijaForm.toolbar.top === null) WysijaForm.toolbar.top = parseInt($(WysijaForm.options.container).positionedOffset().top);
if(WysijaForm.toolbar.y === null) WysijaForm.toolbar.y = parseInt(WysijaForm.toolbar.top);
if(isRtl) {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = 0;
} else {
if(WysijaForm.toolbar.left === null) WysijaForm.toolbar.left = parseInt($(WysijaForm.options.container).positionedOffset().left);
}
if(WysijaForm.toolbar.x === null) WysijaForm.toolbar.x = parseInt(WysijaForm.toolbar.left + $(WysijaForm.options.container).getDimensions().width + 15);
},
setToolbarPosition: function() {
WysijaForm.initToolbarPosition();
var position = { top: WysijaForm.toolbar.y + 'px', visibility: 'visible' };
if(isRtl) {
position.right = WysijaForm.toolbar.x + 'px';
} else {
position.left = WysijaForm.toolbar.x + 'px';
}
$(WysijaForm.options.toolbar).setStyle(position);
},
updateToolbarPosition: function() {
// init toolbar position (updates scroll and toolbar y)
WysijaForm.initToolbarPosition();
// cancel previous effect
if(WysijaForm.toolbar.effect !== null) WysijaForm.toolbar.effect.cancel();
if(WysijaForm.scroll.top >= (WysijaForm.toolbar.top - 20)) {
WysijaForm.toolbar.y = parseInt(20 + WysijaForm.scroll.top);
// start effect
WysijaForm.toolbar.effect = new Effect.Move(WysijaForm.options.toolbar, {
x: WysijaForm.toolbar.x,
y: WysijaForm.toolbar.y,
mode: 'absolute',
duration: 0.2
});
} else {
$(WysijaForm.options.toolbar).setStyle({
left: WysijaForm.toolbar.x + 'px',
top: WysijaForm.toolbar.top + 'px'
});
}
},
blockDropOptions: {
accept: $w('mailpoet_form_field'), // acceptable items (classes array)
onEnter: function (draggable, droppable) {
$(droppable).addClassName('hover');
},
onLeave: function (draggable, droppable) {
$(droppable).removeClassName('hover');
},
onDrop: function (draggable, droppable) {
// custom data for images
droppable.fire('wjfe:item:drop', WysijaForm.getFieldData(draggable));
$(droppable).removeClassName('hover');
}
},
hideControls: function() {
try {
return WysijaForm.getBlocks().invoke('hideControls');
} catch(e) { return; }
},
hideTools: function() {
$$('.wysija_tools').invoke('hide');
WysijaForm.locks.showingTools = false;
},
instances: {},
get: function (element, type) {
if(type === undefined) type = 'block';
// identify element
var id = element.identify();
var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
WysijaForm.instances[id] = instance;
return instance;
},
makeDroppable: function() {
Droppables.add('block_placeholder', WysijaForm.blockDropOptions);
},
makeSortable: function () {
var body = $(WysijaForm.options.body);
Sortable.create(body, {
tag: 'div',
only: 'mailpoet_form_block',
scroll: window,
handle: 'handle',
constraint: 'vertical'
});
Draggables.removeObserver(body);
Draggables.addObserver({
element: body,
onStart: WysijaForm.startBlockPositions,
onEnd: WysijaForm.setBlockPositions
});
},
hideBlockControls: function() {
$$('.wysija_controls').invoke('hide');
this.getBlockElements().invoke('removeClassName', 'hover');
},
getBlocks: function () {
return WysijaForm.getBlockElements().map(function (element) {
return WysijaForm.get(element);
});
},
getBlockElements: function () {
return $(WysijaForm.options.container).select('.mailpoet_form_block');
},
startBlockPositions: function(event, target) {
if(target.element.hasClassName('mailpoet_form_block')) {
// store block placeholder id for the block that is being repositionned
if(target.element.previous('.block_placeholder') !== undefined) {
target.element.writeAttribute('wysija_placeholder', target.element.previous('.block_placeholder').identify());
}
}
WysijaForm.locks.dragging = true;
},
encodeURIComponent: function(str) {
// check if it's a url and if so, prevent encoding of protocol
var regexp = new RegExp(/^http[s]?:\/\//),
protocol = regexp.exec(str);
if(protocol === null) {
// this is not a url so encode the whole thing
return encodeURIComponent(str).replace(/[!'()*]/g, escape);
} else if(protocol.length === 1) {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
}
};
WysijaForm.DraggableItem = Class.create({
initialize: function (element) {
this.elementType = $(element).readAttribute('wysija_type');
this.element = $(element).down() || $(element);
this.clone = this.cloneElement();
this.insert();
},
STYLES: new Template('position: absolute; top: #{top}px; left: #{left}px;'),
cloneElement: function () {
var clone = this.element.clone(),
offset = this.element.cumulativeOffset(),
list = this.getList(),
styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
clone.setStyle(styles);
clone.addClassName('mailpoet_form_widget');
clone.addClassName(this.elementType);
clone.innerHTML = this.element.innerHTML;
return clone;
},
getOffset: function () {
return this.element.offsetTop - this.getList().scrollTop;
},
getList: function () {
return this.element.up('ul');
},
insert: function () {
$$("body")[0].insert(this.clone);
},
onMousedown: function (event) {
var draggable = new Draggable(this.clone, {
scroll: window,
onStart: function () {
Droppables.displayArea(draggable);
},
onEnd: function (drag) {
drag.destroy();
drag.element.remove();
Droppables.hideArea();
},
starteffect: function (element) {
new Effect.Opacity(element, {
duration: 0.2,
from: element.getOpacity(),
to: 0.7
});
},
endeffect: Prototype.emptyFunction
});
draggable.initDrag(event);
draggable.startDrag(event);
return draggable;
}
});
Object.extend(WysijaForm.DraggableItem, Observable).observe('a[class="mailpoet_form_field"]');
WysijaForm.Block = Class.create({
/* Invoked on load */
initialize: function(element) {
info('block -> init');
this.element = $(element);
this.block = new WysijaForm.Widget(this.element);
// enable block placeholder
this.block.makeBlockDroppable();
// setup events
if(this.block['setup'] !== undefined) {
this.block.setup();
}
return this;
},
setPosition: function(position) {
this.element.writeAttribute('wysija_position', position);
},
hideControls: function() {
if(this['getControls']) {
this.element.removeClassName('hover');
this.getControls().hide();
}
},
showControls: function() {
if(this['getControls']) {
this.element.addClassName('hover');
try {
this.getControls().show();
} catch(e) {
;
}
}
},
makeBlockDroppable: function() {
if(this.isBlockDroppableEnabled() === false) {
var block_placeholder = this.getBlockDroppable();
Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions);
block_placeholder.addClassName('enabled');
}
},
removeBlockDroppable: function() {
if(this.isBlockDroppableEnabled()) {
var block_placeholder = this.getBlockDroppable();
Droppables.remove(block_placeholder.identify());
block_placeholder.removeClassName('enabled');
}
},
isBlockDroppableEnabled: function() {
// if the block_placeholder does not exist, create it
var block_placeholder = this.getBlockDroppable();
if(block_placeholder === null) {
return this.createBlockDroppable().hasClassName('enabled');
} else {
return block_placeholder.hasClassName('enabled');
}
},
createBlockDroppable: function() {
info('block -> createBlockDroppable');
this.element.insert({before: '<div class=\"block_placeholder\">'+$('block_placeholder').innerHTML+'</div>'});
return this.element.previous('.block_placeholder');
},
getBlockDroppable: function() {
if(this.element.previous() === undefined || this.element.previous().hasClassName('block_placeholder') === false) {
return null;
} else {
return this.element.previous();
}
},
getControls: function() {
return this.element.down('.wysija_controls');
},
setupControls: function() {
// enable controls
this.controls = this.getControls();
if(this.controls) {
// setup events for block controls
this.element.observe('mouseover', function() {
// special cases where controls shouldn't be displayed
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true || WysijaForm.locks.showingTools === true) return;
// set block flag
this.element.addClassName('hover');
// show controls
this.showControls();
// show settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').show();
}
}.bind(this));
this.element.observe('mouseout', function() {
// special cases where controls shouldn't hide
if(WysijaForm.locks.dragging === true || WysijaForm.locks.selectingColor === true) return;
// hide controls
this.hideControls();
// hide settings if present
if(this.element.down('.wysija_settings') !== undefined) {
this.element.down('.wysija_settings').hide();
}
}.bind(this));
// setup click event for remove button
this.removeButton = this.controls.down('.remove') || null;
if(this.removeButton !== null) {
this.removeButton.observe('click', function() {
this.removeBlock();
this.removeButton.stopObserving('click');
}.bind(this));
}
// setup click event for settings button
this.settingsButton = this.element.down('.settings') || null;
if(this.settingsButton !== null) {
this.settingsButton.observe('click', function(event) {
// TODO: refactor
var block = $(event.target).up('.mailpoet_form_block') || null;
if(block !== null) {
var field = WysijaForm.getFieldData(block);
this.editSettings();
}
}.bind(this));
}
}
return this;
},
removeBlock: function(callback) {
info('block -> removeBlock');
// save block in history
WysijaHistory.enqueue(this.element);
Effect.Fade(this.element.identify(), {
duration: 0.2,
afterFinish: function(effect) {
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
// show controls of next block to allow mass delete
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
}
// remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove();
}
// remove element from the DOM
this.element.remove();
// reset block positions
WysijaForm.setBlockPositions();
// toggle widgets
WysijaForm.toggleWidgets();
// optional callback execution after completely removing block
if(callback !== undefined && typeof(callback) === 'function') {
callback();
}
// remove block instance
delete WysijaForm.instances[this.element.identify()];
}.bind(this)
});
}
});
/* Invoked on item dropped */
WysijaForm.Block.create = function(block, target) {
if($('form_template_'+block.type) === null) {
return false;
}
var body = $(WysijaForm.options.body),
block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+block.type).innerHTML),
output = '';
// set block template (depending on the block type)
block.template = template(block);
output = block_template(block);
// check if the new block is unique and if there's already an instance
// of it in the history. If so, remove its former instance from the history
if(block.unique === 1) {
WysijaHistory.remove(block.field);
}
// if the drop target was the bottom placeholder
if(target.identify() === 'block_placeholder') {
// insert block at the bottom
body.insert(output);
//block = body.childElements().last();
} else {
// insert block before the drop target
target.insert({before: output });
//block = target.previous('.mailpoet_form_block');
}
// refresh sortable items
WysijaForm.makeSortable();
// refresh block positions
WysijaForm.setBlockPositions();
// position settings
WysijaForm.setSettingsPosition();
};
document.observe('wjfe:item:drop', function(event) {
info('create block');
WysijaForm.Block.create(event.memo, event.target);
// hide block controls
info('hide controls');
WysijaForm.hideBlockControls();
// toggle widgets
setTimeout(function() {
WysijaForm.toggleWidgets();
}, 1);
});
/* Form Widget */
WysijaForm.Widget = Class.create(WysijaForm.Block, {
initialize: function(element) {
info('widget -> init');
this.element = $(element);
return this;
},
setup: function() {
info('widget -> setup');
this.setupControls();
},
save: function() {
info('widget -> save');
var data = this.getData();
if(data.element !== undefined) {
delete data.element;
}
return data;
},
setData: function(data) {
var current_data = this.getData(),
params = $H(current_data.params).merge(data.params).toObject();
// update type if it changed
if(data.type !== undefined && data.type !== current_data.type) {
this.element.writeAttribute('wysija_type', data.type);
}
// update params
this.element.writeAttribute('wysija_params', JSON.stringify(params));
},
getData: function() {
var data = WysijaForm.getFieldData(this.element);
// decode params
if(data.params.length > 0) {
data.params = JSON.parse(data.params);
}
return data;
},
getControls: function() {
return this.element.down('.wysija_controls');
},
remove: function() {
this.removeBlock();
},
redraw: function(data) {
// set parameters
this.setData(data);
var options = this.getData();
// redraw block
var block_template = Handlebars.compile($('form_template_block').innerHTML),
template = Handlebars.compile($('form_template_'+options.type).innerHTML),
data = $H(options).merge({ template: template(options) }).toObject();
this.element.replace(block_template(data));
WysijaForm.init();
},
editSettings: function() {
MailPoet.Modal.popup({
title: 'Edit field settings', // TODO: translate!
template: jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function() {
var data = jQuery('#form_field_settings').serializeObject();
this.redraw(data);
}.bind(this)
});
},
getSettings: function() {
return this.element.down('.wysija_settings');
}
});
/* When dom is loaded, initialize WysijaForm */
document.observe('dom:loaded', WysijaForm.init);
/* LOGGING */
function info(value) {
if(WysijaForm.options.debug === false) return;
if(!(window.console && console.log)) {
(function() {
var noop = function() {};
var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
var length = methods.length;
var console = window.console = {};
while(length--) {
console[methods[length]] = noop;
}
}());
}
try {
console.log('[DEBUG] '+value);
} catch(e) {}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, History } from 'react-router'
import MailPoet from 'mailpoet'
import Form from 'form/form.jsx'
const fields = [
{
name: 'name',
label: 'Name',
type: 'text'
},
{
name: 'segments',
label: 'Lists',
type: 'selection',
endpoint: 'segments',
multiple: true
}
]
const messages = {
updated: function() {
MailPoet.Notice.success('Form successfully updated!');
},
created: function() {
MailPoet.Notice.success('Form successfully added!');
}
}
const FormForm = React.createClass({
mixins: [
History
],
render() {
return (
<div>
<h2 className="title">
Form <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="forms"
fields={ fields }
params={ this.props.params }
messages={ messages }
onSuccess={ this.history.goBack } />
</div>
);
}
});
module.exports = FormForm

View File

@ -0,0 +1,29 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import FormList from 'forms/list.jsx'
import FormForm from 'forms/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
}
});
let container = document.getElementById('forms_container');
if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRoute component={ FormList } />
<Route path="new" component={ FormForm } />
<Route path="edit/:id" component={ FormForm } />
<Route path="*" component={ FormList } />
</Route>
</Router>
), container);
}

View File

@ -0,0 +1,188 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Link } from 'react-router'
import Listing from 'listing/listing.jsx'
import classNames from 'classnames'
import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'forms',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const FormList = React.createClass({
createForm() {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
}
});
},
renderItem(form, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
return (
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
</td>
</div>
);
},
render() {
return (
<div>
<h2 className="title">
Forms <a
className="add-new-h2"
href="javascript:;"
onClick={ this.createForm }
>New</a>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = FormList;

View File

@ -45,12 +45,13 @@ function(
data.action = this.state.action;
var callback = function() {};
if(action['onSuccess'] !== undefined) {
data.onSuccess = action.onSuccess;
callback = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data);
this.props.onBulkAction(selected_ids, data, callback);
}
this.setState({

View File

@ -1,57 +1,66 @@
define([
'react'
'react',
'jquery'
],
function(
React
React,
jQuery
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
var filters = this.props.filters.map(function(filter, index) {
var value = this.refs['filter-'+index].value;
if(value) {
return {
'name': filter.name,
'value': value
};
}
}.bind(this));
let filters = {}
this.getAvailableFilters().map((filter, i) => {
filters[this.refs['filter-'+i].name] = this.refs['filter-'+i].value
})
return this.props.onSelectFilter(filters);
},
handleChangeAction: function() {
return true;
getAvailableFilters: function() {
let filters = this.props.filters;
return Object.keys(filters).filter(function(filter) {
return !(
filters[filter].length === 0
|| (
filters[filter].length === 1
&& !filters[filter][0].value
)
);
})
},
render: function() {
var filters = this.props.filters
.filter(function(filter) {
return !(
filter.options.length === 0
|| (
filter.options.length === 1
&& !filter.options[0].value
)
);
})
const filters = this.props.filters;
const selected_filters = this.props.filter;
const available_filters = this.getAvailableFilters()
.map(function(filter, i) {
let default_value = false;
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}
return (
<select
ref={ 'filter-'+i }
key={ 'filter-'+i }
onChange={ this.handleChangeAction }>
{ filter.options.map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
ref={ `filter-${i}` }
key={ `filter-${i}` }
name={ filter }
defaultValue={ default_value }
>
{ filters[filter].map(function(option, j) {
return (
<option
value={ option.value }
key={ 'filter-option-' + j }
>{ option.label }</option>
);
}.bind(this)) }
</select>
);
}.bind(this));
var button = false;
let button = false;
if(filters.length > 0) {
if(available_filters.length > 0) {
button = (
<input
onClick={ this.handleFilterAction }
@ -63,7 +72,7 @@ function(
return (
<div className="alignleft actions actions">
{ filters }
{ available_filters }
{ button }
</div>
);

View File

@ -9,6 +9,9 @@ define(['react', 'classnames'], function(React, classNames) {
render: function() {
var columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
: 'asc';
return (
<ListingColumn
onSort={this.props.onSort}

View File

@ -34,11 +34,9 @@ define(
};
},
handleSelectItem: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelectItem(
parseInt(e.target.value, 10),
is_checked
e.target.checked
);
return !e.target.checked;
@ -46,8 +44,11 @@ define(
handleRestoreItem: function(id) {
this.props.onRestoreItem(id);
},
handleDeleteItem: function(id, confirm = false) {
this.props.onDeleteItem(id, confirm);
handleTrashItem: function(id) {
this.props.onTrashItem(id);
},
handleDeleteItem: function(id) {
this.props.onDeleteItem(id);
},
handleToggleItem: function(id) {
this.setState({ toggled: !this.state.toggled });
@ -58,11 +59,12 @@ define(
if(this.props.is_selectable === true) {
checkbox = (
<th className="check-column" scope="row">
<label className="screen-reader-text">
{ 'Select ' + this.props.item.email }</label>
<label className="screen-reader-text">{
'Select ' + this.props.item[this.props.columns[0].name]
}</label>
<input
type="checkbox"
defaultValue={ this.props.item.id }
value={ this.props.item.id }
checked={
this.props.item.selected || this.props.selection === 'all'
}
@ -77,12 +79,39 @@ define(
if(custom_actions.length > 0) {
item_actions = custom_actions.map(function(action, index) {
return (
<span key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item.id) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
if(action.refresh) {
return (
<span
onClick={ this.props.onRefreshItems }
key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else if(action.link) {
return (
<span
key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else {
return (
<span
key={ 'action-'+index } className={ action.name }>
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
this.props.item,
this.props.onRefreshItems
)
: false
}>{ action.label }</a>
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
}
}.bind(this));
} else {
item_actions = (
@ -112,8 +141,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
true
this.props.item.id
)}
>Delete permanently</a>
</span>
@ -134,10 +162,9 @@ define(
<span className="trash">
<a
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id,
false
this.props.item.id
) }>
Trash
</a>
@ -222,7 +249,7 @@ define(
</td>
</tr>
{this.props.items.map(function(item) {
{this.props.items.map(function(item, index) {
item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
@ -233,11 +260,13 @@ define(
onRenderItem={ this.props.onRenderItem }
onDeleteItem={ this.props.onDeleteItem }
onRestoreItem={ this.props.onRestoreItem }
onTrashItem={ this.props.onTrashItem }
onRefreshItems={ this.props.onRefreshItems }
selection={ this.props.selection }
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
group={ this.props.group }
key={ 'item-' + item.id }
key={ `item-${item.id}-${index}` }
item={ item } />
);
}.bind(this))}
@ -248,6 +277,9 @@ define(
});
var Listing = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
loading: false,
@ -260,43 +292,136 @@ define(
items: [],
groups: [],
group: 'all',
filters: [],
filter: [],
filters: {},
filter: {},
selected_ids: [],
selection: false
};
},
componentDidUpdate: function(prevProps, prevState) {
// reset group to "all" if trash gets emptied
if(
// we were viewing the trash
(prevState.group === 'trash' && prevState.count > 0)
&&
// we are still viewing the trash but there are no items left
(this.state.group === 'trash' && this.state.count === 0)
&&
// only do this when no filter is set
(Object.keys(this.state.filter).length === 0)
) {
this.handleGroup('all');
}
},
getParam: function(param) {
var regex = /(.*)\[(.*)\]/
var matches = regex.exec(param)
return [matches[1], matches[2]]
},
initWithParams: function(params) {
let state = this.state || {}
let original_state = state
// check for url params
if(params.splat !== undefined) {
params.splat.split('/').map(param => {
let [key, value] = this.getParam(param);
switch(key) {
case 'filter':
let filters = {}
value.split('&').map(function(pair) {
let [k, v] = pair.split('=')
filters[k] = v
}
)
state.filter = filters
break;
default:
state[key] = value
}
})
}
if(this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
}
this.setState(state, function() {
this.getItems();
}.bind(this));
},
setParams: function() {
var params = Object.keys(this.state)
.filter(key => {
return (
[
'group',
'filter',
'search',
'page',
'sort_by',
'sort_order'
].indexOf(key) !== -1
)
})
.map(key => {
let value = this.state[key]
if(value === Object(value)) {
value = jQuery.param(value)
} else if(value === Boolean(value)) {
value = value.toString()
}
if(value !== '') {
return `${key}[${value}]`
}
})
.filter(key => { return (key !== undefined) })
.join('/');
params = '/'+params
if(this.props.location) {
if(this.props.location.pathname !== params) {
this.history.pushState(null, `${params}`)
}
}
},
componentDidMount: function() {
this.getItems();
if(this.isMounted()) {
const params = this.props.params || {}
this.initWithParams(params)
}
},
componentWillReceiveProps: function(nextProps) {
const params = nextProps.params || {}
this.initWithParams(params)
},
getItems: function() {
this.setState({ loading: true });
if(this.isMounted()) {
this.setState({ loading: true });
this.clearSelection();
this.clearSelection();
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
if(this.isMounted()) {
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
group: this.state.group,
filter: this.state.filter,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
}
}).done(function(response) {
this.setState({
items: response.items || [],
filters: response.filters || [],
filters: response.filters || {},
groups: response.groups || [],
count: response.count || 0,
loading: false
});
}
}.bind(this));
}.bind(this));
}
},
handleRestoreItem: function(id) {
this.setState({
@ -318,7 +443,27 @@ define(
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id, confirm = false) {
handleTrashItem: function(id) {
this.setState({
loading: true,
page: 1
});
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'trash',
data: id
}).done(function(response) {
if(
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
) {
this.props.messages.onTrash(response);
}
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id) {
this.setState({
loading: true,
page: 1
@ -327,31 +472,18 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'delete',
data: {
id: id,
confirm: confirm
}
data: id
}).done(function(response) {
if(confirm === true) {
if(
this.props.messages !== undefined
&& this.props.messages['onConfirmDelete'] !== undefined
) {
this.props.messages.onConfirmDelete(response);
}
} else {
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
this.getItems();
}.bind(this));
},
handleBulkAction: function(selected_ids, params) {
handleBulkAction: function(selected_ids, params, callback) {
if(
this.state.selection === false
&& this.state.selected_ids.length === 0
@ -362,12 +494,6 @@ define(
this.setState({ loading: true });
var data = params || {};
var callback = ((data['onSuccess'] !== undefined)
? data['onSuccess']
: function() {}
);
delete data.onSuccess;
data.listing = {
offset: 0,
limit: 0,
@ -379,7 +505,7 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'bulk_action',
action: 'bulkAction',
data: data
}).done(function(response) {
this.getItems();
@ -393,6 +519,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -401,6 +528,7 @@ define(
sort_by: sort_by,
sort_order: sort_order,
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -460,6 +588,7 @@ define(
filter: filters,
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -469,10 +598,11 @@ define(
this.setState({
group: group,
filter: [],
filter: {},
search: '',
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -482,6 +612,7 @@ define(
selection: false,
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
@ -489,17 +620,14 @@ define(
var render = this.props.onRenderItem(item, actions);
return render.props.children;
},
handleRefreshItems: function() {
this.getItems();
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
// set sortable columns
columns = this.props.columns.map(function(column) {
column.sorted = (column.name === sort_by) ? sort_order : false;
return column;
});
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
@ -511,12 +639,9 @@ define(
onSuccess: this.props.messages.onRestore
},
{
name: 'trash',
name: 'delete',
label: 'Delete permanently',
onSuccess: this.props.messages.onConfirmDelete,
getData: function() {
return { confirm: true };
}
onSuccess: this.props.messages.onDelete
}
];
}
@ -524,22 +649,42 @@ define(
// item actions
var item_actions = this.props.item_actions || [];
var tableClasses = classNames(
var table_classes = classNames(
'mailpoet_listing_table',
'wp-list-table',
'widefat',
'fixed',
'striped',
{ 'mailpoet_listing_loading': this.state.loading }
);
// search
var search = (
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search }
/>
);
if(this.props.search === false) {
search = false;
}
// groups
var groups = (
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup }
/>
);
if(this.props.groups === false) {
groups = false;
}
return (
<div>
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup } />
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search } />
{ groups }
{ search }
<div className="tablenav top clearfix">
<ListingBulkActions
bulk_actions={ bulk_actions }
@ -548,7 +693,7 @@ define(
onBulkAction={ this.handleBulkAction } />
<ListingFilters
filters={ this.state.filters }
filter={ this.state.filter }
filter={ this.state.filter }
onSelectFilter={ this.handleFilter } />
<ListingPages
count={ this.state.count }
@ -556,7 +701,7 @@ define(
limit={ this.state.limit }
onSetPage={ this.handleSetPage } />
</div>
<table className={ tableClasses }>
<table className={ table_classes }>
<thead>
<ListingHeader
onSort={ this.handleSort }
@ -572,6 +717,8 @@ define(
onRenderItem={ this.handleRenderItem }
onDeleteItem={ this.handleDeleteItem }
onRestoreItem={ this.handleRestoreItem }
onTrashItem={ this.handleTrashItem }
onRefreshItems={ this.handleRefreshItems }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 }
onSelectItem={ this.handleSelectItem }

View File

@ -40,23 +40,23 @@ define(['react', 'classnames'], function(React, classNames) {
},
render: function() {
if(this.props.count === 0) {
return (<div></div>);
return false;
} else {
var pagination,
firstPage = (
var pagination = false;
var firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
),
previousPage = (
);
var previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
nextPage = (
);
var nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
lastPage = (
);
var lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if(this.props.count > this.props.limit) {
if(this.props.limit > 0 && this.props.count > this.props.limit) {
if(this.props.page > 1) {
previousPage = (
<a href="javascript:;"
@ -104,6 +104,7 @@ define(['react', 'classnames'], function(React, classNames) {
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
@ -128,6 +129,7 @@ define(['react', 'classnames'], function(React, classNames) {
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
@ -140,7 +142,7 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } item(s)</span>
<span className="displaying-num">{ this.props.count } items</span>
{ pagination }
</div>
);

View File

@ -7,26 +7,33 @@ define(['react'], function(React) {
this.refs.search.value
);
},
componentWillReceiveProps: function(nextProps) {
this.refs.search.value = nextProps.search
},
render: function() {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
if(this.props.search === false) {
return false;
} else {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>
);
}
}
});

View File

@ -166,81 +166,62 @@ define([
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
initSelection: function(element, callback) {
// On external data load tell select2 which terms to preselect
callback(_.map(
that.model.get('terms').toJSON(),
function(item) {
return {
id: item.id,
text: item.text,
};
}
));
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' ).on({
'change': function(e){
var data = jQuery(this).data('selected');
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
} else {
data = _.filter(data, function(item) {
return item.id !== e.removed.id;
});
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2('close');
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
toggleDisplayOptions: function(event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),

View File

@ -297,9 +297,9 @@ define([
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
targetImageWidth = 1200,
targetImageWidth = 1320,
// For main image use the size, that's closest to being 600px in width
// For main image use the size, that's closest to being 660px in width
sizeKeys = _.keys(sizes),
// Pick the width that is closest to target width

View File

@ -21,7 +21,8 @@ define([
'newsletter_editor/components/wordpress',
'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider'
'newsletter_editor/blocks/divider',
'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict";
@ -249,59 +250,62 @@ define([
this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true,
allowClear: true,
query: function(options) {
var taxonomies = [];
// Delegate data loading to our own endpoints
WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
ajax: {
data: function (params) {
return {
term: params.term
};
},
transport: function(options, success, failure) {
var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function(terms) {
return {
taxonomies: taxonomies,
terms: terms,
};
});
return promise;
});
promise.then(success);
promise.fail(failure);
return promise;
}).done(function(args) {
},
processResults: function(data) {
// Transform taxonomies and terms into select2 compatible format
options.callback({
return {
results: _.map(
args.terms,
data.terms,
function(item) {
return _.defaults({
text: args.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
text: data.taxonomies[item.taxonomy].labels.singular_name + ': ' + item.name,
id: item.term_id
}, item);
}
)
});
});
};
},
},
}).trigger( 'change' ).on({
'change': function(e){
var data = [];
if (typeof data === 'string') {
if (data === '') {
data = [];
} else {
data = JSON.parse(data);
}
}
if ( e.added ){
data.push(e.added);
}
// Update ALC model
that.model.set('terms', data);
jQuery(this).data('selected', JSON.stringify(data));
}
});
}).on({
'select2:select': function(event) {
var terms = that.model.get('terms');
terms.add(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
'select2:unselect': function(event) {
var terms = that.model.get('terms');
terms.remove(event.params.data);
// Reset whole model in order for change events to propagate properly
that.model.set('terms', terms.toJSON());
},
}).trigger( 'change' );
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);

View File

@ -1,9 +1,13 @@
define([
'newsletter_editor/App',
'mailpoet',
'notice',
'backbone',
'backbone.marionette'
], function(App, MailPoet, Backbone, Marionette) {
'backbone.marionette',
'jquery',
'blob',
'filesaver'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver) {
"use strict";
@ -52,6 +56,18 @@ define([
});
};
Module.exportTemplate = function(options) {
var data = _.extend(options || {}, {
body: App.getBody(),
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
FileSaver.saveAs(blob, 'template.json');
};
Module.SaveView = Marionette.LayoutView.extend({
getTemplate: function() { return templates.save; },
events: {
@ -62,7 +78,8 @@ define([
'click .mailpoet_save_template': 'toggleSaveAsTemplate',
'click .mailpoet_save_as_template': 'saveAsTemplate',
/* Export template */
'click .mailpoet_save_export': 'exportTemplate',
'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate',
},
initialize: function(options) {
App.getChannel().on('beforeEditorSave', this.beforeSave, this);
@ -117,12 +134,33 @@ define([
this.hideOptionContents();
},
toggleExportTemplate: function() {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
this.toggleSaveOptions();
},
hideExportTemplate: function() {
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
},
exportTemplate: function() {
console.log('Exporting template');
this.hideOptionContents();
var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val();
if (templateName === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateNameMissing'));
} else if (templateDescription === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateDescriptionMissing'));
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
});
this.hideExportTemplate();
}
},
hideOptionContents: function() {
this.hideSaveAsTemplate();
this.hideExportTemplate();
this.$('.mailpoet_save_options').addClass('mailpoet_hidden');
},
next: function() {

View File

@ -37,19 +37,77 @@ define(
}
];
var messages = {
onTrash: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onDelete: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d newsletters were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.newsletters;
var message = null;
if(count === 1 || response === true) {
message = (
'1 newsletter has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d newsletters have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
label: 'Trash',
onSuccess: messages.onTrash
}
];
var item_actions = [
{
name: 'edit',
link: function(id) {
link: function(item) {
return (
<a href={ '?page=mailpoet-newsletter-editor&id=' + id }>
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
</a>
);
@ -100,11 +158,13 @@ define(
</h2>
<Listing
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions } />
item_actions={ item_actions }
messages={ messages } />
</div>
);
}

View File

@ -7,6 +7,7 @@ import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
@ -27,8 +28,10 @@ if(container) {
<Route path="new" component={ NewsletterTypes } />
<Route name="standard" path="new/standard" component={ NewsletterStandard } />
<Route name="welcome" path="new/welcome" component={ NewsletterWelcome } />
<Route name="notification" path="new/notification" component={ NewsletterNotification } />
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
<Route path="send/:id" component={ NewsletterSend } />
<Route path="filter[:filter]" component={ NewsletterList } />
<Route path="*" component={ NewsletterList } />
</Route>
</Router>

View File

@ -34,8 +34,7 @@ define(
placeholder: "Select a list",
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
select2: true
multiple: true
},
{
name: 'sender',

View File

@ -1,6 +1,7 @@
define(
[
'react',
'underscore',
'mailpoet',
'react-router',
'classnames',
@ -8,11 +9,67 @@ define(
],
function(
React,
_,
MailPoet,
Router,
classNames,
Breadcrumb
) {
var ImportTemplate = React.createClass({
saveTemplate: function(template) {
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: template
}).done(function(response) {
if(response === true) {
this.props.onImport(template);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.bind(this));
},
handleSubmit: function(e) {
e.preventDefault();
if (_.size(this.refs.templateFile.files) <= 0) return false;
var file = _.first(this.refs.templateFile.files),
reader = new FileReader(),
saveTemplate = this.saveTemplate;
reader.onload = function(e) {
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
}
}.bind(this);
reader.readAsText(file);
},
render: function() {
return (
<div>
<h2>Import a template</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
type="submit"
value="Upload" />
</p>
</form>
</div>
);
},
});
var NewsletterTemplates = React.createClass({
mixins: [
Router.History
@ -93,6 +150,9 @@ define(
this.setState({ loading: false });
}
},
handleTemplateImport: function() {
this.getTemplates();
},
render: function() {
var templates = this.state.templates.map(function(template, index) {
var deleteLink = (
@ -152,6 +212,8 @@ define(
<ul className={ boxClasses }>
{ templates }
</ul>
<ImportTemplate onImport={this.handleTemplateImport} />
</div>
);
}

View File

@ -85,6 +85,26 @@ define(
</a>
</div>
</li>
<li data-type="notification">
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Post notifications</h3>
<p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
</p>
</div>
<div className="mailpoet_actions">
<a
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') }
>
Set up
</a>
</div>
</li>
</ul>
</div>
);

View File

@ -0,0 +1,225 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
var intervalField = {
name: 'interval',
values: {
'daily': 'Once a day at...',
'weekly': 'Weekly on...',
'monthly': 'Monthly on the...',
'nthWeekDay': 'Monthly every...',
'immediately': 'Immediately.',
},
};
var SECONDS_IN_DAY = 86400;
var TIME_STEP_SECONDS = 3600; // Default: 3600
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
var timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
function(seconds) {
var date = new Date(null);
date.setSeconds(seconds);
var timeLabel = date.toISOString().substr(11, 5);
return [seconds, timeLabel];
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: 'Monday',
1: 'Tuesday',
2: 'Wednesday',
3: 'Thursday',
4: 'Friday',
5: 'Saturday',
6: 'Sunday',
},
};
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
var monthDayField = {
name: 'monthDay',
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var suffixes = {
0: 'st',
1: 'nd',
2: 'rd'
};
var suffix = suffixes[day] || 'th';
return [day, (day + 1).toString() + suffix];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'0': '1st',
'1': '2nd',
'2': '3rd',
'3': 'last',
},
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 0,
monthDay: 0,
nthWeekDay: 0,
};
},
handleIntervalChange: function(event) {
this.setState({
intervalType: event.target.value,
});
},
handleTimeOfDayChange: function(event) {
this.setState({
timeOfDay: event.target.value,
});
},
handleWeekDayChange: function(event) {
this.setState({
weekDay: event.target.value,
});
},
handleMonthDayChange: function(event) {
this.setState({
monthDay: event.target.value,
});
},
handleNthWeekDayChange: function(event) {
this.setState({
nthWeekDay: event.target.value,
});
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'notification',
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
var timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (this.state.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this.state}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (this.state.intervalType === 'weekly'
|| this.state.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this.state}
onValueChange={this.handleWeekDayChange} />
);
}
if (this.state.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this.state}
onValueChange={this.handleMonthDayChange} />
);
}
if (this.state.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this.state}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<h1>Post notifications</h1>
<Breadcrumb step="type" />
<Select
field={intervalField}
item={this.state}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@ -1,88 +1,78 @@
define('public', ['mailpoet', 'jquery', 'jquery-validation'],
function(MailPoet, $) {
'use strict';
define([
'mailpoet',
'jquery',
'parsleyjs'
],
function(
MailPoet,
jQuery,
Parsley
) {
jQuery(function($) {
function isSameDomain(url) {
var link = document.createElement('a');
link.href = url;
return (window.location.hostname === link.hostname);
}
function formatData(raw) {
var data = {};
$.each(raw, function(index, value) {
if(value.name.endsWith('[]')) {
var value_name = value.name.substr(0, value.name.length - 2);
// it's an array
if(data[value_name] === undefined) {
data[value_name] = [];
}
data[value_name].push(value.value);
} else {
data[value.name] = value.value;
}
});
return data;
}
$(function() {
// setup form validation
$('form.mailpoet_form').each(function() {
$(this).validate({
submitHandler: function(form) {
var data = $(form).serializeArray() || {};
var form = $(this);
// clear messages
$(form).find('.mailpoet_message').html('');
form.parsley().on('form:submit', function(parsley) {
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'save',
data: formatData(data),
onSuccess: function(response) {
if(response !== true) {
// errors
$.each(response, function(index, error) {
$(form)
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
$(form)
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
var data = form.serializeObject() || {};
// reset form
$(form).trigger('reset');
}
// clear messages
form.find('.mailpoet_message').html('');
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
return true;
} else {
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
endpoint: 'subscribers',
action: 'subscribe',
data: data
}).done(function(response) {
if(response.result !== true) {
// errors
$.each(response.errors, function(index, error) {
form
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
// display success message
form
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
}
});
}
return false;
// reset form
form.trigger('reset');
// reset validation
parsley.reset();
}
});
}
return false;
});
});
});
}
);
});
});

View File

@ -17,6 +17,11 @@ define(
name: 'name',
label: 'Name',
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'textarea'
}
];
@ -29,21 +34,27 @@ define(
}
};
var Link = Router.Link;
var SegmentForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Segment <Link className="add-new-h2" to="/">Back to list</Link>
Segment <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="segments"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
onSuccess={ this.history.goBack } />
</div>
);
}

View File

@ -1,85 +1,202 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames'
],
function(
React,
Router,
Listing,
classNames
) {
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'created_at',
label: 'Created on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
}
];
import React from 'react'
import { Router, Link } from 'react-router'
var bulk_actions = [
{
name: 'trash',
label: 'Trash'
}
];
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
var Link = Router.Link;
import Listing from 'listing/listing.jsx'
var SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ segment.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ segment.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="segments"
onRenderItem={this.renderItem}
columns={columns}
bulk_actions={ bulk_actions } />
</div>
);
}
});
return SegmentList;
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'description',
label: 'Description',
sortable: false
},
{
name: 'subscribed',
label: 'Subscribed',
sortable: false
},
{
name: 'unconfirmed',
label: 'Unconfirmed',
sortable: false
},
{
name: 'unsubscribed',
label: 'Unsubscribed',
sortable: false
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
);
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'duplicate_segment',
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
},
{
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
);
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Description">
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = SegmentList;

View File

@ -52,18 +52,26 @@ define(
var Link = Router.Link;
var SubscriberForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Subscriber <Link className="add-new-h2" to="/">Back to list</Link>
Subscriber <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="subscribers"
fields={ fields }
params={ this.props.params }
messages={ messages } />
messages={ messages }
onSuccess={ this.history.goBack } />
</div>
);
}

View File

@ -1,313 +1,294 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'form/fields/selection.jsx',
'classnames',
'mailpoet',
'jquery',
'select2'
],
function(
React,
Router,
Listing,
Selection,
classNames,
MailPoet,
jQuery
) {
var Link = Router.Link;
import React from 'react'
import { Router, Route, Link } from 'react-router'
var columns = [
{
name: 'email',
label: 'Email',
sortable: true
},
{
name: 'first_name',
label: 'Firstname',
sortable: true
},
{
name: 'last_name',
label: 'Lastname',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
import jQuery from 'jquery'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
import Listing from 'listing/listing.jsx'
import Selection from 'form/fields/selection.jsx'
var messages = {
onDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
const columns = [
{
name: 'email',
label: 'Subscriber',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'segments',
label: 'Lists',
sortable: false
},
if(count === 1) {
message = (
'1 subscriber was moved to the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', count);
}
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber was permanently deleted.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.subscribers;
var message = null;
if(count === 1) {
message = (
'1 subscriber has been restored from the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
var field = {
id: 'move_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
var field = {
id: 'add_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
var field = {
id: 'remove_from_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response.subscribers)
);
}
},
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
}
];
var SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
var row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
var status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
var segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
var row_actions = false;
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
{ actions }
</td>
<td className="column" data-colname="First name">
{ subscriber.first_name }
</td>
<td className="column" data-colname="Last name">
{ subscriber.last_name }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
messages={ messages }
/>
</div>
const messages = {
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', ~~response);
}
});
return SubscriberList;
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
);
};
const bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
onSelect: function() {
let field = {
id: 'move_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#move_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'addToList',
label: 'Add to list...',
onSelect: function() {
let field = {
id: 'add_to_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#add_to_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromList',
label: 'Remove from list...',
onSelect: function() {
let field = {
id: 'remove_from_segment',
endpoint: 'segments'
};
return (
<Selection field={ field }/>
);
},
getData: function() {
return {
segment_id: ~~(jQuery('#remove_from_segment').val())
}
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
}
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions',
'column-username'
);
let status = '';
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
break;
case 'unconfirmed':
status = 'Unconfirmed';
break;
case 'unsubscribed':
status = 'Unsubscribed';
break;
}
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
let avatar = false;
if(subscriber.avatar_url) {
avatar = (
<img
className="avatar"
src={ subscriber.avatar_url }
title=""
width="32"
height="32"
/>
);
}
return (
<div>
<td className={ row_classes }>
<strong><Link to={ `/edit/${ subscriber.id }` }>
{ subscriber.email }
</Link></strong>
<p style={{margin: 0}}>
{ subscriber.first_name } { subscriber.last_name }
</p>
{ actions }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
location={ this.props.location }
params={ this.props.params }
endpoint="subscribers"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
messages={ messages }
/>
</div>
)
}
});
module.exports = SubscriberList;

View File

@ -5,15 +5,15 @@ import SubscriberList from 'subscribers/list.jsx'
import SubscriberForm from 'subscribers/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
return this.props.children;
}
});
let container = document.getElementById('subscribers_container');
const container = document.getElementById('subscribers_container')
if(container) {
ReactDOM.render((

View File

@ -7,7 +7,8 @@
"sunra/php-simple-html-dom-parser": "*",
"tburry/pquery": "*",
"j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4"
"swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*"
},
"require-dev": {
"codeception/codeception": "*",

392
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "92704d2679fce692438b9e6f1dc6e02f",
"content-hash": "3297411fcec47a02bc4f456fbf3751d1",
"hash": "7d7ef94b6e40ac2b2d594e5832d7e16d",
"content-hash": "2e70c335edf7429df0794ebf49e2f210",
"packages": [
{
"name": "cerdic/css-tidy",
@ -160,16 +160,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.13",
"version": "v5.2.14",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3"
"reference": "e774bc9152de85547336e22b8926189e582ece95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/45df3a88f7f46071e10d0b600f228d19f95911b3",
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e774bc9152de85547336e22b8926189e582ece95",
"reference": "e774bc9152de85547336e22b8926189e582ece95",
"shasum": ""
},
"require": {
@ -180,7 +180,8 @@
"phpunit/phpunit": "4.7.*"
},
"suggest": {
"league/oauth2-client": "Needed for Gmail's XOAUTH2 authentication system"
"league/oauth2-client": "Needed for XOAUTH2 authentication",
"league/oauth2-google": "Needed for Gmail XOAUTH2"
},
"type": "library",
"autoload": {
@ -216,7 +217,95 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2015-09-14 09:18:12"
"time": "2015-11-01 10:15:28"
},
{
"name": "phpseclib/phpseclib",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "a74aa9efbe61430fcb60157c8e025a48ec8ff604"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/a74aa9efbe61430fcb60157c8e025a48ec8ff604",
"reference": "a74aa9efbe61430fcb60157c8e025a48ec8ff604",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phing/phing": "~2.7",
"phpunit/phpunit": "~4.0",
"sami/sami": "~2.0",
"squizlabs/php_codesniffer": "~2.0"
},
"suggest": {
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP < 5.0.0."
},
"type": "library",
"autoload": {
"psr-4": {
"phpseclib\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"phpseclib/"
],
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"time": "2015-08-04 04:48:03"
},
{
"name": "sunra/php-simple-html-dom-parser",
@ -368,16 +457,16 @@
},
{
"name": "twig/twig",
"version": "v1.22.3",
"version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540"
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ebfc36b7e77b0c1175afe30459cf943010245540",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"shasum": ""
},
"require": {
@ -390,7 +479,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.22-dev"
"dev-master": "1.23-dev"
}
},
"autoload": {
@ -425,7 +514,7 @@
"keywords": [
"templating"
],
"time": "2015-10-13 07:07:02"
"time": "2015-11-05 12:49:06"
}
],
"packages-dev": [
@ -545,16 +634,16 @@
},
{
"name": "codegyre/robo",
"version": "0.5.4",
"version": "0.6.0",
"source": {
"type": "git",
"url": "https://github.com/Codegyre/Robo.git",
"reference": "10aa223f6d1db182dc81d723bf1545dfc6ff380d"
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/10aa223f6d1db182dc81d723bf1545dfc6ff380d",
"reference": "10aa223f6d1db182dc81d723bf1545dfc6ff380d",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/d18185f0494c854d36aa5ee0ad931ee23bbef552",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552",
"shasum": ""
},
"require": {
@ -568,6 +657,7 @@
"require-dev": {
"codeception/aspect-mock": "0.5.*",
"codeception/base": "~2.1",
"codeception/codeception": "2.1",
"codeception/verify": "0.2.*",
"natxet/cssmin": "~3.0",
"patchwork/jsqueeze": "~1.0"
@ -592,7 +682,7 @@
}
],
"description": "Modern task runner",
"time": "2015-08-31 17:35:30"
"time": "2015-10-30 11:29:52"
},
{
"name": "doctrine/instantiator",
@ -650,16 +740,16 @@
},
{
"name": "facebook/webdriver",
"version": "1.0.2",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/facebook/php-webdriver.git",
"reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5"
"reference": "d843e33fd19b49db5ac9daaef2610079daab0bad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5",
"reference": "fe1bbbc5dde804d08a8593f1d9d0d3b05f5c84f5",
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/d843e33fd19b49db5ac9daaef2610079daab0bad",
"reference": "d843e33fd19b49db5ac9daaef2610079daab0bad",
"shasum": ""
},
"require": {
@ -689,7 +779,7 @@
"selenium",
"webdriver"
],
"time": "2015-08-12 20:21:31"
"time": "2015-11-01 20:09:34"
},
{
"name": "guzzlehttp/guzzle",
@ -806,16 +896,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e"
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"reference": "4ef919b0cf3b1989523138b60163bbcb7ba1ff7e",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982",
"shasum": ""
},
"require": {
@ -860,19 +950,19 @@
"stream",
"uri"
],
"time": "2015-08-15 19:32:36"
"time": "2015-11-03 01:34:55"
},
{
"name": "henrikbjorn/lurker",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/henrikbjorn/Lurker.git",
"url": "https://github.com/flint/Lurker.git",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/henrikbjorn/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419",
"url": "https://api.github.com/repos/flint/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419",
"shasum": ""
},
@ -901,18 +991,16 @@
],
"authors": [
{
"name": "Henrik Bjornskov",
"email": "henrik@bjrnskov.dk",
"homepage": "http://henrik.bjrnskov.dk"
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
},
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
"email": "ever.zet@gmail.com"
},
{
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
"name": "Henrik Bjrnskov",
"email": "henrik@bjrnskov.dk"
}
],
"description": "Resource Watcher.",
@ -1873,16 +1961,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
"reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"shasum": ""
},
"require": {
@ -1891,8 +1979,7 @@
},
"require-dev": {
"symfony/css-selector": "~2.0,>=2.0.5",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.0,>=2.0.5"
"symfony/process": "~2.3.34|~2.7,>=2.7.6"
},
"suggest": {
"symfony/process": ""
@ -1924,29 +2011,26 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2015-09-06 08:36:38"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/config",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61"
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61",
"reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61",
"url": "https://api.github.com/repos/symfony/config/zipball/831f88908b51b9ce945f5e6f402931d1ac544423",
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -1974,20 +2058,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2015-09-21 15:02:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/console",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161"
"reference": "5efd632294c8320ea52492db22292ff853a43766"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161",
"url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
"reference": "5efd632294c8320ea52492db22292ff853a43766",
"shasum": ""
},
"require": {
@ -1996,7 +2080,6 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"suggest": {
@ -2031,28 +2114,25 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-09-25 08:32:23"
"time": "2015-10-20 14:38:46"
},
{
"name": "symfony/css-selector",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "abe19cc0429a06be0c133056d1f9859854860970"
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
"reference": "abe19cc0429a06be0c133056d1f9859854860970",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e1b865b26be4a56d22a8dee398375044a80c865b",
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2084,28 +2164,27 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/dom-crawler",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "2e185ca136399f902b948694987e62c80099c052"
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
"reference": "2e185ca136399f902b948694987e62c80099c052",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5fef7d8b80d8f9992df99d8ee283f420484c9612",
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/css-selector": "~2.3",
"symfony/phpunit-bridge": "~2.7"
"symfony/css-selector": "~2.3"
},
"suggest": {
"symfony/css-selector": ""
@ -2137,20 +2216,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2015-09-20 21:13:58"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9"
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"shasum": ""
},
"require": {
@ -2161,7 +2240,6 @@
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/phpunit-bridge": "~2.7",
"symfony/stopwatch": "~2.3"
},
"suggest": {
@ -2195,28 +2273,25 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/filesystem",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
"reference": "56fd6df73be859323ff97418d97edc1d756df6df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
"reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/56fd6df73be859323ff97418d97edc1d756df6df",
"reference": "56fd6df73be859323ff97418d97edc1d756df6df",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2244,28 +2319,25 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2015-09-09 17:42:36"
"time": "2015-10-18 20:23:18"
},
{
"name": "symfony/finder",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "8262ab605973afbb3ef74b945daabf086f58366f"
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
"reference": "8262ab605973afbb3ef74b945daabf086f58366f",
"url": "https://api.github.com/repos/symfony/finder/zipball/2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2293,20 +2365,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2015-09-19 19:59:23"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/form",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "d4a990d2ebe4dd39cac52c5a40a5aac84b12b237"
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/d4a990d2ebe4dd39cac52c5a40a5aac84b12b237",
"reference": "d4a990d2ebe4dd39cac52c5a40a5aac84b12b237",
"url": "https://api.github.com/repos/symfony/form/zipball/b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"shasum": ""
},
"require": {
@ -2325,7 +2397,6 @@
"doctrine/collections": "~1.0",
"symfony/http-foundation": "~2.2",
"symfony/http-kernel": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/security-csrf": "~2.4",
"symfony/translation": "~2.0,>=2.0.5",
"symfony/validator": "~2.6,>=2.6.8"
@ -2363,28 +2434,27 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/intl",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "35f902b232c10056e17d94a842160d44bb540838"
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/35f902b232c10056e17d94a842160d44bb540838",
"reference": "35f902b232c10056e17d94a842160d44bb540838",
"url": "https://api.github.com/repos/symfony/intl/zipball/330f52a996749eb6a2fdc1506c7a4868e070d678",
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/filesystem": "~2.1",
"symfony/phpunit-bridge": "~2.7"
"symfony/filesystem": "~2.1"
},
"suggest": {
"ext-intl": "to use the component with locales other than \"en\""
@ -2438,28 +2508,25 @@
"l10n",
"localization"
],
"time": "2015-09-09 17:53:06"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/options-resolver",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e"
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e",
"reference": "75389f6f948edfdf0c0ebdbe00c4f84ab5d1a03e",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85fd10e551677d3c9a4632def78b8ec4670b247d",
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2492,28 +2559,25 @@
"configuration",
"options"
],
"time": "2015-09-25 06:59:16"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/process",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9"
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/b27c8e317922cd3cdd3600850273cf6b82b2e8e9",
"reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9",
"url": "https://api.github.com/repos/symfony/process/zipball/4a959dd4e19c2c5d7512689413921e0a74386ec7",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2541,28 +2605,25 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2015-09-19 19:59:23"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/property-access",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c"
"reference": "368b784738fa932e6d86866038312b03e073a824"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c",
"reference": "f8ea7aa472f0e3f8cdf43287caa72a70ff5c088c",
"url": "https://api.github.com/repos/symfony/property-access/zipball/368b784738fa932e6d86866038312b03e073a824",
"reference": "368b784738fa932e6d86866038312b03e073a824",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2601,20 +2662,20 @@
"property path",
"reflection"
],
"time": "2015-08-24 07:13:45"
"time": "2015-10-23 14:47:27"
},
{
"name": "symfony/routing",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "6c5fae83efa20baf166fcf4582f57094e9f60f16"
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/6c5fae83efa20baf166fcf4582f57094e9f60f16",
"reference": "6c5fae83efa20baf166fcf4582f57094e9f60f16",
"url": "https://api.github.com/repos/symfony/routing/zipball/f353e1f588679c3ec987624e6c617646bd01ba38",
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38",
"shasum": ""
},
"require": {
@ -2630,7 +2691,6 @@
"symfony/config": "~2.7",
"symfony/expression-language": "~2.4",
"symfony/http-foundation": "~2.3",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.0,>=2.0.5"
},
"suggest": {
@ -2672,20 +2732,20 @@
"uri",
"url"
],
"time": "2015-09-14 14:14:09"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/translation",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "485877661835e188cd78345c6d4eef1290d17571"
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/485877661835e188cd78345c6d4eef1290d17571",
"reference": "485877661835e188cd78345c6d4eef1290d17571",
"url": "https://api.github.com/repos/symfony/translation/zipball/6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"shasum": ""
},
"require": {
@ -2698,7 +2758,6 @@
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.2"
},
"suggest": {
@ -2733,20 +2792,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2015-09-06 08:36:38"
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/twig-bridge",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
"reference": "bce37975610a46bde48dbf2f67f724401251d199"
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bce37975610a46bde48dbf2f67f724401251d199",
"reference": "bce37975610a46bde48dbf2f67f724401251d199",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"shasum": ""
},
"require": {
@ -2758,10 +2817,9 @@
"symfony/console": "~2.7",
"symfony/expression-language": "~2.4",
"symfony/finder": "~2.3",
"symfony/form": "~2.7,>=2.7.2",
"symfony/form": "~2.7,>=2.7.6",
"symfony/http-kernel": "~2.3",
"symfony/intl": "~2.3",
"symfony/phpunit-bridge": "~2.7",
"symfony/routing": "~2.2",
"symfony/security": "~2.6",
"symfony/security-acl": "~2.6",
@ -2812,28 +2870,25 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
"time": "2015-09-23 09:17:11"
"time": "2015-10-11 09:39:48"
},
{
"name": "symfony/yaml",
"version": "v2.7.5",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770"
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2861,7 +2916,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-09-14 14:14:09"
"time": "2015-10-11 09:39:48"
},
{
"name": "twig/extensions",
@ -2966,25 +3021,30 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v2.0.1",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "91064290f5b53a09bdff1b939d7f69fb0e7531b5"
"reference": "c10040e0df17d2ee88e9212b50cbe9319e878f59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/91064290f5b53a09bdff1b939d7f69fb0e7531b5",
"reference": "91064290f5b53a09bdff1b939d7f69fb0e7531b5",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/c10040e0df17d2ee88e9212b50cbe9319e878f59",
"reference": "c10040e0df17d2ee88e9212b50cbe9319e878f59",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
"php": ">=5.3.9"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
@ -3008,7 +3068,7 @@
"env",
"environment"
],
"time": "2015-05-30 16:15:01"
"time": "2015-10-28 18:53:35"
}
],
"aliases": [],

View File

@ -39,6 +39,7 @@ class Initializer {
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields';
@ -50,6 +51,7 @@ class Initializer {
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);

View File

@ -2,6 +2,9 @@
namespace MailPoet\Config;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Form;
use \MailPoet\Form\Block;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Settings\Hosts;
use \MailPoet\Settings\Pages;
use \MailPoet\Settings\Charsets;
@ -41,6 +44,14 @@ class Menu {
'mailpoet-newsletters',
array($this, 'newsletters')
);
add_submenu_page(
'mailpoet',
__('Forms'),
__('Forms'),
'manage_options',
'mailpoet-forms',
array($this, 'forms')
);
add_submenu_page(
'mailpoet',
__('Subscribers'),
@ -79,8 +90,8 @@ class Menu {
function registered_pages() {
global $_registered_pages;
$pages = array(
//'mailpoet-form-editor' => 'formEditor',
'mailpoet-newsletter-editor' => array($this, 'newletterForm')
'mailpoet-form-editor' => array($this, 'formEditor'),
'mailpoet-newsletter-editor' => array($this, 'newletterEditor')
);
foreach($pages as $menu_slug => $callback) {
$hookname = get_plugin_page_hookname($menu_slug, null);
@ -163,6 +174,13 @@ class Menu {
echo $this->renderer->render('segments.html', $data);
}
function forms() {
$data = array();
$data['segments'] = Segment::findArray();
echo $this->renderer->render('forms.html', $data);
}
function newsletters() {
global $wp_roles;
@ -178,11 +196,30 @@ class Menu {
echo $this->renderer->render('newsletters.html', $data);
}
function newletterForm() {
function newletterEditor() {
$data = array();
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'));
echo $this->renderer->render('newsletter/form.html', $data);
}
function formEditor() {
$id = (isset($_GET['id']) ? (int)$_GET['id'] : 0);
$form = Form::findOne($id);
if($form !== false) {
$form = $form->asArray();
}
$data = array(
'form' => $form,
'pages' => Pages::getAll(),
'segments' => Segment::findArray(),
'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats()
);
echo $this->renderer->render('form/editor.html', $data);
}
}

View File

@ -21,6 +21,7 @@ class Migrator {
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'forms'
);
}
@ -66,7 +67,7 @@ class Migrator {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value varchar(255) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
@ -107,6 +108,7 @@ class Migrator {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
@ -123,7 +125,8 @@ class Migrator {
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -135,7 +138,8 @@ class Migrator {
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -145,6 +149,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
@ -192,6 +197,21 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function forms() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'body longtext,',
'settings longtext,',
'styles longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
private function sqlify($model, $attributes) {
$table = $this->prefix . $model;

View File

@ -60,6 +60,27 @@ class Populator {
'name' => 'afterTimeType',
'newsletter_type' => 'welcome',
),
array(
'name' => 'intervalType',
'newsletter_type' => 'notification',
),
array(
'name' => 'timeOfDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'weekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'monthDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
);
}

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -12,16 +13,39 @@ class Widget {
add_action('widgets_init', array($this, 'registerWidget'));
if(!is_admin()) {
add_action('widgets_init', array($this, 'setupActions'));
//$this->setupActions();
add_action('widgets_init', array($this, 'setupDependencies'));
} else {
add_action('widgets_init', array($this, 'setupAdminDependencies'));
}
}
function registerWidget() {
register_widget('\MailPoet\Form\Widget');
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
}
function getSubscribersCount($params) {
return Subscriber::filter('subscribed')->count();
}
function setupDependencies() {
wp_enqueue_style('mailpoet_public', Env::$assets_url.'/css/public.css');
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_public',
Env::$assets_url.'/js/public.js',
array(),
@ -36,6 +60,22 @@ class Widget {
));
}
function setupAdminDependencies() {
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_admin',
Env::$assets_url.'/js/mailpoet.js',
array(),
Env::$version,
true
);
}
function setupActions() {
// ajax requests
add_action(
@ -55,9 +95,9 @@ class Widget {
'admin_post_mailpoet_form_subscribe',
'mailpoet_form_subscribe'
);
/*add_action(
add_action(
'init',
'mailpoet_form_subscribe'
);*/
);
}
}
}

103
lib/Form/Block/Base.php Normal file
View File

@ -0,0 +1,103 @@
<?php
namespace MailPoet\Form\Block;
abstract class Base {
protected static function getInputValidation($block) {
$rules = array();
if($block['id'] === 'email') {
$rules['required'] = true;
$rules['error-message'] = __('You need to specify a valid email address');
}
if($block['id'] === 'segments') {
$rules['required'] = true;
$rules['mincheck'] = 1;
$rules['error-message'] = __('You need to select a list');
}
if(!empty($block['params']['required'])) {
$rules['required'] = true;
}
if(!empty($block['params']['validate'])) {
if($block['params']['validate'] === 'phone') {
$rules['pattern'] = "^[\d\+\-\.\(\)\/\s]*$";
$rules['error-message'] = __('You need to specify a valid phone number');
} else {
$rules['type'] = $block['params']['validate'];
}
}
$validation = '';
if(!empty($rules)) {
$rules = array_unique($rules);
foreach($rules as $rule => $value) {
if(is_bool($value)) {
$value = ($value) ? 'true' : 'false';
}
$validation .= 'data-parsley-'.$rule.'="'.$value.'"';
}
}
return $validation;
}
protected static function renderLabel($block) {
$html = '';
if(
isset($block['params']['label_within'])
&& $block['params']['label_within']
) {
return $html;
}
if(isset($block['params']['label'])
&& strlen(trim($block['params']['label'])) > 0) {
$html .= '<label class="mailpoet_'.$block['type'].'_label">';
$html .= $block['params']['label'];
if(isset($block['params']['required']) && $block['params']['required']) {
$html .= ' <span class="mailpoet_required">*</span>';
}
$html .= '</label>';
}
return $html;
}
protected static function renderInputPlaceholder($block) {
$html = '';
// if the label is displayed as a placeholder,
if(
isset($block['params']['label_within'])
&& $block['params']['label_within']
) {
// display only label
$html .= ' placeholder="';
$html .= static::getFieldLabel($block);
// add an asterisk if it's a required field
if(isset($block['params']['required']) && $block['params']['required']) {
$html .= ' *';
}
$html .= '" ';
}
return $html;
}
// return field name depending on block data
protected static function getFieldName($block = array()) {
return $block['id'];
}
protected static function getFieldLabel($block = array()) {
return (isset($block['params']['label'])
&& strlen(trim($block['params']['label'])) > 0)
? trim($block['params']['label']) : '';
}
protected static function getFieldValue($block = array()) {
return (isset($block['params']['value'])
&& strlen(trim($block['params']['value'])) > 0)
? trim($block['params']['value']) : '';
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace MailPoet\Form\Block;
class Checkbox extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) {
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'" ';
$html .= 'value="1" ';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />'.$option['value'];
$html .= '</label>';
}
$html .= '</p>';
return $html;
}
}

155
lib/Form/Block/Date.php Normal file
View File

@ -0,0 +1,155 @@
<?php
namespace MailPoet\Form\Block;
class Date extends Base {
static function render($block) {
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= static::renderDateSelect($block);
$html .= '</p>';
return $html;
}
private static function renderDateSelect($block = array()) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$date_formats = static::getDateFormats();
// automatically select first date format
$date_format = $date_formats[$block['params']['date_type']][0];
// set date format if specified
if(isset($block['params']['date_format'])
&& strlen(trim($block['params']['date_format'])) > 0) {
$date_format = $block['params']['date_format'];
}
// generate an array of selectors based on date format
$date_selectors = explode('/', $date_format);
foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') {
$html .= '<select class="mailpoet_date_day" ';
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block);
$html .= '</select>';
} else if($date_selector === 'mm') {
$html .= '<select class="mailpoet_date_month" ';
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block);
$html .= '</select>';
} else if($date_selector === 'yyyy') {
$html .= '<select class="mailpoet_date_year" ';
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block);
$html .= '</select>';
}
}
return $html;
}
static function getDateTypes() {
return array(
'year_month_day' => __('Year, month, day'),
'year_month' => __('Year, month'),
'month' => __('Month (January, February,...)'),
'year' => __('Year')
);
}
static function getDateFormats() {
return array(
'year_month_day' => array('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy/mm/dd'),
'year_month' => array('mm/yyyy', 'yyyy/mm'),
'year' => array('yyyy'),
'month' => array('mm')
);
}
static function getMonthNames() {
return array(__('January'), __('February'), __('March'), __('April'),
__('May'), __('June'), __('July'), __('August'), __('September'),
__('October'), __('November'), __('December')
);
}
static function getMonths($block = array()) {
$defaults = array(
'selected' => null
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%m');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$month_names = static::getMonthNames();
$html = '';
for($i = 1; $i < 13; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>';
$html .= $month_names[$i - 1];
$html .= '</option>';
}
return $html;
}
static function getYears($block = array()) {
$defaults = array(
'selected' => null,
'from' => (int)strftime('%Y') - 100,
'to' => (int)strftime('%Y')
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%Y');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$html = '';
// return years as an array
for($i = (int)$block['to']; $i > (int)($block['from'] - 1); $i--) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>'.$i.'</option>';
}
return $html;
}
static function getDays($block = array()) {
$defaults = array(
'selected' => null
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%d');
}
// merge block with defaults
$block = array_merge($defaults, $block);
$html = '';
// return days as an array
for($i = 1; $i < 32; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>'.$i.'</option>';
}
return $html;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace MailPoet\Form\Block;
class Divider {
static function render() {
return '<hr class="mailpoet_divider" />';
}
}

23
lib/Form/Block/Html.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace MailPoet\Form\Block;
class Html {
static function render($block) {
$html = '';
if(isset($block['params']['text']) && $block['params']['text']) {
$text = html_entity_decode($block['params']['text'], ENT_QUOTES);
}
if(isset($block['params']['nl2br']) && $block['params']['nl2br']) {
$text = nl2br($text);
}
$html .= '<p class="mailpoet_paragraph">';
$html .= $text;
$html .= '</p>';
return $html;
}
}

36
lib/Form/Block/Input.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace MailPoet\Form\Block;
class Input extends Base {
static function render($block) {
$type = 'text';
if($block['id'] === 'email') {
$type = 'email';
}
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= '<input type="'.$type.'" class="mailpoet_input" ';
$html .= 'name="'.static::getFieldName($block).'" ';
$html .= 'title="'.static::getFieldLabel($block).'" ';
$html .= 'value="'.static::getFieldValue($block).'" ';
$html .= static::renderInputPlaceholder($block);
$html .= static::getInputValidation($block);
$html .= '/>';
$html .= '</p>';
return $html;
}
}

42
lib/Form/Block/Radio.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace MailPoet\Form\Block;
class Radio extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) {
$html .= '<label class="mailpoet_radio_label">';
$html .= '<input type="radio" class="mailpoet_radio" ';
$html .= 'name="'.$field_name.'" ';
$html .= 'value="'.$option['value'].'" ';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />&nbsp;'.$option['value'];
$html .= '</label>';
}
$html .= '</p>';
return $html;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace MailPoet\Form\Block;
class Segment extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
if(!empty($block['params']['values'])) {
// display values
foreach($block['params']['values'] as $segment) {
if(!isset($segment['id']) || !isset($segment['name'])) continue;
$is_checked = (isset($segment['is_checked']) && $segment['is_checked']) ? 'checked="checked"' : '';
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$segment['id'].'" '.$is_checked.' ';
$html .= $field_validation;
$html .= ' />'.$segment['name'];
$html .= '</label>';
}
}
$html .= '</p>';
return $html;
}
}

36
lib/Form/Block/Select.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace MailPoet\Form\Block;
class Select extends Base {
static function render($block) {
$html = '';
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within'])
&& $block['params']['label_within']) {
$html .= '<option value="">'.static::getFieldLabel($block).'</option>';
}
foreach($block['params']['values'] as $option) {
$is_selected = (isset($option['is_checked']) && $option['is_checked'])
? 'selected="selected"' : '';
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>';
$html .= $option['value'];
$html .= '</option>';
}
$html .= '</select>';
$html .= '</p>';
return $html;
}
}

17
lib/Form/Block/Submit.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace MailPoet\Form\Block;
class Submit extends Base {
static function render($block) {
$html = '';
$html .= '<input class="mailpoet_submit" type="submit" ';
$html .= 'value="'.static::getFieldLabel($block).'" ';
$html .= '/>';
return $html;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace MailPoet\Form\Block;
class Textarea extends Base {
static function render($block) {
$html = '';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$lines = (isset($block['params']['lines']) ? (int)$block['params']['lines'] : 1);
$html .= '<textarea class="mailpoet_textarea" rows="'.$lines.'" ';
$html .= 'name="'.static::getFieldName($block).'"';
$html .= static::renderInputPlaceholder($block);
$html .= static::getInputValidation($block);
$html .= '></textarea>';
$html .= '</p>';
return $html;
}
}

95
lib/Form/Renderer.php Normal file
View File

@ -0,0 +1,95 @@
<?php
namespace MailPoet\Form;
use MailPoet\Form\Block;
use MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Renderer {
// public: rendering method
static function render($form = array()) {
$html = static::renderStyles($form);
$html .= static::renderHTML($form);
return $html;
}
static function renderStyles($form = array()) {
$html = '<style type="text/css">';
$html .= static::getStyles($form);
$html .= '</style>';
return $html;
}
static function renderHTML($form = array()) {
if(isset($form['body']) && !empty($form['body'])) {
return static::renderBlocks($form['body']);
}
return '';
}
static function getStyles($form = array()) {
if(isset($form['styles'])
&& strlen(trim($form['styles'])) > 0) {
return strip_tags($form['styles']);
} else {
return Util\Styles::$defaults;
}
}
// private: rendering methods
private static function renderBlocks($blocks = array()) {
$html = '';
foreach ($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n";
}
return $html;
}
private static function renderBlock($block = array()) {
$html = '';
switch ($block['type']) {
case 'html':
$html .= Block\Html::render($block);
break;
case 'divider':
$html .= Block\Divider::render();
break;
case 'checkbox':
$html .= Block\Checkbox::render($block);
break;
case 'radio':
$html .= Block\Radio::render($block);
break;
case 'segment':
$html .= Block\Segment::render($block);
break;
case 'date':
$html .= Block\Date::render($block);
break;
case 'select':
$html .= Block\Select::render($block);
break;
case 'input':
$html .= Block\Input::render($block);
break;
case 'textarea':
$html .= Block\Textarea::render($block);
break;
case 'submit':
$html .= Block\Submit::render($block);
break;
}
return $html;
}
}

91
lib/Form/Util/Export.php Normal file
View File

@ -0,0 +1,91 @@
<?php
namespace MailPoet\Form\Util;
use MailPoet\Form\Widget;
class Export {
static function getAll($form = null) {
return array(
'html' => static::get('html', $form),
'php' => static::get('php', $form),
'iframe' => static::get('iframe', $form),
'shortcode' => static::get('shortcode', $form),
);
}
static function get($type = 'html', $form = null) {
switch($type) {
case 'iframe':
// generate url to load iframe's content
$iframe_url = add_query_arg(array(
'mailpoet_page' => 'mailpoet_form_iframe',
'mailpoet_form' => $form['id']
), site_url());
// generate iframe
return '<iframe '.
'width="100%" '.
'scrolling="no" '.
'frameborder="0" '.
'src="'.$iframe_url.'" '.
'class="mailpoet_form_iframe" '.
'vspace="0" '.
'tabindex="0" '.
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"'.
'marginwidth="0" '.
'marginheight="0" '.
'hspace="0" '.
'allowtransparency="true"></iframe>';
break;
case 'php':
$output = array(
'$form_widget = new \MailPoet\Form\Widget();',
'echo $form_widget->widget(array(\'form\' => '.(int)$form['id'].', \'form_type\' => \'php\'));'
);
return join("\n", $output);
break;
case 'html':
// TODO: get locale setting in order to load translations
$wp_locale = \get_locale();
$output = array();
$output[] = '<!-- BEGIN Scripts : you should place them in the header of your theme -->';
// jQuery
$output[] = '<script type="text/javascript" src="'.includes_url().'js/jquery/jquery.js'.'?mpv='.MAILPOET_VERSION.'"></script>';
// (JS) form validation
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'lib/jquery.validationEngine.js?mpv='.MAILPOET_VERSION).'"></script>';
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'lib/jquery.validationEngine-en.js?mpv='.MAILPOET_VERSION).'"></script>';
// (CSS) form validation styles
$output[] = '<link rel="stylesheet" type="text/css" href="'.plugins_url('wysija-newsletters/'.'lib/validationEngine.jquery.css?mpv='.MAILPOET_VERSION).'">';
// (JS) form submission
$output[] = '<script type="text/javascript" src="'.plugins_url('wysija-newsletters/'.'www/mailpoet_form_subscribe.js?mpv='.MAILPOET_VERSION).'"></script>';
// (JS) variables...
$output[] = '<script type="text/javascript">';
$output[] = ' var MailPoetData = MailPoetData || {';
$output[] = ' is_rtl: '.((int)is_rtl()).",";
$output[] = ' ajax_url: "'.admin_url('admin-ajax.php').'"';
$output[] = ' };';
$output[] = '</script>';
$output[] = '<!--END Scripts-->';
$form_widget = new Widget();
$output[] = $form_widget->widget(array(
'form' => (int)$form['id'],
'form_type' => 'php'
));
return join("\n", $output);
break;
case 'shortcode':
return '[mailpoet_form id="'.(int)$form['id'].'"]';
break;
}
}
}

181
lib/Form/Util/Styles.php Normal file
View File

@ -0,0 +1,181 @@
<?php
namespace MailPoet\Form\Util;
class Styles {
private $_stylesheet = null;
private $_styles = array();
static $defaults =<<<EOL
/* form */
.mailpoet_form {
}
/* paragraphs (label + input) */
.mailpoet_paragraph {
}
/* labels */
.mailpoet_input_label,
.mailpoet_textarea_label,
.mailpoet_select_label,
.mailpoet_radio_label,
.mailpoet_checkbox_label,
.mailpoet_list_label,
.mailpoet_date_label {
display:block;
}
/* inputs */
.mailpoet_input,
.mailpoet_textarea,
.mailpoet_select,
.mailpoet_date {
display:block;
}
.mailpoet_checkbox {
display:inline;
margin-right: 5px;
vertical-align:middle;
}
.mailpoet_validate_success {
color:#468847;
}
.mailpoet_validate_error {
color:#B94A48;
}
EOL;
function __construct($stylesheet = null) {
// store raw styles
$this->setStylesheet($stylesheet);
// extract rules/properties
$this->parseStyles();
return $this;
}
function render($prefix = '') {
$styles = $this->getStyles();
if(!empty($styles)) {
$output = array();
// add prefix on each selector
foreach($styles as $style) {
// check if selector is an array
if(is_array($style['selector'])) {
$selector = join(",\n", array_map(function($value) use ($prefix) {
return $prefix.' '.$value;
}, $style['selector']));
} else {
$selector = $prefix.' '.$style['selector'];
}
// format selector
$output[] = $selector . ' {';
// format rules
if(!empty($style['rules'])) {
$rules = join("\n", array_map(function($rule) {
return "\t".$rule['property'] . ': ' . $rule['value'].';';
}, $style['rules']));
$output[] = $rules;
}
$output[] = '}';
}
return join("\n", $output);
}
}
private function setStylesheet($stylesheet) {
$this->_stylesheet = $this->stripComments($stylesheet);
return $this;
}
private function stripComments($stylesheet) {
// remove comments
return preg_replace('!/\*.*?\*/!s', '', $stylesheet);
}
private function getStylesheet() {
return $this->_stylesheet;
}
private function setStyles($styles) {
$this->_styles = $styles;
return $this;
}
private function getStyles() {
return $this->_styles;
}
private function parseStyles() {
if($this->getStylesheet() !== null) {
// extract selectors and rules
preg_match_all( '/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\}]*)\}/',
$this->getStylesheet(),
$matches
);
$selectors = $matches[1];
$rules = $matches[2];
// extracted styles
$styles = array();
// loop through each selector
foreach($selectors as $index => $selector) {
// trim selector
$selector = trim($selector);
// get selector rules
$selector_rules = array_filter(array_map(function($value) {
if(strlen(trim($value)) > 0) {
// split property / value
$pair = explode(':', trim($value));
if(isset($pair[0]) && isset($pair[1])) {
return array(
'property' => $pair[0],
'value' => $pair[1]
);
}
}
}, explode(';', trim($rules[$index]))));
// check if we have multiple selectors
if(strpos($selector, ',') !== FALSE) {
$selectors_array = array_filter(array_map(function($value) {
return trim($value);
}, explode(',', $selector)));
// multiple selectors
$styles[$index] = array(
'selector' => $selectors_array,
'rules' => $selector_rules
);
} else {
// it's a single selector
$styles[$index] = array(
'selector' => $selector,
'rules' => $selector_rules
);
}
}
$this->setStyles($styles);
}
}
function __toString() {
$this->stripComments();
return $this->render();
}
}

View File

@ -1,120 +1,310 @@
<?php
namespace MailPoet\Form;
use \MailPoet\Config\Renderer;
use \MailPoet\Models\Form;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Widget extends \WP_Widget {
function __construct () {
// add_action(
// 'wp_ajax_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'wp_ajax_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'init',
// array($this, 'subscribe')
// );
function __construct() {
return parent::__construct(
'mailpoet_form',
__('MailPoet Subscription Form'),
__("MailPoet Subscription Form"),
array(
'title' => __('Newsletter subscription form')
'title' => __("Newsletter subscription form"),
)
);
}
public function update($new_instance, $old_instance) {
/**
* Save the new widget's title.
*/
public function update( $new_instance, $old_instance ) {
$instance = $old_instance;
$instance['title'] = strip_tags($new_instance['title']);
$instance['form'] = (int)$new_instance['form'];
return $instance;
}
/**
* Output the widget's option form.
*/
public function form($instance) {
$instance = wp_parse_args(
(array)$instance,
array(
'title' => __('Subscribe to our Newsletter')
'title' => __("Subscribe to our Newsletter")
)
);
// set title
$title = isset($instance['title']) ? strip_tags($instance['title']) : '';
$output = '';
// set form
$selected_form = isset($instance['form']) ? (int)($instance['form']) : 0;
$output .= '<p>';
$output .= ' <label for="'.$this->get_field_id('title').'">';
$output .= __('Title:' );
$output .= ' </label>';
$output .= ' <input type="text" class="widefat"';
$output .= ' id="'.$this->get_field_id('title').'"';
$output .= ' name="'.$this->get_field_name('title').'"';
$output .= ' value="'.esc_attr($title).'"';
$output .= ' />';
$output .= '</p>';
$output .= '<p>';
$output .= ' <a href="javascript:;" class="mailpoet_form_new">';
$output .= __('Create a new form');
$output .= ' </a>';
$output .= '</p>';
echo $output;
// get forms list
$forms = Form::whereNull('deleted_at')->orderByAsc('name')->findArray();
?><p>
<label for="<?php $this->get_field_id( 'title' ) ?>"><?php _e( 'Title:' ); ?></label>
<input
type="text"
class="widefat"
id="<?php echo $this->get_field_id('title') ?>"
name="<?php echo $this->get_field_name('title'); ?>"
value="<?php echo esc_attr($title); ?>"
/>
</p>
<p>
<select class="widefat" id="<?php echo $this->get_field_id('form') ?>" name="<?php echo $this->get_field_name('form'); ?>">
<?php
foreach ($forms as $form) {
$is_selected = ($selected_form === (int)$form['id']) ? 'selected="selected"' : '';
?>
<option value="<?php echo (int)$form['id']; ?>" <?php echo $is_selected; ?>><?php echo esc_html($form['name']); ?></option>
<?php } ?>
</select>
</p>
<p>
<a href="javascript:;" class="mailpoet_form_new"><?php _e("Create a new form"); ?></a>
</p>
<script type="text/javascript">
jQuery(function($) {
$(function() {
$('.mailpoet_form_new').on('click', function() {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
}
});
});
});
});
</script>
<?php
}
/**
* Output the widget itself.
*/
function widget($args, $instance = null) {
// turn $args into variables
extract($args);
if($instance === null) { $instance = $args; }
if($instance === null) {
$instance = $args;
}
$title = apply_filters(
'widget_title',
$instance['title'],
!empty($instance['title']) ? $instance['title'] : '',
$instance,
$this->id_base
);
$form_id = $this->id_base.'_'.$this->number;
$form_type = 'widget';
// get form
$form = Form::whereNull('deleted_at')->findOne($instance['form']);
$output = '';
// if the form was not found, return nothing.
if($form === false) {
return '';
} else {
$form = $form->asArray();
$form_type = 'widget';
if(isset($instance['form_type']) && in_array(
$instance['form_type'],
array('html', 'php', 'iframe', 'shortcode')
)) {
$form_type = $instance['form_type'];
}
// before widget
$output .= (isset($before_widget) ? $before_widget : '');
$settings = (isset($form['settings']) ? $form['settings'] : array());
$body = (isset($form['body']) ? $form['body'] : array());
$output = '';
// title
$output .= $before_title.$title.$after_title;
if(!empty($body)) {
$data = array(
'form_id' => $this->id_base.'_'.$this->number,
'form_type' => $form_type,
'form' => $form,
'title' => $title,
'styles' => FormRenderer::renderStyles($form),
'html' => FormRenderer::renderHTML($form),
'before_widget' => (!empty($before_widget) ? $before_widget : ''),
'after_widget' => (!empty($after_widget) ? $after_widget : ''),
'before_title' => (!empty($before_title) ? $before_title : ''),
'after_title' => (!empty($after_title) ? $after_title : '')
);
// container
$output .= '<div class="mailpoet_form mailpoet_form_'.$form_type.'">';
// if(isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] === $form['id']) {
// // form messages (success / error)
// $output .= '<div class="mailpoet_message">';
// // success message
// if(isset($_GET['mailpoet_success'])) {
// $output .= '<p class="mailpoet_validate_success">'.strip_tags(urldecode($_GET['mailpoet_success']), '<a><strong><em><br><p>').'</p>';
// }
// // error message
// if(isset($_GET['mailpoet_error'])) {
// $output .= '<p class="mailpoet_validate_error">'.strip_tags(urldecode($_GET['mailpoet_error']), '<a><strong><em><br><p>').'</p>';
// }
// $output .= '</div>';
// } else {
// $output .= '<div class="mailpoet_message"></div>';
// }
// styles
$styles = '.mailpoet_validate_success { color:#468847; }';
$styles .= '.mailpoet_validate_error { color:#B94A48; }';
$output .= '<style type="text/css">'.$styles.'</style>';
// render form
$renderer = new Renderer();
$renderer = $renderer->init();
$output = $renderer->render('form/widget.html', $data);
$output = do_shortcode($output);
}
$output .= '<form '.
'id="'.$form_id.'" '.
'method="post" '.
'action="'.admin_url('admin-post.php?action=mailpoet_form_subscribe').'" '.
'class="mailpoet_form mailpoet_form_'.$form_type.'" novalidate>';
$output .= '<div class="mailpoet_message"></div>';
$output .= ' <p>';
$output .= ' <label>'.__('E-mail');
$output .= ' <input type="email" name="email"';
$output .= ' data-rule-required="true"';
$output .= ' data-rule-email="true"';
$output .= ' data-msg-required="'.__('Please enter your email address.').'"';
$output .= ' data-msg-email="'.__('Please enter a valid email address.').'"';
$output .= ' />';
$output .= ' </label>';
$output .= ' </p>';
$output .= ' <p>';
$output .= ' <label>';
$output .= ' <input type="submit" value="'.esc_attr('Subscribe!').'" />';
$output .= ' </label>';
$output .= ' </p>';
$output .= '</form>';
$output .= '</div>';
// after widget
$output .= (isset($after_widget) ? $after_widget : '');
echo $output;
if($form_type === 'widget') {
echo $output;
} else {
return $output;
}
}
}
}
// mailpoet shortcodes
// form shortcode
add_shortcode('mailpoet_form', 'mailpoet_form_shortcode');
add_shortcode('wysija_form', 'mailpoet_form_shortcode');
function mailpoet_form_shortcode($params = array()) {
// IMPORTANT: this is to make sure MagicMember won't scan our form and find [user_list] as a code they should replace.
remove_shortcode('user_list');
if(isset($params['id']) && (int)$params['id'] > 0) {
$form_widget = new \MailPoet\Form\Widget();
return $form_widget->widget(array(
'form' => (int)$params['id'],
'form_type' => 'shortcode'
));
}
}
// set the content filter to replace the shortcode
if(isset($_GET['mailpoet_page']) && strlen(trim($_GET['mailpoet_page'])) > 0) {
switch($_GET['mailpoet_page']) {
case 'mailpoet_form_iframe':
$id = (isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] > 0) ? (int)$_GET['mailpoet_form'] : null;
$form = Form::findOne($id);
if($form !== false) {
// render form
$output = Util\Export::get('html', $form->asArray());
// $output = do_shortcode($output);
print $output;
exit;
}
break;
default:
// add_filter('wp_title', 'mailpoet_meta_page_title'));
add_filter('the_title', 'mailpoet_page_title', 10, 2);
add_filter('the_content', 'mailpoet_page_content', 98, 1);
break;
}
}
function mailpoet_page_title($title = '', $id = null) {
// get signup confirmation page id
$signup_confirmation = Setting::getValue('signup_confirmation');
$page_id = $signup_confirmation['page'];
// check if we're on the signup confirmation page
if((int)$page_id === (int)$id) {
global $post;
// disable comments
$post->comment_status = 'close';
// disable password
$post->post_password = '';
$subscriber = null;
// get subscriber key from url
$subscriber_digest = (isset($_GET['mailpoet_key']) && strlen(trim($_GET['mailpoet_key'])) === 32) ? trim($_GET['mailpoet_key']) : null;
if($subscriber_digest !== null) {
// get subscriber
// TODO: change select() to selectOne() once it's implemented
$subscribers = $mailpoet->subscribers()->select(array(
'filter' => array(
'subscriber_digest' => $subscriber_digest
),
'limit' => 1
));
if(!empty($subscribers)) {
$subscriber = array_shift($subscribers);
}
}
// check if we have a subscriber record
if($subscriber === null) {
return __('Your confirmation link expired, please subscribe again.');
} else {
// we have a subscriber, let's check its state
switch($subscriber['subscriber_state']) {
case MailPoetSubscribers::STATE_UNCONFIRMED:
case MailPoetSubscribers::STATE_UNSUBSCRIBED:
// set subscriber state as confirmed
$mailpoet->subscribers()->update(array(
'subscriber' => $subscriber['subscriber'],
'subscriber_state' => MailPoetSubscribers::STATE_SUBSCRIBED,
'subscriber_confirmed_at' => time()
));
return __("You've subscribed");
break;
case MailPoetSubscribers::STATE_SUBSCRIBED:
return __("You've already subscribed");
break;
}
}
} else {
return $title;
}
}
function mailpoet_page_content($content = '') {
if(strpos($content, '[mailpoet_page]') !== FALSE) {
$content = str_replace('[mailpoet_page]', '', $content);
}
return $content;
}

View File

@ -5,15 +5,17 @@ if(!defined('ABSPATH')) exit;
class BulkAction {
private $listing = null;
private $action = null;
private $data = null;
private $model_class = null;
function __construct($model_class, $data) {
$this->model_class = $model_class;
$this->action = $data['action'];
unset($data['action']);
$this->data = $data;
$this->model_class = $model_class;
$this->listing = new Handler(
$this->model_class,
$model_class,
$this->data['listing']
);
return $this;
@ -21,8 +23,9 @@ class BulkAction {
function apply() {
return call_user_func_array(
array($this->model_class, $this->data['action']),
array($this->listing, $this->data)
array($this->model_class, 'bulk'.ucfirst($this->action)),
array($this->listing->getSelection(), $this->data)
);
return $models->count();
}
}

View File

@ -4,16 +4,13 @@ namespace MailPoet\Listing;
if(!defined('ABSPATH')) exit;
class Handler {
private $data = array();
private $model = null;
function __construct($model_class, $data = array()) {
$class = new \ReflectionClass($model_class);
$this->table_name = $class->getStaticPropertyValue('_table');
$this->model = \Model::factory($model_class);
$this->model = $model_class::select('*');
$this->data = array(
// pagination
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
@ -31,7 +28,7 @@ class Handler {
'selection' => (isset($data['selection']) ? $data['selection'] : null)
);
$this->model = $this->setFilter();
$this->setFilter();
$this->setSearch();
$this->setGroup();
$this->setOrder();
@ -59,22 +56,18 @@ class Handler {
private function setFilter() {
if($this->data['filter'] === null) {
return $this->model;
return;
}
return $this->model->filter('filterBy', $this->data['filter']);
$this->model = $this->model->filter('filterBy', $this->data['filter']);
}
function getSelection() {
if(!empty($this->data['selection'])) {
$this->model->whereIn('id', $this->data['selection']);
$this->model->whereIn($this->table_name.'.id', $this->data['selection']);
}
return $this->model;
}
function count() {
return (int)$this->model->count();
}
function getSelectionIds() {
$models = $this->getSelection()
->select('id')
@ -86,14 +79,18 @@ class Handler {
}
function get() {
$count = $this->model->count();
$items = $this->model
->offset($this->data['offset'])
->limit($this->data['limit'])
->findArray();
return array(
'count' => $this->model->count(),
'count' => $count,
'filters' => $this->model->filter('filters'),
'groups' => $this->model->filter('groups'),
'items' => $this->model
->offset($this->data['offset'])
->limit($this->data['limit'])
->findArray()
'items' => $items
);
}
}

View File

@ -16,6 +16,27 @@ class CustomField extends Model {
));
}
function asArray() {
$model = parent::asArray();
$model['params'] = (
is_serialized($this->params)
? unserialize($this->params)
: $this->params
);
return $model;
}
function save() {
$this->set('params', (
is_serialized($this->params)
? $this->params
: serialize($this->params)
));
return parent::save();
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__ . '\Subscriber',
@ -24,4 +45,33 @@ class CustomField extends Model {
'subscriber_id'
)->select_expr(MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.value');
}
static function createOrUpdate($data = array()) {
$custom_field = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$custom_field = self::findOne((int)$data['id']);
}
// set name as label by default
if(empty($data['params']['label'])) {
$data['params']['label'] = $data['name'];
}
if($custom_field === false) {
$custom_field = self::create();
$custom_field->hydrate($data);
} else {
unset($data['id']);
$custom_field->set($data);
}
try {
$custom_field->save();
return $custom_field;
} catch(Exception $e) {
return $custom_field->getValidationErrors();
}
return false;
}
}

93
lib/Models/Form.php Normal file
View File

@ -0,0 +1,93 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class Form extends Model {
static $_table = MP_FORMS_TABLE;
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('You need to specify a name.')
));
}
function asArray() {
$model = parent::asArray();
$model['body'] = (
is_serialized($this->body)
? unserialize($this->body)
: $this->body
);
$model['settings'] = (
is_serialized($this->settings)
? unserialize($this->settings)
: $this->settings
);
return $model;
}
function save() {
$this->set('body', (
is_serialized($this->body)
? $this->body
: serialize($this->body)
));
$this->set('settings', (
is_serialized($this->settings)
? $this->settings
: serialize($this->settings)
));
return parent::save();
}
static function search($orm, $search = '') {
return $orm->where_like('name', '%'.$search.'%');
}
static function groups() {
return array(
array(
'name' => 'all',
'label' => __('All'),
'count' => Form::whereNull('deleted_at')->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Form::whereNotNull('deleted_at')->count()
)
);
}
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
}
}
static function createOrUpdate($data = array()) {
$form = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$form = self::findOne((int)$data['id']);
}
if($form === false) {
$form = self::create();
$form->hydrate($data);
} else {
unset($data['id']);
$form->set($data);
}
$form->save();
return $form;
}
}

View File

@ -9,6 +9,10 @@ class Model extends \Sudzy\ValidModel {
parent::__construct($customValidators->init());
}
static function create() {
return parent::create();
}
function save() {
$this->setTimestamp();
try {
@ -21,9 +25,57 @@ class Model extends \Sudzy\ValidModel {
}
}
function trash() {
return $this->set_expr('deleted_at', 'NOW()')->save();
}
static function bulkTrash($orm) {
$models = $orm->findResultSet();
$models->set_expr('deleted_at', 'NOW()')->save();
return $models->count();
}
static function bulkDelete($orm) {
$models = $orm->findMany();
$count = 0;
foreach($models as $model) {
$model->delete();
$count++;
}
return $count;
}
function restore() {
return $this->set_expr('deleted_at', 'NULl')->save();
}
static function bulkRestore($orm) {
$models = $orm->findResultSet();
$models->set_expr('deleted_at', 'NULL')->save();
return $models->count();
}
function duplicate($data = array()) {
$model = get_called_class();
$model_data = array_merge($this->asArray(), $data);
unset($model_data['id']);
$duplicate = $model::create();
$duplicate->hydrate($model_data);
$duplicate->set_expr('created_at', 'NOW()');
$duplicate->set_expr('updated_at', 'NOW()');
$duplicate->set_expr('deleted_at', 'NULL');
if($duplicate->save()) {
return $duplicate;
} else {
return false;
}
}
private function setTimestamp() {
if($this->created_at === null) {
$this->created_at = date('Y-m-d H:i:s');
$this->set_expr('created_at', 'NOW()');
}
}

View File

@ -10,6 +10,13 @@ class Newsletter extends Model {
parent::__construct();
}
function save() {
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
$this->set_expr('deleted_at', 'NULL');
}
return parent::save();
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
@ -39,10 +46,10 @@ class Newsletter extends Model {
'label' => __('All lists'),
'value' => ''
);
foreach($segments as $segment) {
$newsletters_count = $segment->newsletters()->count();
if($newsletters_count > 0) {
$segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $newsletters_count),
'value' => $segment->id()
@ -51,34 +58,21 @@ class Newsletter extends Model {
}
$filters = array(
array(
'name' => 'segment',
'options' => $segment_list
)
'segment' => $segment_list
);
return $filters;
}
static function filterBy($orm, $filters = null) {
if(empty($filters)) {
if(empty($filters)) {
return $orm;
}
foreach($filters as $filter) {
if($filter['name'] === 'segment') {
$segment = Segment::findOne($filter['value']);
foreach($filters as $key => $value) {
if($key === 'segment') {
$segment = Segment::findOne($value);
if($segment !== false) {
$orm = $orm
->select(MP_NEWSLETTERS_TABLE.'.*')
->select('newsletter_segment.id', 'newsletter_segment_id')
->join(
MP_NEWSLETTER_SEGMENT_TABLE,
MP_NEWSLETTERS_TABLE.'.id = newsletter_segment.newsletter_id',
'newsletter_segment'
)
->where('newsletter_segment.segment_id', (int)$filter['value']);
$orm = $segment->newsletters();
}
}
}
@ -112,12 +106,21 @@ class Newsletter extends Model {
array(
'name' => 'all',
'label' => __('All'),
'count' => Newsletter::count()
'count' => Newsletter::whereNull('deleted_at')->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Newsletter::whereNotNull('deleted_at')->count()
)
);
}
static function group($orm, $group = null) {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
}
return $orm->whereNull('deleted_at');
}
static function createOrUpdate($data = array()) {
@ -135,21 +138,7 @@ class Newsletter extends Model {
$newsletter->set($data);
}
$saved = $newsletter->save();
if($saved === true) {
return $newsletter->id();
} else {
$errors = $newsletter->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
}
static function trash($listing) {
return $listing->getSelection()
->deleteMany();
$newsletter->save();
return $newsletter;
}
}

View File

@ -14,13 +14,10 @@ class Segment extends Model {
));
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
function delete() {
// delete all relations to subscribers
SubscriberSegment::where('segment_id', $this->id)->deleteMany();
parent::delete();
}
function newsletters() {
@ -32,6 +29,44 @@ class Segment extends Model {
);
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
}
function duplicate($data = array()) {
$duplicate = parent::duplicate($data);
if($duplicate !== false) {
foreach($this->subscribers()->findResultSet() as $relation) {
$new_relation = SubscriberSegment::create();
$new_relation->set('subscriber_id', $relation->id);
$new_relation->set('segment_id', $duplicate->id);
$new_relation->save();
}
return $duplicate;
}
return false;
}
function addSubscriber($subscriber_id) {
$relation = SubscriberSegment::create();
$relation->set('subscriber_id', $subscriber_id);
$relation->set('segment_id', $this->id);
return $relation->save();
}
function removeSubscriber($subscriber_id) {
return SubscriberSegment::where('subscriber_id', $subscriber_id)
->where('segment_id', $this->id)
->delete();
}
static function search($orm, $search = '') {
return $orm->where_like('name', '%'.$search.'%');
}
@ -41,12 +76,22 @@ class Segment extends Model {
array(
'name' => 'all',
'label' => __('All'),
'count' => Segment::count()
'count' => Segment::whereNull('deleted_at')->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Segment::whereNotNull('deleted_at')->count()
)
);
}
static function group($orm, $group = null) {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
}
}
static function createOrUpdate($data = array()) {
@ -64,20 +109,7 @@ class Segment extends Model {
$segment->set($data);
}
$saved = $segment->save();
if($saved === true) {
return true;
} else {
$errors = $segment->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
}
static function trash($listing) {
return $listing->getSelection()->deleteMany();
$segment->save();
return $segment;
}
}

View File

@ -15,11 +15,20 @@ class Subscriber extends Model {
));
}
function delete() {
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment',
'subscriber_id',
'segment_id'
);
}
function delete() {
// delete all relations to segments
SubscriberSegment::where('subscriber_id', $this->id)->deleteMany();
parent::delete();
return parent::delete();
}
static function search($orm, $search = '') {
@ -36,14 +45,15 @@ class Subscriber extends Model {
static function filters() {
$segments = Segment::orderByAsc('name')->findMany();
$segment_list = array();
$segment_list[] = array(
'label' => __('All lists'),
'value' => ''
);
foreach($segments as $segment) {
$subscribers_count = $segment->subscribers()->count();
$subscribers_count = $segment->subscribers()
->whereNull('deleted_at')
->count();
if($subscribers_count > 0) {
$segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
@ -53,10 +63,7 @@ class Subscriber extends Model {
}
$filters = array(
array(
'name' => 'segment',
'options' => $segment_list
)
'segment' => $segment_list
);
return $filters;
@ -66,12 +73,11 @@ class Subscriber extends Model {
if(empty($filters)) {
return $orm;
}
foreach($filters as $filter) {
if($filter['name'] === 'segment') {
$segment = Segment::findOne($filter['value']);
foreach($filters as $key => $value) {
if($key === 'segment') {
$segment = Segment::findOne($value);
if($segment !== false) {
$orm = $segment->subscribers();
return $segment->subscribers();
}
}
}
@ -88,23 +94,17 @@ class Subscriber extends Model {
array(
'name' => 'subscribed',
'label' => __('Subscribed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'subscribed')
->count()
'count' => Subscriber::filter('subscribed')->count()
),
array(
'name' => 'unconfirmed',
'label' => __('Unconfirmed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'unconfirmed')
->count()
'count' => Subscriber::filter('unconfirmed')->count()
),
array(
'name' => 'unsubscribed',
'label' => __('Unsubscribed'),
'count' => Subscriber::whereNull('deleted_at')
->where('status', 'unsubscribed')
->count()
'count' => Subscriber::filter('unsubscribed')->count()
),
array(
'name' => 'trash',
@ -117,12 +117,10 @@ class Subscriber extends Model {
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else if($group === 'all') {
return $orm->whereNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
if(in_array($group, array('subscribed', 'unsubscribed', 'unconfirmed'))) {
return $orm->where('status', $group);
}
return $orm->filter($group);
}
}
@ -148,15 +146,6 @@ class Subscriber extends Model {
return $orm;
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment',
'subscriber_id',
'segment_id'
);
}
function customFields() {
return $this->has_many_through(
__NAMESPACE__.'\CustomField',
@ -171,36 +160,25 @@ class Subscriber extends Model {
if(isset($data['id']) && (int)$data['id'] > 0) {
$subscriber = self::findOne((int)$data['id']);
unset($data['id']);
}
if($subscriber === false) {
$subscriber = self::create();
$subscriber->hydrate($data);
} else {
unset($data['id']);
$subscriber->set($data);
}
$saved = $subscriber->save();
if($saved === true) {
return true;
} else {
$errors = $subscriber->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
$subscriber->save();
return $subscriber;
}
static function moveToList($listing, $data = array()) {
static function bulkMoveToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
$subscribers_count = 0;
$subscribers = $listing->getSelection()->findMany();
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
// remove subscriber from all segments
SubscriberSegment::where('subscriber_id', $subscriber->id)->deleteMany();
@ -210,37 +188,37 @@ class Subscriber extends Model {
$association->subscriber_id = $subscriber->id;
$association->segment_id = $segment->id;
$association->save();
$subscribers_count++;
}
return array(
'subscribers' => $subscribers_count,
'subscribers' => $subscribers->count(),
'segment' => $segment->name
);
}
return false;
}
static function removeFromList($listing, $data = array()) {
static function bulkRemoveFromList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
// delete relations with segment
$subscriber_ids = $listing->getSelectionIds();
SubscriberSegment::whereIn('subscriber_id', $subscriber_ids)
->where('segment_id', $segment->id)
->deleteMany();
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
SubscriberSegment::where('subscriber_id', $subscriber->id)
->where('segment_id', $segment->id)
->deleteMany();
}
return array(
'subscribers' => count($subscriber_ids),
'subscribers' => $subscribers->count(),
'segment' => $segment->name
);
}
return false;
}
static function removeFromAllLists($listing) {
static function bulkRemoveFromAllLists($orm) {
$segments = Segment::findMany();
$segment_ids = array_map(function($segment) {
return $segment->id();
@ -248,62 +226,48 @@ class Subscriber extends Model {
if(!empty($segment_ids)) {
// delete relations with segment
$subscriber_ids = $listing->getSelectionIds();
SubscriberSegment::whereIn('subscriber_id', $subscriber_ids)
->whereIn('segment_id', $segment_ids)
->deleteMany();
return array(
'subscribers' => count($subscriber_ids)
);
}
return false;
}
static function confirmUnconfirmed($listing) {
$subscriber_ids = $listing->getSelectionIds();
$subscribers = Subscriber::whereIn('id', $subscriber_ids)
->where('status', 'unconfirmed')
->findMany();
if(!empty($subscribers)) {
$subscribers_count = 0;
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
$subscriber->set('status', 'subscribed');
if($subscriber->save() === true) {
$subscribers_count++;
}
SubscriberSegment::where('subscriber_id', $subscriber->id)
->whereIn('segment_id', $segment_ids)
->deleteMany();
}
return array(
'subscribers' => $subscribers_count
);
return $subscribers->count();
}
return false;
}
static function resendConfirmationEmail($listing) {
$subscriber_ids = $listing->getSelectionIds();
$subscribers = Subscriber::whereIn('id', $subscriber_ids)
static function bulkConfirmUnconfirmed($orm) {
$subscribers = $orm->findResultSet();
$subscribers->set('status', 'subscribed')->save();
return $subscribers->count();
}
static function bulkResendConfirmationEmail($orm) {
$subscribers = $orm
->where('status', 'unconfirmed')
->findMany();
->findResultSet();
if(!empty($subscribers)) {
foreach($subscribers as $subscriber) {
// TODO: resend confirmation email
// TODO: send confirmation email
// $subscriber->sendConfirmationEmail()
}
return true;
return $subscribers->count();
}
return false;
}
static function addToList($listing, $data = array()) {
static function bulkAddToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
if($segment !== false) {
$subscribers_count = 0;
$subscribers = $listing->getSelection()->findMany();
$subscribers = $orm->findMany();
foreach($subscribers as $subscriber) {
// create relation with segment
$association = \MailPoet\Models\SubscriberSegment::create();
@ -321,45 +285,21 @@ class Subscriber extends Model {
return false;
}
static function trash($listing, $data = array()) {
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
if($confirm_delete) {
// delete relations with all segments
$subscribers = $listing->getSelection()->findResultSet();
if(!empty($subscribers)) {
$subscribers_count = 0;
foreach($subscribers as $subscriber) {
if($subscriber->delete()) {
$subscribers_count++;
}
}
return array(
'subscribers' => $subscribers_count
);
}
return false;
} else {
// soft delete
$subscribers = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NOW()')
->save();
return array(
'subscribers' => $subscribers->count()
);
}
static function subscribed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'subscribed');
}
static function restore($listing, $data = array()) {
$subscribers = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NULL')
->save();
static function unsubscribed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'unsubscribed');
}
return array(
'subscribers' => $subscribers->count()
);
static function unconfirmed($orm) {
return $orm
->whereNull('deleted_at')
->where('status', 'unconfirmed');
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace MailPoet\Router;
use \MailPoet\Models\CustomField;
if(!defined('ABSPATH')) exit;
class CustomFields {
function __construct() {
}
function getAll() {
$collection = CustomField::findMany();
$custom_fields = array_map(function($custom_field) {
return $custom_field->asArray();
}, $collection);
wp_send_json($custom_fields);
}
function delete($id) {
$result = false;
$custom_field = CustomField::findOne($id);
if($custom_field !== false) {
$custom_field->delete();
$result = true;
}
wp_send_json($result);
}
function save($data = array()) {
$custom_field = CustomField::createOrUpdate($data);
if($custom_field === false) {
$result = array(
'result' => false,
'errors' => array(
__('The custom field could not be created.')
)
);
} else {
$errors = $custom_field->getValidationErrors();
if(!empty($errors)) {
$result = array(
'result' => false,
'errors' => $errors
);
} else {
$result = array(
'result' => true,
'field' => $custom_field->asArray()
);
}
}
wp_send_json($result);
}
function get($id) {
$custom_field = CustomField::findOne($id);
if($custom_field === false) {
wp_send_json(false);
} else {
$custom_field = $custom_field->asArray();
wp_send_json($custom_field);
}
}
}

258
lib/Router/Forms.php Normal file
View File

@ -0,0 +1,258 @@
<?php
namespace MailPoet\Router;
use \MailPoet\Models\Form;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Listing;
use \MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;
class Forms {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : 0);
$form = Form::findOne($id);
if($form === false) {
wp_send_json(false);
} else {
$form = $form->asArray();
wp_send_json($form);
}
}
function listing($data = array()) {
$listing = new Listing\Handler(
'\MailPoet\Models\Form',
$data
);
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) {
// form's segments
$form_settings = (
(is_serialized($item['settings']))
? unserialize($item['settings'])
: array()
);
$item['segments'] = (
!empty($form_settings['segments'])
? $form_settings['segments']
: array()
);
}
wp_send_json($listing_data);
}
function getAll() {
$collection = Form::findArray();
wp_send_json($collection);
}
function create() {
// create new form
$form_data = array(
'name' => __('New form'),
'body' => array(
array(
'id' => 'email',
'name' => __('Email'),
'type' => 'input',
'static' => true,
'params' => array(
'label' => __('Email'),
'required' => true
)
),
array(
'id' => 'submit',
'name' => __('Submit'),
'type' => 'submit',
'static' => true,
'params' => array(
'label' => __('Subscribe!')
)
)
),
'settings' => array(
'on_success' => 'message',
'success_message' => __('Check your inbox or spam folder now to confirm your subscription.'),
'segments' => null,
'segments_selected_by' => 'admin'
)
);
$form = Form::createOrUpdate($form_data);
if($form !== false && $form->id()) {
wp_send_json(
admin_url('admin.php?page=mailpoet-form-editor&id='.$form->id())
);
} else {
wp_send_json(false);
}
}
function save($data = array()) {
$form = Form::createOrUpdate($data);
if($form !== false && $form->id()) {
wp_send_json($form->id());
} else {
wp_send_json($form);
}
}
function previewEditor($data = array()) {
// html
$html = FormRenderer::renderHTML($data);
// convert shortcodes
$html = do_shortcode($html);
// styles
$css = new Util\Styles(FormRenderer::getStyles($data));
wp_send_json(array(
'html' => $html,
'css' => $css->render()
));
}
function exportsEditor($id) {
$exports = false;
$form = Form::findOne($id);
if($form !== false) {
$exports = Util\Export::getAll($form->asArray());
}
wp_send_json($exports);
}
function saveEditor($data = array()) {
$form_id = (isset($data['id']) ? (int)$data['id'] : 0);
$body = (isset($data['body']) ? $data['body'] : array());
$settings = (isset($data['settings']) ? $data['settings'] : array());
$styles = (isset($data['styles']) ? $data['styles'] : array());
if(empty($body) || empty($settings)) {
// error
wp_send_json(false);
} else {
// check if the form is used as a widget
$is_widget = false;
$widgets = get_option('widget_mailpoet_form');
if(!empty($widgets)) {
foreach($widgets as $widget) {
if(isset($widget['form']) && (int)$widget['form'] === $form_id) {
$is_widget = true;
break;
}
}
}
// check if the user gets to pick his own lists
// or if it's selected by the admin
$has_segment_selection = false;
foreach ($body as $i => $block) {
if($block['type'] === 'segment') {
$has_segment_selection = true;
if(!empty($block['params']['values'])) {
$list_selection = array_map(function($segment) {
if(!empty($segment)) {
return (int)$segment['id'];
}
}, $block['params']['values']);
}
break;
}
}
// check list selectio
if($has_segment_selection === true) {
$settings['segments_selected_by'] = 'user';
} else {
$settings['segments_selected_by'] = 'admin';
}
}
$form = Form::createOrUpdate(array(
'id' => $form_id,
'body' => $body,
'settings' => $settings,
'styles' => $styles
));
// response
wp_send_json(array(
'result' => ($form !== false),
'is_widget' => $is_widget
));
}
function restore($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$result = $form->restore();
}
wp_send_json($result);
}
function trash($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$result = $form->trash();
}
wp_send_json($result);
}
function delete($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$form->delete();
$result = 1;
}
wp_send_json($result);
}
function duplicate($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$data = array(
'name' => sprintf(__('Copy of %s'), $form->name)
);
$result = $form->duplicate($data)->asArray();
}
wp_send_json($result);
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Form',
$data
);
wp_send_json($bulk_action->apply());
}
}

View File

@ -54,29 +54,34 @@ class Newsletters {
unset($data['options']);
}
$newsletter_id = Newsletter::createOrUpdate($data);
$newsletter = Newsletter::createOrUpdate($data);
if($newsletter_id !== false) {
if($newsletter->id) {
if(!empty($segment_ids)) {
// remove previous relationships with segments
NewsletterSegment::where('newsletter_id', $newsletter_id)->deleteMany();
// create relationship with segments
NewsletterSegment::where('newsletter_id', $newsletter->id)
->deleteMany();
foreach($segment_ids as $segment_id) {
$relation = NewsletterSegment::create();
$relation->segment_id = $segment_id;
$relation->newsletter_id = $newsletter_id;
$relation->newsletter_id = $newsletter->id;
$relation->save();
}
}
if(!empty($options)) {
NewsletterOption::where('newsletter_id', $newsletter_id)->deletemany();
$optionFields = NewsletterOptionField::where('newsletter_type', $data['type'])->findArray();
NewsletterOption::where('newsletter_id', $newsletter->id)
->deleteMany();
$optionFields = NewsletterOptionField::where(
'newsletter_type',
$data['type']
)->findArray();
foreach($optionFields as $optionField) {
if(isset($options[$optionField['name']])) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter_id;
$relation->newsletter_id = $newsletter->id;
$relation->option_field_id = $optionField['id'];
$relation->value = $options[$optionField['name']];
$relation->save();
@ -85,13 +90,31 @@ class Newsletters {
}
}
wp_send_json(($newsletter_id !== false));
wp_send_json(($newsletter->id !== false));
}
function delete($id) {
function delete($data = array()) {
$newsletter = newsletter::findOne($data['id']);
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
if($newsletter !== false) {
if($confirm_delete) {
$newsletter->delete();
$result = array('newsletters' => 1);
} else {
$newsletter->set_expr('deleted_at', 'NOW()');
$result = array('newsletters' => (int)$newsletter->save());
}
} else {
$result = false;
}
wp_send_json($result);
}
function restore($id) {
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$result = $newsletter->delete();
$newsletter->set_expr('deleted_at', 'NULL');
$result = array('newsletters' => (int)$newsletter->save());
} else {
$result = false;
}
@ -181,7 +204,7 @@ class Newsletters {
wp_send_json($listing_data);
}
function bulk_action($data = array()) {
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Newsletter',
$data

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Router;
use \MailPoet\Models\Segment;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Listing;
if(!defined('ABSPATH')) exit;
@ -25,11 +26,47 @@ class Segments {
'\MailPoet\Models\Segment',
$data
);
wp_send_json($listing->get());
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) {
$stats = SubscriberSegment::table_alias('relation')
->where(
'relation.segment_id',
$item['id']
)
->join(
MP_SUBSCRIBERS_TABLE,
'subscribers.id = relation.subscriber_id',
'subscribers'
)
->select_expr(
'SUM(CASE status WHEN "subscribed" THEN 1 ELSE 0 END)',
'subscribed'
)
->select_expr(
'SUM(CASE status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
'unsubscribed'
)
->select_expr(
'SUM(CASE status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
'unconfirmed'
)
->findOne()->asArray();
$item = array_merge($item, $stats);
$item['subscribers_url'] = admin_url(
'admin.php?page=mailpoet-subscribers#/filter[segment='.$item['id'].']'
);
}
wp_send_json($listing_data);
}
function getAll() {
$collection = Segment::find_array();
$collection = Segment::findArray();
wp_send_json($collection);
}
@ -43,18 +80,55 @@ class Segments {
}
}
function delete($id) {
function restore($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$result = $segment->delete();
} else {
$result = false;
$result = $segment->restore();
}
wp_send_json($result);
}
function bulk_action($data = array()) {
function trash($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$result = $segment->trash();
}
wp_send_json($result);
}
function delete($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$segment->delete();
$result = 1;
}
wp_send_json($result);
}
function duplicate($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$data = array(
'name' => sprintf(__('Copy of %s'), $segment->name)
);
$result = $segment->duplicate($data)->asArray();
}
wp_send_json($result);
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Segment',
$data

View File

@ -4,6 +4,9 @@ namespace MailPoet\Router;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Form;
if(!defined('ABSPATH')) exit;
@ -32,12 +35,18 @@ class Subscribers {
// fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) {
$segments = SubscriberSegment::select('segment_id')
// avatar
$item['avatar_url'] = get_avatar_url($item['email'], array(
'size' => 32
));
// subscriber's segments
$relations = SubscriberSegment::select('segment_id')
->where('subscriber_id', $item['id'])
->findMany();
$item['segments'] = array_map(function($relation) {
return $relation->segment_id;
}, $segments);
}, $relations);
}
wp_send_json($listing_data);
@ -49,39 +58,234 @@ class Subscribers {
}
function save($data = array()) {
$result = Subscriber::createOrUpdate($data);
$result = false;
$subscriber = Subscriber::createOrUpdate($data);
if($subscriber !== false && !$subscriber->id()) {
$result = array(
'errors' => $subscriber->getValidationErrors()
);
} else {
$result = true;
}
wp_send_json($result);
}
function subscribe($data = array()) {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
$errors = array();
$form = Form::findOne($data['form_id']);
unset($data['form_id']);
if($form === false || !$form->id()) {
$errors[] = __('This form does not exist.');
}
if(empty($data['segments'])) {
$errors[] = __('You need to select a list');
} else {
$segments = Segment::whereIn('id', (array)$data['segments'])->findMany();
if(empty($segments)) {
$errors[] = __('You need to select a list');
}
}
unset($data['segments']);
$subscriber = false;
if(!empty($errors)) {
wp_send_json(array('errors' => $errors));
} else {
if(!empty($data['email'])) {
$subscriber = Subscriber::where('email', $data['email'])->findOne();
}
}
$signup_confirmation = Setting::getValue('signup_confirmation', array());
if($subscriber === false) {
// create new subscriber
$data['status'] = (
(!empty($signup_confirmation['enabled']))
? 'unconfirmed' : 'subscribed'
);
// // set custom fields
// $meta_fields = $mailpoet->getOption('mailpoet_subscriber_meta', array());
// if(!empty($meta_fields)) {
// // loop through data to see if any meta field has been passed
// foreach($meta_fields as $field => $field_data) {
// // check if it's a mandatory field
// $is_required = (isset($field_data['params']['required']) && (bool)$field_data['params']['required'] === true);
// if(array_key_exists($field, $data)) {
// // check if it's a mandatory field
// if($is_required === true && empty($data[$field])) {
// // if it's missing, throw an error
// $errors[] = sprintf(__('&quot;%s&quot; is required'), $field_data['name']);
// } else {
// // assign field to subscriber
// $subscriber[$field] = $data[$field];
// }
// }
// }
// }
// insert new subscriber
$subscriber = Subscriber::createOrUpdate($data);
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
}
} else {
$subscriber->set('status', (
!empty($signup_confirmation['enabled'])
? 'unconfirmed' : 'subscribed'
));
// restore deleted subscriber
if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL');
}
if(!$subscriber->save()) {
$errors[] = __('An error occurred. Please try again later.');
}
}
// get segments
// IDEA: $subscriptions->addToSegments($data['segments']);
$segments_subscribed = array();
foreach($segments as $segment) {
if($segment->addSubscriber($subscriber->id())) {
$segments_subscribed[] = $segment->id;
}
}
// if signup confirmation is enabled and the subscriber is unconfirmed
if(!empty($signup_confirmation['enabled'])
&& !empty($segments_subscribed)
&& $subscriber->status !== 'subscribed'
) {
// TODO: send confirmation email
// resend confirmation email
$is_sent = true;
/*$is_sent = static::sendSignupConfirmation(
$subscriber->asArray(),
$segments->asArray()
);*/
// error message if the email could not be sent
if($is_sent === false) {
$errors[] = __('The signup confirmation email could not be sent. Please check your settings.');
}
}
// get success message to display after subscription
$form_settings = (
isset($form->settings)
? unserialize($form->settings) : null
);
if(!empty($errors)) {
wp_send_json(array(
'result' => false,
'errors' => $errors
));
} else {
$result = true;
}
if($form_settings !== null) {
$message = $form_settings['success_message'];
// url params for non ajax requests
if($doing_ajax === false) {
// get referer
$referer = (wp_get_referer() !== false)
? wp_get_referer() : $_SERVER['HTTP_REFERER'];
// redirection parameters
$params = array(
'mailpoet_form' => $form->id()
);
// handle success/error messages
if($result === false) {
$params['mailpoet_error'] = urlencode($message);
} else {
$params['mailpoet_success'] = urlencode($message);
}
}
switch ($form_settings['on_success']) {
case 'page':
// response depending on context
if($doing_ajax === true) {
wp_send_json(array(
'result' => $result,
'page' => get_permalink($form_settings['success_page']),
'message' => $message
));
} else {
$redirect_to = ($result === false) ? $referer : get_permalink($form_settings['success_page']);
wp_redirect(add_query_arg($params, $redirect_to));
}
break;
case 'message':
default:
// response depending on context
if($doing_ajax === true) {
wp_send_json(array(
'result' => $result,
'message' => $message
));
} else {
// redirect to previous page
wp_redirect(add_query_arg($params, $referer));
}
break;
}
}
}
function restore($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$subscriber->set_expr('deleted_at', 'NULL');
$result = array('subscribers' => (int)$subscriber->save());
} else {
$result = false;
$result = $subscriber->restore();
}
wp_send_json($result);
}
function delete($data = array()) {
$subscriber = Subscriber::findOne($data['id']);
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
function trash($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
if($confirm_delete) {
$subscriber->delete();
$result = array('subscribers' => 1);
} else {
$subscriber->set_expr('deleted_at', 'NOW()');
$result = array('subscribers' => (int)$subscriber->save());
}
} else {
$result = false;
$result = $subscriber->trash();
}
wp_send_json($result);
}
function bulk_action($data = array()) {
function delete($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$subscriber->delete();
$result = 1;
}
wp_send_json($result);
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Subscriber',
$data

View File

@ -51,9 +51,13 @@ class Handlebars extends \Twig_Extension {
if($alias !== null) {
$output[] = '<script type="text/javascript">';
$output[] = ' Handlebars.registerPartial(
"'.$alias.'",
jQuery("#'.$id.'").html());';
$output[] = 'jQuery(function($) {';
$output[] = '$(function() {';
$output[] = ' Handlebars.registerPartial(
"'.$alias.'",
jQuery("#'.$id.'").html());';
$output[] = '});';
$output[] = '});';
$output[] = '</script>';
}
return join("\n", $output);

View File

@ -1,24 +1,17 @@
<?php
namespace MailPoet\Util;
use \phpseclib\Crypt\RSA;
class DKIM {
static function generateKeys() {
try {
$certificate = openssl_pkey_new(array('private_bits' => 1024));
$rsa = new RSA();
$rsa_keys = $rsa->createKey();
$keys = array('public' => '', 'private' => '');
// get private key
openssl_pkey_export($certificate, $keys['private']);
// get public key
$public = openssl_pkey_get_details($certificate);
// trim keys by removing BEGIN/END lines
$keys['public'] = self::trimKey($public['key']);
$keys['private'] = self::trimKey($keys['private']);
return $keys;
return array(
'public' => self::trimKey($rsa_keys['publickey']),
'private' => self::trimKey($rsa_keys['privatekey'])
);
} catch(Exception $e) {
return false;
}

View File

@ -5,7 +5,7 @@ if (!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet
* Version: 0.0.1
* Version: 0.0.3
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@ -18,12 +18,12 @@ if (!defined('ABSPATH')) exit;
*
* @package WordPress
* @author MailPoet
* @since 0.0.1
* @since 0.0.3
*/
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.1');
define('MAILPOET_VERSION', '0.0.3');
$initializer = new Initializer(array(
'file' => __FILE__,

View File

@ -4,6 +4,8 @@
"install": "napa"
},
"napa": {
"blob": "eligrey/Blob.js.git",
"filesaver": "eligrey/FileSaver.js.git",
"sticky-kit": "leafo/sticky-kit.git"
},
"dependencies": {
@ -13,19 +15,19 @@
"backbone.supermodel": "1.2.0",
"c3": "~0.4.10",
"classnames": "^2.1.3",
"codemirror": "^5.5.0",
"codemirror": "^5.8.0",
"d3": "~3.5.5",
"handlebars": "3.0.3",
"history": "^1.12.5",
"html2canvas": "latest",
"interact.js": "latest",
"jquery-validation": "^1.14.0",
"moment": "^2.10.3",
"napa": "^1.2.0",
"papaparse": "4.1.1",
"react": "0.14.0",
"parsley": "^0.1.0",
"react": "^0.14.1",
"react-checkbox-group": "0.2.2",
"react-dom": "^0.14.0",
"react-dom": "^0.14.1",
"react-infinity": "1.2.2",
"react-prefixr": "0.1.0",
"react-router": "^1.0.0-rc3",
@ -36,9 +38,9 @@
"underscore": "1.8.3"
},
"devDependencies": {
"export-loader": "webpack/exports-loader.git",
"import-loader": "webpack/imports-loader.git",
"expose-loader": "webpack/expose-loader.git",
"expose-loader": "latest",
"exports-loader": "latest",
"imports-loader": "latest",
"babel-core": "^5.8.22",
"babel-loader": "^5.3.2",
"amd-inject-loader": "latest",

View File

@ -18,7 +18,11 @@ $models = array(
'SubscriberSegment'
);
$destroy = function ($model) {
Model::factory('\MailPoet\Models\\' . $model)
->deleteMany();
$class = new \ReflectionClass('\MailPoet\Models\\' . $model);
$table = $class->getStaticPropertyValue('_table');
$db = ORM::getDb();
$db->beginTransaction();
$db->exec('TRUNCATE '.$table);
$db->commit();
};
array_map($destroy, $models);

View File

@ -65,6 +65,6 @@ class WPMailCest {
$this->newsletter,
$this->subscriber
);
expect($result)->true();
//expect($result)->true();
}
}

View File

@ -0,0 +1,84 @@
<?php
use MailPoet\Models\Form;
class FormCest {
function _before() {
$this->before_time = time();
$this->data = array(
'name' => 'my form',
);
$this->form = Form::create();
$this->form->hydrate($this->data);
$this->saved = $this->form->save();
}
function itCanBeCreated() {
expect($this->saved)->equals(true);
}
function itHasToBeValid() {
expect($this->saved)->equals(true);
$empty_model = Form::create();
expect($empty_model->save())->notEquals(true);
$validations = $empty_model->getValidationErrors();
expect(count($validations))->equals(1);
}
function itHasACreatedAtOnCreation() {
$form = Form::where('name', $this->data['name'])
->findOne();
$time_difference = strtotime($form->created_at) >= $this->before_time;
expect($time_difference)->equals(true);
}
function itHasAnUpdatedAtOnCreation() {
$form = Form::where('name', $this->data['name'])
->findOne();
$time_difference = strtotime($form->updated_at) >= $this->before_time;
expect($time_difference)->equals(true);
}
function itKeepsTheCreatedAtOnUpdate() {
$form = Form::where('name', $this->data['name'])
->findOne();
$old_created_at = $form->created_at;
$form->name = 'new name';
$form->save();
expect($old_created_at)->equals($form->created_at);
}
function itUpdatesTheUpdatedAtOnUpdate() {
$form = Form::where('name', $this->data['name'])
->findOne();
$update_time = time();
$form->name = 'new name';
$form->save();
$time_difference = strtotime($form->updated_at) >= $update_time;
expect($time_difference)->equals(true);
}
function itCanCreateOrUpdate() {
$is_created = Form::createOrUpdate(array(
'name' => 'new form'
));
expect($is_created)->notEquals(false);
expect($is_created->getValidationErrors())->isEmpty();
$form = Form::where('name', 'new form')->findOne();
expect($form->name)->equals('new form');
$is_updated = Form::createOrUpdate(array(
'id' => $form->id,
'name' => 'updated form'
));
$form = Form::where('name', 'updated form')->findOne();
expect($form->name)->equals('updated form');
}
function _after() {
ORM::forTable(Form::$_table)
->deleteMany();
}
}

View File

@ -80,7 +80,7 @@ class NewsletterCest {
'body' => 'body'
));
expect($is_created)->notEquals(false);
expect($is_created)->greaterThan(0);
expect($is_created->getValidationErrors())->isEmpty();
$newsletter = Newsletter::where('subject', 'new newsletter')
->findOne();

View File

@ -67,7 +67,8 @@ class SegmentCest {
$is_created = Segment::createOrUpdate(array(
'name' => 'new list'
));
expect($is_created)->equals(true);
expect($is_created)->notEquals(false);
expect($is_created->getValidationErrors())->isEmpty();
$segment = Segment::where('name', 'new list')->findOne();
expect($segment->name)->equals('new list');

View File

@ -137,7 +137,7 @@ class SubscriberCest {
->findOne($this->subscriber->id);
expect($subscriber->DOB)->equals($association->value);
}
function itCanFilterCustomFields() {
$customFieldData = array(
array(
@ -270,14 +270,17 @@ class SubscriberCest {
'last_name' => 'Doe'
);
$result = Subscriber::createOrUpdate($data);
expect($result)->equals(true);
expect($result)->notEquals(false);
expect($result->getValidationErrors())->isEmpty();
$record = Subscriber::where('email', $data['email'])
->findOne();
expect($record->first_name)->equals($data['first_name']);
expect($record->last_name)->equals($data['last_name']);
$record->last_name = 'Mailer';
$result = Subscriber::createOrUpdate($record->asArray());
expect($result)->equals(true);
expect($result)->notEquals(false);
expect($result->getValidationErrors())->isEmpty();
$record = Subscriber::where('email', $data['email'])
->findOne();
expect($record->last_name)->equals('Mailer');

View File

@ -2,7 +2,7 @@
<% block title %>
<h2 class="title">
<span id="mailpoet_form_name"><%= form.form_name %></span>
<span id="mailpoet_form_name"><%= form.name %></span>
<input id="mailpoet_form_name_input" type="text" value="" style="display:none;" />
<span>
<a id="mailpoet_form_edit_name" class="button" href="javascript:;"><%= __('Edit name' ) %></a>
@ -34,24 +34,26 @@
<div>
<!-- Form settings -->
<form id="mailpoet_form_settings" action="" method="POST">
<div id="mailpoet_settings_list_selection">
<input
type="hidden"
id="mailpoet_form_id"
value="<%= form.id | default(0) %>"
/>
<div id="mailpoet_settings_segment_selection">
<!-- Form settings: list selection -->
<p>
<strong><%= __('This form adds subscribers to these lists:') %></strong>
</p>
<select name="lists" data-placeholder="<%= __('Choose a list') %>" multiple>
<% for list in lists %>
<option value="<%= list.id %>"
<% if list.id in form.data.settings.lists %>
selected="selected"
<% endif %>
><%= list.name %></option>
<select
id="mailpoet_form_segments"
name="segments"
data-placeholder="<%= __('Choose a list') %>"
multiple
>
<% for segment in segments %>
<option value="<%= segment.id %>"><%= segment.name %></option>
<% endfor %>
</select>
<!-- error if user tries to save and has not selected a list -->
<p class="mailpoet_error" data-error="admin_no_list">
<%= __('You have to select at least 1 list') %>
</p>
</div>
<div id="mailpoet_on_success">
@ -63,6 +65,9 @@
type="radio"
name="on_success"
value="message"
<% if(form.data.settings.on_success is empty or form.data.settings.on_success == 'message') %>
checked="checked"
<% endif %>
/><%= __('Show message') %>
</label>
&nbsp;
@ -71,6 +76,9 @@
type="radio"
name="on_success"
value="page"
<% if(form.data.settings.on_success == 'page') %>
checked="checked"
<% endif %>
/><%= __('Go to page') %>
</label>
</p>
@ -93,10 +101,10 @@
>
<select name="success_page">
<% for page in pages %>
<option value="<%= page.ID %>"
<%- if form.data.settings.success_page != page.ID %>
<option value="<%= page.id %>"
<%- if form.data.settings.success_page != page.id %>
<%=- ' selected="selected"' %>
<%- endif %>><%= page.post_title %></option>
<%- endif %>><%= page.title %></option>
<% endfor %>
</select>
</p>
@ -105,39 +113,42 @@
</div>
</div>
<!-- Toolbar: Shortcodes / Export -->
<div class="mailpoet_toolbar_section closed" data-section="shortcodes">
<a href="javascript:;" class="mailpoet_toggle"><br /></a>
<h3><%= __('Shortcodes') %></h3>
<!-- Toolbar: Shortcodes / Export -->
<div class="mailpoet_toolbar_section closed" data-section="shortcodes">
<a href="javascript:;" class="mailpoet_toggle"><br /></a>
<h3><%= __('Shortcodes') %></h3>
<div>
<!-- Form export links -->
<p>
<%= __("You can easily add this form to your theme's in the %sWidgets area%s")
| format('<a href="widgets.php" target="_blank">', '</a>')
| raw
%>
</p>
<p>
<%= __('%sHTML%s, %sPHP%s, %siframe%s and %sshortcode%s versions are also available.', 'wysija-newsletters')
| format(
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="html">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="php">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="iframe">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="shortcode">',
'</a>'
)
| raw
%>
</p>
<div>
<!-- Form export links -->
<p>
<%= __("You can easily add this form to your theme's in the [link]Widgets area[/link]")
| replace({
'[link]': '<a href="widgets.php" target="_blank">',
'[/link]': '</a>'
})
| raw
%>
</p>
<p>
<%= __('%sHTML%s, %sPHP%s, %siframe%s and %sshortcode%s versions are also available.', 'wysija-newsletters')
| format(
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="html">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="php">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="iframe">',
'</a>',
'<a href="javascript:;" class="mailpoet_form_export_toggle" data-type="shortcode">',
'</a>'
)
| raw
%>
</p>
<!-- Form export -->
<div id="mailpoet_form_export"></div>
</div>
<!-- Form export -->
<div id="mailpoet_form_export"></div>
</div>
</div>
<!-- Toolbar: Fields -->
<div class="mailpoet_toolbar_section closed" data-section="fields">
@ -161,7 +172,14 @@
<div>
<textarea id="mailpoet_form_styles"><%= styles %></textarea>
<br />
<p class="mailpoet_align_center"><a id="mailpoet_form_preview" href="javascript:;" class="button button-primary" style="width:100%;"><%= __('Preview') %></a></p>
<p class="mailpoet_align_center">
<a
id="mailpoet_form_preview"
href="javascript:;"
class="button button-primary"
style="width:100%;"
><%= __('Preview') %></a>
</p>
</div>
</div>
@ -173,100 +191,110 @@
</div>
<%= javascript(
'vendor.js',
'lib/prototype.min.js',
'lib/select2.js',
'lib/scriptaculous.min.js',
'lib/codemirror/codemirror.js',
'lib/codemirror/syntax_php.js',
'lib/codemirror/syntax_css.js',
'lib/jquery.serializeObject.js',
'form_editor.js'
)%>
<%= stylesheet(
'lib/select2/select2.css',
'lib/codemirror/codemirror.css',
'lib/codemirror/theme_neo.css'
) %>
<script type="text/javascript">
var mailpoet_segments = <%= json_encode(segments) %>;
var mailpoet_default_fields = [
{
id: 'divider',
name: "<%= __('Divider') %>",
type: 'divider',
multiple: true,
readonly: true
},
{
id: "first_name",
name: "<%= __('First name') %>",
type: 'input',
params: {
label: "<%= __('First name') %>"
},
readonly: true
},
{
id: "last_name",
name: "<%= __('Last name') %>",
type: 'input',
params: {
label: "<%= __('Last name') %>"
},
readonly: true
},
{
id: "segments",
name: "<%= __('List selection') %>",
type: 'segment',
params: {
label: "<%= __('Select list(s):') %>"
},
readonly: true
},
{
id: 'html',
name: "<%= __('Random text or HTML') %>",
type: 'html',
params: {
text: "<%= __('Subscribe to our newsletter and join our [mailpoet_subscribers_count] subscribers.') %>"
},
multiple: true,
readonly: true
}
];
jQuery(function($) {
function mailpoet_form_fields(data) {
function mailpoet_form_fields() {
// form editor: default fields
var template = Handlebars.compile(jQuery('#form_template_fields').html());
// render toolbar
jQuery('#mailpoet_toolbar_fields').html(template(data));
var data = {
fields: mailpoet_default_fields
};
// enable code mirror editor on styles textarea
MailPoet.CodeEditor = CodeMirror.fromTextArea(document.getElementById('mailpoet_form_styles'), {
lineNumbers: true,
tabMode: "indent",
matchBrackets: true,
theme: 'neo',
mode: 'css',
return MailPoet.Ajax.post({
endpoint: 'customFields',
action: 'getAll',
}).done(function(response) {
if(response !== false) {
data.fields = $.merge(response, data.fields);
}
// render toolbar
jQuery('#mailpoet_toolbar_fields').html(template(data));
});
}
window.mailpoet_form_fields = mailpoet_form_fields;
// form editor: default fields
var default_fields = [
// enable code mirror editor on styles textarea
MailPoet.CodeEditor = CodeMirror.fromTextArea(
document.getElementById('mailpoet_form_styles'),
{
name: "<%= __('Divider') %>",
field: 'divider',
type: 'divider',
multiple: true,
readonly: true
},
{
name: "<%= __('First name') %>",
field: 'firstname',
type: 'input',
params: {
label: "<%= __('First name') %>"
},
readonly: true
},
{
name: "<%= __('Last name') %>",
field: 'lastname',
type: 'input',
params: {
label: "<%= __('Last name') %>"
},
readonly: true
},
{
name: "<%= __('List selection') %>",
field: 'list',
type: 'list',
params: {
label: "<%= __('Select list(s):') %>",
values: [ <%= default_list | json_encode | raw %> ] // default list
},
readonly: true
},
{
name: "<%= __('Random text or HTML') %>",
field: 'html',
type: 'html',
params: {
text: "<%= __('Subscribe to our newsletter and join our [mailpoet_subscribers_count] subscribers.') %>"
},
multiple: true,
readonly: true
lineNumbers: true,
tabMode: "indent",
matchBrackets: true,
theme: 'neo',
mode: 'css'
}
];
);
// toolbar sections
$(document).on('click', '.mailpoet_toolbar_section.closed', function() {
// close currently opened section
$('.mailpoet_toolbar_section:not(.closed)').addClass('closed');
// open selected section after a mini delay
setTimeout(function() { $(this).removeClass('closed'); }.bind(this), 250);
setTimeout(function() {
$(this).removeClass('closed');
}.bind(this), 250);
return false;
});
// toolbar: open default section
$('.mailpoet_toolbar_section[data-section="settings"]').removeClass('closed');
$('.mailpoet_toolbar_section[data-section="settings"]')
.removeClass('closed');
// form: edit name (in place editor)
$('#mailpoet_form_edit_name').on('click', function() {
@ -282,11 +310,14 @@
})
function mailpoet_edit_form_name() {
var is_editing = $('#mailpoet_form_name').data('mailpoet_editing') || false;
var is_editing = $('#mailpoet_form_name')
.data('mailpoet_editing') || false;
if(!is_editing) {
// set input value and show
$('#mailpoet_form_name_input').val($('#mailpoet_form_name').text()).show();
$('#mailpoet_form_name_input')
.val($('#mailpoet_form_name').text())
.show();
// set editing mode
$('#mailpoet_form_name').data('mailpoet_editing', true);
@ -320,98 +351,92 @@
// save change if necessary
if(new_value !== '' && current_value !== new_value) {
console.log('TODO: form->save', {
form: <%= form.form %>,
form_name: new_value,
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'save',
data: {
id: $('#mailpoet_form_id').val(),
name: new_value
}
}).done(function(response) {
MailPoet.Notice.success(
"<%= __('Form name successfully updated!') %>"
);
});
MailPoet.Notice.success("<%= __('Form name successfully updated!') %>");
}
}
}
// on dom loaded
$(function() {
// load form
WysijaForm.load(<%= form | json_encode | raw %>);
WysijaForm.load(<%= json_encode(form) | raw %>);
// save form
$('#mailpoet_form_save').on('click', function() {
mailpoet_form_save();
mailpoet_form_export();
return false;
});
// preview form
$(document).on('click', '#mailpoet_form_preview', function() {
mailpoet_form_save(mailpoet_form_preview);
//mailpoet_form_save(mailpoet_form_preview);
mailpoet_form_preview();
return false;
});
function mailpoet_form_preview() {
console.log('TODO: form->preview');
/*
MailPoet.Modal.popup({
title: "<%= __('Form preview') %>",
template: $('#mailpoet_form_preview_template').html(),
data: response
});*/
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'previewEditor',
data: WysijaForm.save()
}).done(function(response) {
MailPoet.Modal.popup({
title: "<%= __('Form preview') %>",
template: $('#mailpoet_form_preview_template').html(),
data: response
});
})
}
mailpoet_form_export();
function mailpoet_form_save(callback) {
console.log('TODO: form->save');
var form = WysijaForm.save();
form.id = $('#mailpoet_form_id').val();
// if there is a callback, call it!
if(callback !== undefined) {
callback();
}
/*mailpoet_post_json('form_save.php', {
form: <%= form.form %>,
data: WysijaForm.save()
}, function(response) {
if(response.result === false) {
var error = null;
if(response.error !== undefined) {
// display custom error message
error = $('.mailpoet_error[data-error="'+response.error+'"]');
}
if(error !== null) {
$(error).show();
} else {
// display unknown error message
MailPoet.Notice.error("<%= __('An unknown error occurred. Please try again or get in touch with our support.') %>", { scroll: true });
}
} else {
// if there is a callback, call it!
if(callback !== undefined) {
callback();
} else {
// otherwise display a success message
$success_message = str_replace(array(
'[link_widget]',
'[/link_widget]'
), array(
'<a href="'.admin_url('widgets.php').'" target="_blank">',
'</a>'
),
__('Saved! Add this form to [link_widget]a widget[/link_widget].')
);
?>
var success_message = "<?php echo addslashes($success_message); ?>";
if(response.is_widget === true) {
success_message = "<?php esc_html_e("Saved! The changes are already active in your widget."); ?>";
}
// display success message and scroll to it
MailPoet.Notice.hide(true);
MailPoet.Notice.success(success_message, { scroll: true, static: true });
}
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'saveEditor',
data: form
}).done(function(response) {
if(response === false) {
MailPoet.Notice.error(
"<%= __('An error occured, please reload the page and try again.') %>"
);
return false;
}
});*/
var message = null;
if(response.is_widget === true) {
message = "<%= __('Saved! The changes are already active in your widget.') %>";
} else {
message = "<%= __('Saved! Add this form to %1$sa widget%2$s.') | format("<a href='widgets.php' target='_blank'>", '</a>') | raw %>";
}
if(message !== null) {
MailPoet.Notice.hide();
MailPoet.Notice.success(message, {
scroll: true,
static: (response.is_widget === false)
});
}
// if there is a callback, call it!
if(callback !== undefined) {
callback();
}
});
}
// toolbar: on success toggle
@ -429,8 +454,17 @@
function mailpoet_form_export() {
var template = Handlebars.compile($('#form_template_exports').html());
$('#mailpoet_form_export').html(template({ exports: <%= exports | json_encode | raw %> }));
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'exportsEditor',
data: $('#mailpoet_form_id').val()
}).done(function(response) {
if(response !== false) {
$('#mailpoet_form_export').html(template({ exports: response }));
}
});
}
mailpoet_form_export();
$(document).on('click', '.mailpoet_form_export_toggle', function() {
$('.mailpoet_form_export_output').hide();
@ -455,53 +489,45 @@
// edit field
$(document).on('click', '.mailpoet_form_field_edit', function() {
var field = $(this).data('field');
var id = $(this).data('id');
MailPoet.Modal.popup({
title: "<%= __('Edit field') %>",
template: $('#form_template_field_new').html(),
//url: mailpoet_api_url('subscriber_get_meta.php?field='+field),
onSuccess: function(data) {
console.log('TODO: field->edit');
/*mailpoet_get_json('subscriber_get_meta.php', { field: data.field }, function(data) {
// update field in form
var block_id = $('.mailpoet_form_block[wysija_field="'+field+'"]');
if(block_id.length > 0) {
// redraw block
WysijaForm.get(block_id[0]).block.redraw(data);
// save form
mailpoet_form_save();
}
// toggle widgets
WysijaForm.toggleWidgets();
});*/
MailPoet.Ajax.post({
endpoint: 'customFields',
action: 'get',
data: id
}).done(function(response) {
if(response !== false) {
MailPoet.Modal.popup({
title: "<%= __('Edit field') %>",
template: $('#form_template_field_new').html(),
data: response
});
}
});
});
// delete field
$(document).on('click', '.mailpoet_form_field_delete', function() {
var field = $(this).data('field');
var id = $(this).data('id');
var item = $(this).parent();
var name = $(this).siblings('.mailpoet_form_field').attr('wysija_name');
if(window.confirm("<%= __('Do you really want to delete this custom field?') %>")) {
// delete field
console.log('TODO subscriber->meta->delete');
/*mailpoet_post_json('subscriber_delete_meta.php', { field: field }, function(response) {
var fields = (response.fields !== undefined) ? (response.fields || []) : [];
// refresh toolbar
mailpoet_form_fields({
fields: $.merge(fields, default_fields)
});
// delete field in form
// remove any instance of the field present in the form
var blocks = $$('#mailpoet_form_body .mailpoet_form_block[wysija_field="'+field+'"]');
if(blocks.length > 0) {
var block = WysijaForm.get(blocks[0]);
block.removeBlock();
if(window.confirm(
"<%= __('Do you really want to delete this custom field?') %>"
)) {
MailPoet.Ajax.post({
endpoint: 'customFields',
action: 'delete',
data: id
}).done(function(response) {
if(response === true) {
item.remove();
mailpoet_form_fields();
MailPoet.Notice.success(
"<%= __('Removed custom field “"+name+"“') %>"
);
}
});*/
});
}
});
@ -510,31 +536,28 @@
// pop last element off the history
WysijaHistory.dequeue();
// init wysija form
WysijaForm.init();
return false;
});
// toolbar: list selection
var selected_lists = <%= selected_lists | json_encode | raw %>,
selected_lists_ids = selected_lists.map(function(list) { return parseInt(list.list, 10); });
// enable select2 for list selection
$('select[name="lists"]').select2({
width:'100%'
// get form fields
mailpoet_form_fields().done(function() {
WysijaForm.init();
});
// subscriber meta fields
var meta_fields = [
{"name":"CF text input (mandatory + numbers only)","field":"627e1c8d4fe97712836484e0f2377abd","type":"input","params":{"required":"1","validate":"onlyNumberSp","label":"CF text input (mandatory + numbers only)"}},
{"name":"CF text area (no validation)","field":"6607e6e447b89384c59adc124e979d6b","type":"textarea","params":{"required":"","validate":"","label":"CF text area (no validation)"}},
{"name":"CF text area (letters only)","field":"47a9cae88f8b3b3e23a16990922e7905","type":"textarea","params":{"required":"","validate":"onlyLetterSp","label":"CF text area (letters only)"}},
{"name":"CF radio (mandatory)","field":"5f01b4ccc146fdd6b889bab63dc1b5cd","type":"radio","params":{"values":[{"value":"un"},{"is_checked":"1","value":"deux"},{"value":"trois"}],"required":"1","label":"CF radio (mandatory)"}}
];
// toolbar: segment selection
var selected_segments = <%= form.settings.segments | json_encode | raw %>;
// toolbar: fiels
mailpoet_form_fields({
fields: $.merge(meta_fields, default_fields)
});
// enable select2 for segment selection
$('#mailpoet_form_segments').select2({
width:'100%',
templateResult: function(item) {
if(item.element && item.element.selected) {
return null;
} else {
return item.text;
}
}
}).select2('val', <%= form.settings.segments | json_encode | raw %>);
});
});
</script>
@ -550,39 +573,87 @@
<%= partial('form_template_divider', 'form/templates/blocks/divider.hbs') %>
<%= partial('form_template_input', 'form/templates/blocks/input.hbs') %>
<%= partial('form_template_submit', 'form/templates/blocks/submit.hbs') %>
<%= partial('form_template_list', 'form/templates/blocks/list.hbs') %>
<%= partial('form_template_segment', 'form/templates/blocks/segment.hbs') %>
<%= partial('form_template_radio', 'form/templates/blocks/radio.hbs') %>
<%= partial('form_template_select', 'form/templates/blocks/select.hbs') %>
<%= partial('form_template_checkbox', 'form/templates/blocks/checkbox.hbs') %>
<%= partial('form_template_textarea', 'form/templates/blocks/textarea.hbs') %>
<%= partial('form_template_html', 'form/templates/blocks/html.hbs') %>
<%= partial('form_template_date_years',
'form/templates/blocks/date_years.hbs',
'_settings_date_years'
) %>
<%= partial('form_template_date_months',
'form/templates/blocks/date_months.hbs',
'_settings_date_months'
) %>
<%= partial('form_template_date_days',
'form/templates/blocks/date_days.hbs',
'_settings_date_days'
) %>
<%= partial('form_template_date', 'form/templates/blocks/date.hbs') %>
<%= partial('form_template_date_years', 'form/templates/blocks/date_years.hbs') %>
<%= partial('form_template_date_months', 'form/templates/blocks/date_months.hbs') %>
<%= partial('form_template_date_days', 'form/templates/blocks/date_days.hbs') %>
<!-- field settings -->
<%= partial('form_template_field_settings', 'form/templates/settings/field.hbs') %>
<%= partial('field_settings_label', 'form/templates/settings/label.hbs', '_settings_label') %>
<%= partial('field_settings_label_within', 'form/templates/settings/label_within.hbs', '_settings_label_within') %>
<%= partial('field_settings_required', 'form/templates/settings/required.hbs', '_settings_required') %>
<%= partial('field_settings_validate', 'form/templates/settings/validate.hbs', '_settings_validate') %>
<%= partial('field_settings_values', 'form/templates/settings/values.hbs', '_settings_values') %>
<%= partial('field_settings_date_default', 'form/templates/settings/date_default.hbs', '_settings_date_default') %>
<%= partial('field_settings_submit', 'form/templates/settings/submit.hbs', '_settings_submit') %>
<%= partial('field_settings_label',
'form/templates/settings/label.hbs',
'_settings_label'
) %>
<%= partial('field_settings_label_within',
'form/templates/settings/label_within.hbs',
'_settings_label_within'
) %>
<%= partial('field_settings_required',
'form/templates/settings/required.hbs',
'_settings_required'
) %>
<%= partial('field_settings_validate',
'form/templates/settings/validate.hbs',
'_settings_validate'
) %>
<%= partial('field_settings_values',
'form/templates/settings/values.hbs',
'_settings_values'
) %>
<%= partial('field_settings_date_default',
'form/templates/settings/date_default.hbs',
'_settings_date_default'
) %>
<%= partial('field_settings_submit',
'form/templates/settings/submit.hbs',
'_settings_submit'
) %>
<%= partial('field_settings_values_item', 'form/templates/settings/values_item.hbs') %>
<%= partial('field_settings_date_formats', 'form/templates/settings/date_formats.hbs') %>
<%= partial('field_settings_date_type', 'form/templates/settings/date_types.hbs') %>
<%= partial('field_settings_list_selection_item', 'form/templates/settings/list_selection_item.hbs') %>
<%= partial('field_settings_list_selection', 'form/templates/settings/list_selection.hbs', '_settings_list_selection') %>
<%= partial('field_settings_values_item',
'form/templates/settings/values_item.hbs') %>
<%= partial(
'field_settings_date_format',
'form/templates/settings/date_formats.hbs',
'_settings_date_format'
) %>
<%= partial(
'field_settings_date_type',
'form/templates/settings/date_types.hbs',
'_settings_date_type'
) %>
<%= partial('field_settings_segment_selection_item',
'form/templates/settings/segment_selection_item.hbs'
) %>
<%= partial('field_settings_segment_selection',
'form/templates/settings/segment_selection.hbs',
'_settings_segment_selection'
) %>
<!-- custom field: new -->
<%= partial('form_template_field_new', 'form/templates/settings/field_new.hbs') %>
<%= partial('form_template_field_new',
'form/templates/settings/field_new.hbs'
) %>
<!-- form preview -->
<%= partial('mailpoet_form_preview_template', 'form/templates/preview.hbs') %>
<%= partial('mailpoet_form_preview_template',
'form/templates/preview.hbs'
) %>
<!-- field settings depending on field type -->
<script id="form_template_field_input" type="text/x-handlebars-template">

View File

@ -1,9 +1,9 @@
<div class="mailpoet_form_block"
wysija_type="{{ type }}"
wysija_field="{{ field }}"
{{#if id}}wysija_id="{{ id }}"{{/if}}
wysija_name="{{ name }}"
wysija_unique="{{#if multiple}}0{{else}}1{{/if}}"
wysija_static="{{#if static}}1{{else}}0{{/if}}"
wysija_unique="{{ unique }}"
wysija_static="{{#ifCond static '==' 1}}1{{else}}0{{/ifCond}}"
wysija_params="{{#if params}}{{ json_encode params }}{{/if}}">
{{#ifCond type '!==' 'divider'}}
<div class="wysija_settings" style="display:none;">
@ -12,7 +12,9 @@
{{/ifCond}}
<ul class="wysija_controls clearfix" style="display: none;">
<li class="handle_container"><a class="handle" href="javascript:;"><span></span></a></li>
{{#unless static}}<li><a class="remove" href="javascript:;"><span></span></a></li>{{/unless}}
{{#ifCond static '==' '0'}}
<li><a class="remove" href="javascript:;"><span></span></a></li>
{{/ifCond}}
</ul>
{{{ template }}}
</div>

View File

@ -1,6 +1,6 @@
<% set currentDay = 'now'|date("d") %>
<select id="{{ field }}_days">
<select id="{{ id }}_days">
<% for day in 1..31 %>
<option
{{#if params.is_default_today}}

View File

@ -1,10 +1,11 @@
<% set currentMonth = 'now'|date('n') %>
<select id="{{ field }}_months">
<select id="{{ id }}_months">
<% for month in 1..12 %>
<option
<option
{{#if params.is_default_today}}
{{#ifCond month "==" "<%= currentMonth %>"}}selected="selected"{{/ifCond}}
{{/if}}><%= month |date('1984-' ~ month ~ '-01') |date('F') %></option>
{{/if}}
><%= month |date('1984-' ~ month ~ '-01') |date('F') %></option>
<% endfor %>
</select>

View File

@ -1,7 +1,7 @@
<% set currentYear = "now"|date("Y") %>
<% set minYear = currentYear - 100 %>
<select id="{{ field }}_years">
<select id="{{ id }}_years">
<% for year in currentYear..minYear %>
<option
{{#if params.is_default_today}}

View File

@ -2,6 +2,6 @@
{{#unless params.values}}<p class="mailpoet_error"><%= __('You have to select at least 1 list') %></p>{{/unless}}
{{#each params.values}}
<p>
<input class="mailpoet_checkbox" type="checkbox" data-list-id="{{ id }}" {{#if is_checked}}checked="checked"{{/if}} disabled="disabled" />{{ name }}
<input class="mailpoet_checkbox" type="checkbox" {{#if is_checked}}checked="checked"{{/if}} disabled="disabled" />{{ name }}
</p>
{{/each}}

View File

@ -1,13 +1,13 @@
<% for date_type, formats in date_formats %>
{{#ifCond params.date_type "===" "<%= date_type %>"}}
<% if formats.count == 1 %>
<% if(formats | length == 1) %>
<!-- display format as hidden value -->
<input type="hidden" name="params[date_format]" value="<%= formats[0] %>" />
<% else %>
<!-- display label -->
<p class="clearfix">
<label><%= __('Order') %></label>
// display all possible date formats
<!-- display all possible date formats -->
<select name="params[date_format]">
<% for format in formats %>
<option

View File

@ -6,7 +6,7 @@
{{#ifCond type '==' 'input'}}
{{> _settings_label }}
{{> _settings_label_within }}
{{#ifCond field 'in' 'firstname,lastname' }}
{{#ifCond field 'in' 'first_name,last_name' }}
{{> _settings_required }}
{{/ifCond}}
{{/ifCond}}
@ -31,9 +31,9 @@
{{> _settings_label }}
{{/ifCond}}
{{#ifCond type '==' 'list'}}
{{#ifCond type '==' 'segment'}}
{{> _settings_label }}
{{> _settings_list_selection }}
{{> _settings_segment_selection }}
{{/ifCond}}
{{#ifCond type '==' 'select'}}

View File

@ -1,9 +1,9 @@
<form id="form_field_new" name="form_field_new" action="" method="post">
{{#if field}}<input type="hidden" id="field" name="field" value="{{ field }}" />{{/if}}
{{#if id}}<input type="hidden" id="field_id" name="id" value="{{ id }}" />{{/if}}
<p>
<label for="type"><%= __('Select a field type:') %></label>
<select id="type" name="type">
<label for="field_type"><%= __('Select a field type:') %></label>
<select id="field_type" name="type">
<option value="">--</option>
<option
{{#ifCond type '==' 'input'}}selected="selected"{{/ifCond}}
@ -31,22 +31,10 @@
</option>
</select>
</p>
<p class="mailpoet_error" data-error="type_required">
<%= __('You need to select a type.') %>
</p>
<p>
<label><%= __("Field's name:") %></label>
<input type="text" name="name" value="{{ name }}" />
</p>
<p class="mailpoet_error" data-error="name_required">
<%= __('You need to specify a name.') %>
</p>
<p class="mailpoet_error" data-error="name_already_exists">
<%= __('This name is already used.') %>
</p>
<hr />
<div class="field_type_form"></div>
@ -63,54 +51,57 @@
loadFieldForm();
});
$('#form_field_new').on('submit', function() {
$('#form_field_new').on('submit', function(e) {
e.preventDefault();
// get data
var data = $(this).serializeObject();
MailPoet.Ajax.post({
endpoint: 'customFields',
action: 'save',
data: data
}).done(function(response) {
if(response.result === true) {
mailpoet_form_fields();
if(data.id) {
MailPoet.Notice.success(
"<%= __('Updated custom field “"+data.name+"“') %>"
);
} else {
MailPoet.Notice.success(
"<%= __('Added custom field “"+data.name+"“') %>"
);
}
// hide errors
$('.mailpoet_error').hide();
console.log(('TODO: subscriber->meta->edit'));
// mailpoet_post_json('subscriber_extend.php', data, function(response) {
// if(response.error !== undefined) {
// // there's an error!
// $('.mailpoet_error[data-error="'+response.error+'"]').show();
// } else {
// // we should get the fields list in the response
// if(response.fields !== undefined) {
// // get fields (defaults to empty array)
// var fields = response.fields || [];
// // refresh toolbar
// mailpoet_form_fields({
// fields: $.merge(fields, default_fields)
// });
// }
} else {
if(response.errors.length > 0) {
for(error in response.errors) {
MailPoet.Notice.error(error);
}
}
}
});
// close popup
MailPoet.Modal.success();
// }
// });
return false;
return MailPoet.Modal.success();
});
$('#form_field_new #type').on('change', function() {
if($(this).val() !== '') {
$('.mailpoet_error[data-error="type_required"]').hide();
}
// load template based on type
$('#form_field_new #field_type').on('change', function() {
loadFieldForm($(this).val());
});
function loadFieldForm(type) {
type = (type === undefined) ? $('#form_field_new #type').val() : type;
type = (type === undefined) ? $('#form_field_new #field_type').val() : type;
if(type !== '') {
var template = Handlebars.compile($('#form_template_field_'+type).html()),
data = {type: type},
field = $('#form_field_new #field').val();
field_id = $('#form_field_new #field_id').val();
if(field !== undefined && field.length > 0) {
var params = $('.mailpoet_form_field[wysija_field="'+field+'"]').attr('wysija_params');
data.params = JSON.parse(params);
if(field_id !== undefined && field_id.length > 0) {
var params = $('.mailpoet_form_field[wysija_id="'+field_id+'"]').attr('wysija_params');
if(params !== undefined) {
data.params = JSON.parse(params);
}
}
// render field template
$('#form_field_new .field_type_form').html(template(data));

View File

@ -1,124 +0,0 @@
<p class="mailpoet_error" data-error="user_no_list">
<%= __('You need to specify at least 1 list.') %>
</p>
<ul id="mailpoet_list_selection" class="clearfix"></ul>
<div id="mailpoet_list_available_container">
<h3><%= __('Select the list you want to add:') %></h3>
<select class="mailpoet_list_available"></select>
<a href="javascript:;" class="mailpoet_list_add"><span><%= __('Add') %></span></a>
</div>
<script type="text/javascript">
<% autoescape false %>
var selected_lists = {{#if params.values}}{{{ json_encode params.values }}}
{{else}}<%= default_list | json_encode %>{{/if}},
available_lists = [
<% for list in lists %>
<%= list | json_encode %>,
<% endfor %>
];
<% endautoescape %>
jQuery(function($) {
$(function() {
mailpoet_list_available_render();
mailpoet_list_selection_render();
setInputNames();
// make list selection sortable
Sortable.create('mailpoet_list_selection', {
handles: $$('#mailpoet_list_selection .handle')
});
// add list
$('.mailpoet_list_add').on('click', function() {
// add currently selected list to the selection
var selected_list = $('.mailpoet_list_available :selected');
// add list to selection
selected_lists.push({
list: selected_list.val(),
list_name: selected_list.text(),
is_checked: 0
});
// remove list from available lists
selected_list.remove();
// render selection
mailpoet_list_selection_render();
setInputNames();
});
// remove list
$('#mailpoet_list_selection').on('click', '.remove', function() {
var element = $(this).parents('li');
// remove currently selected list to the selection
var selected_list = parseInt(element.data('list'), 10);
// remove list from selection
selected_lists = selected_lists.filter(function(list) {
return (parseInt(list.id, 10) !== selected_list);
});
// remove element
element.remove();
// render available list
mailpoet_list_available_render();
setInputNames();
});
});
function mailpoet_list_available_render() {
// get selected lists ids
var selected_lists_ids = selected_lists.map(function(list) { return parseInt(list.id, 10); });
// clear available lists
$('.mailpoet_list_available').html('');
// display available lists
$.each(available_lists, function(i, list) {
if($.inArray(list.id, selected_lists_ids) < 0) {
$('.mailpoet_list_available').append('<option value="'+list.id+'">'+list.name+'</option>');
}
});
mailpoet_list_available_toggle();
}
function mailpoet_list_selection_render() {
// list item template
var template = Handlebars.compile($('#field_settings_list_selection_item').html());
// update view
$('#mailpoet_list_selection').html(template({ lists: selected_lists }));
mailpoet_list_available_toggle();
}
function mailpoet_list_available_toggle() {
// toggle visibility of available lists
if($('.mailpoet_list_available option').length === 0) {
$('#mailpoet_list_available_container').hide();
} else {
$('#mailpoet_list_available_container').show();
}
}
function setInputNames() {
$('#mailpoet_list_selection li').each(function(index, item) {
$(item).find('.mailpoet_is_checked').attr('name', 'params[values]['+index+'][is_checked]');
$(item).find('.mailpoet_list_id').attr('name', 'params[values]['+index+'][id]');
$(item).find('.mailpoet_list_name').attr('name', 'params[values]['+index+'][name]');
});
}
});
<{{!}}/script>

View File

@ -0,0 +1,130 @@
<ul id="mailpoet_segment_selection" class="clearfix"></ul>
<div id="mailpoet_segment_available_container">
<h3><%= __('Select the segment you want to add:') %></h3>
<select class="mailpoet_segment_available"></select>
<a href="javascript:;" class="mailpoet_segment_add"><span><%= __('Add') %></span></a>
</div>
<script type="text/javascript">
jQuery(function($) {
<% autoescape false %>
var selected_segments = {{#if params.values}}{{{ json_encode params.values }}}
{{else}}[]{{/if}};
<% endautoescape %>
$(function() {
mailpoet_segment_available_render();
mailpoet_segment_selection_render();
setInputNames();
setupSortableSegments();
// add segment
$('.mailpoet_segment_add').on('click', function() {
// add currently selected segment to the selection
var selected_segment = $('.mailpoet_segment_available :selected');
// add segment to selection
selected_segments.push({
id: selected_segment.val(),
name: selected_segment.text(),
is_checked: 0
});
// remove segment from available segments
selected_segment.remove();
// render selection
mailpoet_segment_selection_render();
setInputNames();
});
// remove segment
$('#mailpoet_segment_selection').on('click', '.remove', function(e) {
if($('#mailpoet_segment_selection').children().length === 1) {
return e.preventDefault();
}
var element = $(this).parents('li');
// remove currently selected segment to the selection
var selected_segment = parseInt(element.data('segment'), 10);
// remove segment from selection
selected_segments = selected_segments.filter(function(segment) {
return (parseInt(segment.id, 10) !== selected_segment);
});
// remove element
element.remove();
// render available segment
mailpoet_segment_available_render();
setInputNames();
});
});
function setupSortableSegments() {
// make segment selection sortable
Sortable.create('mailpoet_segment_selection', {
handles: $$('#mailpoet_segment_selection .handle'),
onChange: function(item) {
setInputNames();
}
});
}
function mailpoet_segment_available_render() {
// clear available segments
$('.mailpoet_segment_available').html('');
var selected_segment_ids = selected_segments.map(function(segment) {
return segment.id;
});
// display available segments
$.each(mailpoet_segments, function(i, segment) {
if($.inArray(segment.id, selected_segment_ids) < 0) {
$('.mailpoet_segment_available').append(
'<option value="'+segment.id+'">'+segment.name+'</option>'
);
}
});
mailpoet_segment_available_toggle();
}
function mailpoet_segment_selection_render() {
// segment item template
var template = Handlebars.compile(
$('#field_settings_segment_selection_item').html()
);
// update view
$('#mailpoet_segment_selection').html(
template({ segments: selected_segments })
);
mailpoet_segment_available_toggle();
}
function mailpoet_segment_available_toggle() {
// toggle visibility of available segments
if($('.mailpoet_segment_available option').length === 0) {
$('#mailpoet_segment_available_container').hide();
} else {
$('#mailpoet_segment_available_container').show();
}
}
function setInputNames() {
$('#mailpoet_segment_selection li').each(function(index, item) {
$(item).find('.mailpoet_is_checked').attr('name', 'params[values]['+index+'][is_checked]');
$(item).find('.mailpoet_segment_id').attr('name', 'params[values]['+index+'][id]');
$(item).find('.mailpoet_segment_name').attr('name', 'params[values]['+index+'][name]');
});
}
});
<{{!}}/script>

View File

@ -1,10 +1,10 @@
{{#each lists}}
<li data-list="{{ id }}">
{{#each segments}}
<li data-segment="{{ id }}">
<label>
<input class="mailpoet_list_id" type="hidden" value="{{ id }}" />
<input class="mailpoet_segment_id" type="hidden" value="{{ id }}" />
<input class="mailpoet_is_checked" type="checkbox" value="1"
{{#ifCond is_checked '>' 0}}checked="checked"{{/ifCond}} />
<input class="mailpoet_list_name" type="hidden" value="{{ name }}" />
<input class="mailpoet_segment_name" type="hidden" value="{{ name }}" />
{{ name }}
</label>
<a class="remove" href="javascript:;"><%= __('Remove') %></a>

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