Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
d85f51e9fc | |||
40a62687cf | |||
136e09e9fb | |||
f509dc0d7e | |||
c100130f39 | |||
9922ecd93c | |||
a4cf2f9c76 | |||
576fbf2085 | |||
5c63971314 | |||
7418923bbc | |||
a8f8134f67 | |||
103da61d45 | |||
01e6a5e6b2 | |||
f5ccf3b38a | |||
c8929351ba | |||
6ca536e9ca | |||
89b04e8691 | |||
588b441fb1 | |||
13dc3577f1 | |||
505b979ac5 | |||
3b4c5c83e1 | |||
056e79eeac | |||
4bde705f04 |
@ -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
|
||||
|
@ -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%
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -23,4 +23,4 @@ $warning-alternate-text-color = #f4c6c8
|
||||
$error-text-color = #d54e21
|
||||
|
||||
// Dimensions
|
||||
$newsletter-width = 600px
|
||||
$newsletter-width = 660px
|
||||
|
@ -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 {
|
||||
|
56
assets/js/src/forms/form.jsx
Normal file
56
assets/js/src/forms/form.jsx
Normal file
@ -0,0 +1,56 @@
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
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
|
29
assets/js/src/forms/forms.jsx
Normal file
29
assets/js/src/forms/forms.jsx
Normal 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);
|
||||
}
|
178
assets/js/src/forms/list.jsx
Normal file
178
assets/js/src/forms/list.jsx
Normal file
@ -0,0 +1,178 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Router, Link, History } 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: 'created_at',
|
||||
label: 'Created on',
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
|
||||
const messages = {
|
||||
onTrash: function(response) {
|
||||
let count = ~~response.forms;
|
||||
let message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
message = (
|
||||
'1 form was moved to the trash.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
message = (
|
||||
'%$1d forms were moved to the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
},
|
||||
onDelete: function(response) {
|
||||
let count = ~~response.forms;
|
||||
let message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
message = (
|
||||
'1 form was permanently deleted.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
message = (
|
||||
'%$1d forms were permanently deleted.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
},
|
||||
onRestore: function(response) {
|
||||
let count = ~~response.forms;
|
||||
let message = null;
|
||||
|
||||
if(count === 1 || response === true) {
|
||||
message = (
|
||||
'1 form has been restored from the trash.'
|
||||
);
|
||||
} else if(count > 1) {
|
||||
message = (
|
||||
'%$1d forms have been restored from the trash.'
|
||||
).replace('%$1d', count);
|
||||
}
|
||||
|
||||
if(message !== null) {
|
||||
MailPoet.Notice.success(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const item_actions = [
|
||||
{
|
||||
name: 'edit',
|
||||
link: function(item) {
|
||||
return (
|
||||
<Link to={ `/edit/${item.id}` }>Edit</Link>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate_form',
|
||||
refresh: true,
|
||||
link: function(item) {
|
||||
return (
|
||||
<a
|
||||
href="javascript:;"
|
||||
onClick={ this.onDuplicate.bind(null, item) }
|
||||
>Duplicate</a>
|
||||
);
|
||||
},
|
||||
onDuplicate: function(item) {
|
||||
MailPoet.Ajax.post({
|
||||
endpoint: 'forms',
|
||||
action: 'duplicate',
|
||||
data: item.id
|
||||
}).done(function() {
|
||||
MailPoet.Notice.success(
|
||||
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
getData: function() {
|
||||
return {
|
||||
confirm: false
|
||||
}
|
||||
},
|
||||
onSuccess: messages.onDelete
|
||||
}
|
||||
];
|
||||
|
||||
const FormList = React.createClass({
|
||||
renderItem: function(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 <Link className="add-new-h2" to="/new">New</Link>
|
||||
</h2>
|
||||
|
||||
<Listing
|
||||
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;
|
@ -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({
|
||||
|
@ -1,57 +1,67 @@
|
||||
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 +73,7 @@ function(
|
||||
|
||||
return (
|
||||
<div className="alignleft actions actions">
|
||||
{ filters }
|
||||
{ available_filters }
|
||||
{ button }
|
||||
</div>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -46,8 +46,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 });
|
||||
@ -77,12 +80,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 +142,7 @@ define(
|
||||
href="javascript:;"
|
||||
onClick={ this.handleDeleteItem.bind(
|
||||
null,
|
||||
this.props.item.id,
|
||||
true
|
||||
this.props.item.id
|
||||
)}
|
||||
>Delete permanently</a>
|
||||
</span>
|
||||
@ -134,10 +163,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 +250,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 +261,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-' + index }
|
||||
item={ item } />
|
||||
);
|
||||
}.bind(this))}
|
||||
@ -248,6 +278,9 @@ define(
|
||||
});
|
||||
|
||||
var Listing = React.createClass({
|
||||
mixins: [
|
||||
Router.History
|
||||
],
|
||||
getInitialState: function() {
|
||||
return {
|
||||
loading: false,
|
||||
@ -260,43 +293,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 +444,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 +473,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 +495,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,
|
||||
@ -393,6 +520,7 @@ define(
|
||||
selection: false,
|
||||
selected_ids: []
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
@ -401,6 +529,7 @@ define(
|
||||
sort_by: sort_by,
|
||||
sort_order: sort_order,
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
@ -460,6 +589,7 @@ define(
|
||||
filter: filters,
|
||||
page: 1
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
@ -469,10 +599,11 @@ define(
|
||||
|
||||
this.setState({
|
||||
group: group,
|
||||
filter: [],
|
||||
filter: {},
|
||||
search: '',
|
||||
page: 1
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
@ -482,6 +613,7 @@ define(
|
||||
selection: false,
|
||||
selected_ids: []
|
||||
}, function() {
|
||||
this.setParams();
|
||||
this.getItems();
|
||||
}.bind(this));
|
||||
},
|
||||
@ -489,17 +621,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 +640,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 +650,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 +694,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 +702,7 @@ define(
|
||||
limit={ this.state.limit }
|
||||
onSetPage={ this.handleSetPage } />
|
||||
</div>
|
||||
<table className={ tableClasses }>
|
||||
<table className={ table_classes }>
|
||||
<thead>
|
||||
<ListingHeader
|
||||
onSort={ this.handleSort }
|
||||
@ -572,6 +718,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 }
|
||||
|
@ -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}
|
||||
|
||||
{previousPage}
|
||||
|
||||
<span className="paging-input">
|
||||
@ -128,6 +129,7 @@ define(['react', 'classnames'], function(React, classNames) {
|
||||
</span>
|
||||
|
||||
{nextPage}
|
||||
|
||||
{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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
225
assets/js/src/newsletters/types/notification.jsx
Normal file
225
assets/js/src/newsletters/types/notification.jsx
Normal 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;
|
||||
}
|
||||
);
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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, Route, 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
|
||||
}
|
||||
);
|
||||
];
|
||||
|
||||
var messages = {
|
||||
onTrash: function(response) {
|
||||
if(response) {
|
||||
var 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) {
|
||||
var 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) {
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
var bulk_actions = [
|
||||
{
|
||||
name: 'trash',
|
||||
label: 'Trash',
|
||||
onSuccess: messages.onTrash
|
||||
}
|
||||
];
|
||||
|
||||
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="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;
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
@ -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((
|
||||
|
@ -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": "*",
|
||||
|
359
composer.lock
generated
359
composer.lock
generated
@ -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",
|
||||
@ -218,6 +218,94 @@
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"time": "2015-09-14 09:18:12"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"version": "v1.5.0",
|
||||
@ -368,16 +456,16 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v1.22.3",
|
||||
"version": "v1.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540"
|
||||
"reference": "5868cd822fd6cf626d5f805439575f9c323cee2a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ebfc36b7e77b0c1175afe30459cf943010245540",
|
||||
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/5868cd822fd6cf626d5f805439575f9c323cee2a",
|
||||
"reference": "5868cd822fd6cf626d5f805439575f9c323cee2a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -390,7 +478,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.22-dev"
|
||||
"dev-master": "1.23-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -425,7 +513,7 @@
|
||||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"time": "2015-10-13 07:07:02"
|
||||
"time": "2015-10-29 23:29:01"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
@ -545,16 +633,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 +656,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 +681,7 @@
|
||||
}
|
||||
],
|
||||
"description": "Modern task runner",
|
||||
"time": "2015-08-31 17:35:30"
|
||||
"time": "2015-10-30 11:29:52"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
@ -867,12 +956,12 @@
|
||||
"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 +990,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 +1960,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 +1978,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 +2010,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 +2057,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 +2079,6 @@
|
||||
"require-dev": {
|
||||
"psr/log": "~1.0",
|
||||
"symfony/event-dispatcher": "~2.1",
|
||||
"symfony/phpunit-bridge": "~2.7",
|
||||
"symfony/process": "~2.1"
|
||||
},
|
||||
"suggest": {
|
||||
@ -2031,28 +2113,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 +2163,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 +2215,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 +2239,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 +2272,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 +2318,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 +2364,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 +2396,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 +2433,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 +2507,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 +2558,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 +2604,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 +2661,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 +2690,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 +2731,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 +2757,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 +2791,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 +2816,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 +2869,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 +2915,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 +3020,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 +3067,7 @@
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"time": "2015-05-30 16:15:01"
|
||||
"time": "2015-10-28 18:53:35"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
@ -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);
|
||||
|
@ -41,6 +41,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'),
|
||||
@ -163,6 +171,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;
|
||||
|
||||
|
@ -21,6 +21,7 @@ class Migrator {
|
||||
'subscriber_custom_field',
|
||||
'newsletter_option_fields',
|
||||
'newsletter_option',
|
||||
'forms'
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,',
|
||||
@ -192,6 +194,19 @@ 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,',
|
||||
'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;
|
||||
|
||||
|
@ -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',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
36
lib/Listing/ItemAction.php
Normal file
36
lib/Listing/ItemAction.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace MailPoet\Listing;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class ItemAction {
|
||||
private $model = null;
|
||||
private $action = null;
|
||||
private $data = null;
|
||||
|
||||
function __construct($model_class, $data) {
|
||||
$id = (int)$data['id'];
|
||||
unset($data['id']);
|
||||
$this->action = $data['action'];
|
||||
unset($data['action']);
|
||||
$this->model = $model_class::findOne($id);
|
||||
if(!empty($data)) {
|
||||
$this->data = $data;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function apply() {
|
||||
if($this->data === null) {
|
||||
return call_user_func_array(
|
||||
array($this->model, $this->action),
|
||||
array()
|
||||
);
|
||||
} else {
|
||||
return call_user_func_array(
|
||||
array($this->model, $this->action),
|
||||
array($this->data)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
112
lib/Models/Form.php
Normal file
112
lib/Models/Form.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?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.')
|
||||
));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
$saved = $form->save();
|
||||
|
||||
if($saved === true) {
|
||||
return true;
|
||||
} else {
|
||||
$errors = $form->getValidationErrors();
|
||||
if(!empty($errors)) {
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
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
|
||||
$forms = $listing->getSelection()->findResultSet();
|
||||
if(!empty($forms)) {
|
||||
$forms_count = 0;
|
||||
foreach($forms as $form) {
|
||||
if($form->delete()) {
|
||||
$forms_count++;
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'segments' => $forms_count
|
||||
);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// soft delete
|
||||
$forms = $listing->getSelection()
|
||||
->findResultSet()
|
||||
->set_expr('deleted_at', 'NOW()')
|
||||
->save();
|
||||
|
||||
return array(
|
||||
'segments' => $forms->count()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static function restore($listing, $data = array()) {
|
||||
$forms = $listing->getSelection()
|
||||
->findResultSet()
|
||||
->set_expr('deleted_at', 'NULL')
|
||||
->save();
|
||||
|
||||
return array(
|
||||
'segments' => $forms->count()
|
||||
);
|
||||
}
|
||||
}
|
@ -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()');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,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 +51,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 +99,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()) {
|
||||
@ -147,9 +143,4 @@ class Newsletter extends Model {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function trash($listing) {
|
||||
return $listing->getSelection()
|
||||
->deleteMany();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
@ -41,12 +38,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()) {
|
||||
@ -77,7 +84,28 @@ class Segment extends Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
static function trash($listing) {
|
||||
return $listing->getSelection()->deleteMany();
|
||||
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 subscribers() {
|
||||
return $this->has_many_through(
|
||||
__NAMESPACE__.'\Subscriber',
|
||||
__NAMESPACE__.'\SubscriberSegment',
|
||||
'segment_id',
|
||||
'subscriber_id'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class Subscriber extends Model {
|
||||
// delete all relations to segments
|
||||
SubscriberSegment::where('subscriber_id', $this->id)->deleteMany();
|
||||
|
||||
parent::delete();
|
||||
return parent::delete();
|
||||
}
|
||||
|
||||
static function search($orm, $search = '') {
|
||||
@ -36,7 +36,6 @@ class Subscriber extends Model {
|
||||
static function filters() {
|
||||
$segments = Segment::orderByAsc('name')->findMany();
|
||||
$segment_list = array();
|
||||
|
||||
$segment_list[] = array(
|
||||
'label' => __('All lists'),
|
||||
'value' => ''
|
||||
@ -53,10 +52,7 @@ class Subscriber extends Model {
|
||||
}
|
||||
|
||||
$filters = array(
|
||||
array(
|
||||
'name' => 'segment',
|
||||
'options' => $segment_list
|
||||
)
|
||||
'segment' => $segment_list
|
||||
);
|
||||
|
||||
return $filters;
|
||||
@ -66,12 +62,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,13 +189,11 @@ class Subscriber extends Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 +203,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 +241,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();
|
||||
@ -320,46 +299,4 @@ 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 restore($listing, $data = array()) {
|
||||
$subscribers = $listing->getSelection()
|
||||
->findResultSet()
|
||||
->set_expr('deleted_at', 'NULL')
|
||||
->save();
|
||||
|
||||
return array(
|
||||
'subscribers' => $subscribers->count()
|
||||
);
|
||||
}
|
||||
}
|
95
lib/Router/Forms.php
Normal file
95
lib/Router/Forms.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace MailPoet\Router;
|
||||
use \MailPoet\Models\Form;
|
||||
use \MailPoet\Listing;
|
||||
|
||||
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 {
|
||||
wp_send_json($form->asArray());
|
||||
}
|
||||
}
|
||||
|
||||
function listing($data = array()) {
|
||||
$listing = new Listing\Handler(
|
||||
'\MailPoet\Models\Form',
|
||||
$data
|
||||
);
|
||||
|
||||
$listing_data = $listing->get();
|
||||
|
||||
wp_send_json($listing_data);
|
||||
}
|
||||
|
||||
function getAll() {
|
||||
$collection = Form::findArray();
|
||||
wp_send_json($collection);
|
||||
}
|
||||
|
||||
function save($data = array()) {
|
||||
$result = Form::createOrUpdate($data);
|
||||
|
||||
if($result !== true) {
|
||||
wp_send_json($result);
|
||||
} else {
|
||||
wp_send_json(true);
|
||||
}
|
||||
}
|
||||
|
||||
function restore($id) {
|
||||
$form = Form::findOne($id);
|
||||
if($form !== false) {
|
||||
$form->set_expr('deleted_at', 'NULL');
|
||||
$result = $form->save();
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
wp_send_json($result);
|
||||
}
|
||||
|
||||
function delete($data = array()) {
|
||||
$form = Form::findOne($data['id']);
|
||||
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
|
||||
if($form !== false) {
|
||||
if($confirm_delete) {
|
||||
$form->delete();
|
||||
$result = true;
|
||||
} else {
|
||||
$form->set_expr('deleted_at', 'NOW()');
|
||||
$result = $form->save();
|
||||
}
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
wp_send_json($result);
|
||||
}
|
||||
|
||||
function duplicate($id) {
|
||||
$result = false;
|
||||
|
||||
$form = Form::duplicate($id);
|
||||
if($form !== false) {
|
||||
$result = $form;
|
||||
}
|
||||
wp_send_json($result);
|
||||
}
|
||||
|
||||
function bulk_action($data = array()) {
|
||||
$bulk_action = new Listing\BulkAction(
|
||||
'\MailPoet\Models\Form',
|
||||
$data
|
||||
);
|
||||
|
||||
wp_send_json($bulk_action->apply());
|
||||
}
|
||||
}
|
@ -88,10 +88,28 @@ class Newsletters {
|
||||
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;
|
||||
}
|
||||
|
@ -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,17 +80,63 @@ 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 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 item_action($data = array()) {
|
||||
$item_action = new Listing\ItemAction(
|
||||
'\MailPoet\Models\Segment',
|
||||
$data
|
||||
);
|
||||
|
||||
wp_send_json($item_action->apply());
|
||||
}
|
||||
|
||||
function bulk_action($data = array()) {
|
||||
$bulk_action = new Listing\BulkAction(
|
||||
'\MailPoet\Models\Segment',
|
||||
|
@ -32,12 +32,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);
|
||||
@ -54,33 +60,48 @@ class Subscribers {
|
||||
}
|
||||
|
||||
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 delete($id) {
|
||||
$result = false;
|
||||
|
||||
$subscriber = Subscriber::findOne($id);
|
||||
if($subscriber !== false) {
|
||||
$subscriber->delete();
|
||||
$result = 1;
|
||||
}
|
||||
|
||||
wp_send_json($result);
|
||||
}
|
||||
|
||||
function item_action($data = array()) {
|
||||
$item_action = new Listing\ItemAction(
|
||||
'\MailPoet\Models\Segment',
|
||||
$data
|
||||
);
|
||||
|
||||
wp_send_json($item_action->apply());
|
||||
}
|
||||
|
||||
function bulk_action($data = array()) {
|
||||
$bulk_action = new Listing\BulkAction(
|
||||
'\MailPoet\Models\Subscriber',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -36,9 +36,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",
|
||||
|
@ -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);
|
||||
|
16
views/forms.html
Normal file
16
views/forms.html
Normal file
@ -0,0 +1,16 @@
|
||||
<% extends 'layout.html' %>
|
||||
|
||||
<% block content %>
|
||||
<div id="forms_container"></div>
|
||||
|
||||
<%= localize({
|
||||
'pageTitle': __('Forms'),
|
||||
'searchLabel': __('Search'),
|
||||
'loadingItems': __('Loading forms...'),
|
||||
'noItemsFound': __('No forms found.')
|
||||
}) %>
|
||||
|
||||
<script type="text/javascript">
|
||||
var mailpoet_segments = <%= json_encode(segments) %>;
|
||||
</script>
|
||||
<% endblock %>
|
@ -14,8 +14,12 @@
|
||||
|
||||
<div class="mailpoet_form_field">
|
||||
<div class="mailpoet_form_field_title"><%= __('Categories & tags:') %></div>
|
||||
<div class="mailpoet_form_field_input_option">
|
||||
<input type="hidden" class="mailpoet_input mailpoet_automated_latest_content_categories_and_tags" value="{{json_encode terms}}" data-selected="{{json_encode terms}}" />
|
||||
<div class="mailpoet_form_field_select_option">
|
||||
<select class="mailpoet_select mailpoet_automated_latest_content_categories_and_tags" multiple="multiple">
|
||||
{{#each terms}}
|
||||
<option value="{{ id }}" selected="selected">{{ text }}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mailpoet_form_field_radio_option">
|
||||
<label>
|
||||
|
@ -14,7 +14,11 @@
|
||||
<option value="private"><%= __('Private') %></option>
|
||||
</select></div>
|
||||
<div class="mailpoet_post_selection_filter_row">
|
||||
<input type="hidden" class="mailpoet_input mailpoet_posts_categories_and_tags" value="{{json_encode terms}}" data-selected="{{json_encode terms}}" data-placeholder="<%= __('Filter by category or tag') %>" />
|
||||
<select class="mailpoet_select mailpoet_posts_categories_and_tags" multiple="multiple">
|
||||
{{#each terms}}
|
||||
<option value="{{ id }}" selected="selected">{{ text }}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mailpoet_post_selection_container">
|
||||
|
@ -21,7 +21,7 @@ baseConfig = {
|
||||
'backbone.supermodel$': 'backbone.supermodel/build/backbone.supermodel.js',
|
||||
'sticky-kit': 'sticky-kit/jquery.sticky-kit',
|
||||
'interact$': 'interact.js/interact.js',
|
||||
'spectrum$': 'spectrum-colorpicker/spectrum.js',
|
||||
'spectrum$': 'spectrum-colorpicker/spectrum.js'
|
||||
},
|
||||
},
|
||||
node: {
|
||||
@ -69,6 +69,7 @@ config.push(_.extend({}, baseConfig, {
|
||||
'subscribers/subscribers.jsx',
|
||||
'newsletters/newsletters.jsx',
|
||||
'segments/segments.jsx',
|
||||
'forms/forms.jsx',
|
||||
'settings/tabs.js'
|
||||
],
|
||||
newsletter_editor: [
|
||||
|
Reference in New Issue
Block a user