Merge pull request #201 from mailpoet/forms

Listing/Model/Router refactoring + Forms
This commit is contained in:
Marco
2015-10-30 11:45:34 +01:00
26 changed files with 914 additions and 425 deletions

View 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

View File

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

View File

@ -0,0 +1,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;

View File

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

View File

@ -36,6 +36,7 @@ function(
let default_value = false;
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}

View File

@ -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 });
@ -86,10 +89,26 @@ define(
{(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 }>
{ action.link(this.props.item) }
<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>
);
@ -123,8 +142,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
true
this.props.item.id
)}
>Delete permanently</a>
</span>
@ -145,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>
@ -233,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);
@ -244,12 +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))}
@ -426,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
@ -435,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);
}
}
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
@ -470,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,
@ -621,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
}
];
}
@ -702,6 +718,7 @@ 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 }

View File

@ -38,7 +38,7 @@ define(
];
var messages = {
onDelete: function(response) {
onTrash: function(response) {
var count = ~~response.newsletters;
var message = null;
@ -56,7 +56,7 @@ define(
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
onDelete: function(response) {
var count = ~~response.newsletters;
var message = null;
@ -98,12 +98,7 @@ define(
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];

View File

@ -41,65 +41,66 @@ var columns = [
];
var messages = {
onDelete: function(response) {
var count = ~~response.segments;
onTrash: function(response) {
if(response) {
var message = null;
if(count === 1 || response === true) {
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(count > 1) {
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', count);
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onConfirmDelete: function(response) {
var count = ~~response.segments;
onDelete: function(response) {
if(response) {
var message = null;
if(count === 1 || response === true) {
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(count > 1) {
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', count);
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
var count = ~~response.segments;
if(response) {
var message = null;
if(count === 1 || response === true) {
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(count > 1) {
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', count);
).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>
@ -108,24 +109,17 @@ var item_actions = [
},
{
name: 'duplicate_segment',
refresh: true,
link: function(item) {
return (
<a
href="javascript:;"
onClick={ this.onDuplicate.bind(null, item) }
>Duplicate</a>
);
},
onDuplicate: function(item) {
MailPoet.Ajax.post({
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function() {
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
},
@ -143,12 +137,7 @@ var bulk_actions = [
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];

View File

@ -38,60 +38,60 @@ const columns = [
];
const messages = {
onDelete: function(response) {
let count = ~~response.subscribers;
let message = null;
if(count === 1) {
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
).replace('%$1d', count);
} else if(count > 1) {
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', count);
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onConfirmDelete: function(response) {
let count = ~~response.subscribers;
let message = null;
if(count === 1) {
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
).replace('%$1d', count);
} else if(count > 1) {
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', count);
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
let count = ~~response.subscribers;
let message = null;
if(count === 1) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
).replace('%$1d', count);
} else if(count > 1) {
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', count);
).replace('%$1d', ~~response);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
const bulk_actions = [
@ -179,8 +179,7 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
.replace('%$1d', ~~response)
);
}
},
@ -190,19 +189,14 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response.subscribers)
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];

View File

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

View File

@ -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;

View File

@ -21,6 +21,7 @@ class Migrator {
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'forms'
);
}
@ -193,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;

View File

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

View File

@ -4,16 +4,13 @@ namespace MailPoet\Listing;
if(!defined('ABSPATH')) exit;
class Handler {
private $data = array();
private $model = null;
function __construct($model_class, $data = array()) {
$class = new \ReflectionClass($model_class);
$this->table_name = $class->getStaticPropertyValue('_table');
$this->model = \Model::factory($model_class);
$this->model = $model_class::select('*');
$this->data = array(
// pagination
'offset' => (isset($data['offset']) ? (int)$data['offset'] : 0),
@ -31,7 +28,7 @@ class Handler {
'selection' => (isset($data['selection']) ? $data['selection'] : null)
);
$this->model = $this->setFilter();
$this->setFilter();
$this->setSearch();
$this->setGroup();
$this->setOrder();
@ -59,9 +56,9 @@ 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() {

View 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
View 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()
);
}
}

View File

@ -9,6 +9,10 @@ class Model extends \Sudzy\ValidModel {
parent::__construct($customValidators->init());
}
static function create() {
return parent::create();
}
function save() {
$this->setTimestamp();
try {
@ -21,6 +25,54 @@ 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->set_expr('created_at', 'NOW()');

View File

@ -143,46 +143,4 @@ class Newsletter 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
$newsletters = $listing->getSelection()->findResultSet();
if(!empty($newsletters)) {
$newsletters_count = 0;
foreach($newsletters as $newsletter) {
if($newsletter->delete()) {
$newsletters_count++;
}
}
return array(
'newsletters' => $newsletters_count
);
}
return false;
} else {
// soft delete
$newsletters = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NOW()')
->save();
return array(
'newsletters' => $newsletters->count()
);
}
}
static function restore($listing, $data = array()) {
$newsletters = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NULL')
->save();
return array(
'newsletters' => $newsletters->count()
);
}
}

View File

@ -17,48 +17,7 @@ class Segment extends Model {
function delete() {
// delete all relations to subscribers
SubscriberSegment::where('segment_id', $this->id)->deleteMany();
return parent::delete();
}
static function duplicate($id) {
$segment = self::findOne($id)->asArray();
if($segment !== false) {
unset($segment['id']);
$new_segment = self::create();
$new_segment->hydrate($segment);
$new_segment->set(
'name',
sprintf(__('Copy of %s'), $new_segment->name)
);
$new_segment->set_expr('created_at', 'NOW()');
$new_segment->set_expr('updated_at', 'NOW()');
$new_segment->save();
$relations = SubscriberSegment::select('subscriber_id')
->where('segment_id', $id)
->findResultSet();
foreach($relations as $relation) {
$new_relation = SubscriberSegment::create();
$new_relation->set('subscriber_id', $relation->subscriber_id);
$new_relation->set('segment_id', $new_segment->id());
$new_relation->save();
}
return true;
}
return false;
}
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
parent::delete();
}
function newsletters() {
@ -125,45 +84,28 @@ class Segment 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
$segments = $listing->getSelection()->findResultSet();
function duplicate($data = array()) {
$duplicate = parent::duplicate($data);
if(!empty($segments)) {
$segments_count = 0;
foreach($segments as $segment) {
if($segment->delete()) {
$segments_count++;
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 array(
'segments' => $segments_count
);
return $duplicate;
}
return false;
} else {
// soft delete
$segments = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NOW()')
->save();
return array(
'segments' => $segments->count()
);
}
}
static function restore($listing, $data = array()) {
$segments = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NULL')
->save();
return array(
'segments' => $segments->count()
function subscribers() {
return $this->has_many_through(
__NAMESPACE__.'\Subscriber',
__NAMESPACE__.'\SubscriberSegment',
'segment_id',
'subscriber_id'
);
}
}

View File

@ -66,7 +66,7 @@ class Subscriber extends Model {
if($key === 'segment') {
$segment = Segment::findOne($value);
if($segment !== false) {
$orm = $segment->subscribers();
return $segment->subscribers();
}
}
}
@ -189,12 +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()->findResultSet();
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
// remove subscriber from all segments
SubscriberSegment::where('subscriber_id', $subscriber->id)->deleteMany();
@ -204,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)
$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();
@ -242,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)
$subscribers = $orm->findResultSet();
foreach($subscribers as $subscriber) {
SubscriberSegment::where('subscriber_id', $subscriber->id)
->whereIn('segment_id', $segment_ids)
->deleteMany();
}
return array(
'subscribers' => count($subscriber_ids)
);
return $subscribers->count();
}
return false;
}
static function confirmUnconfirmed($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();
if(!empty($subscribers)) {
$subscribers_count = 0;
foreach($subscribers as $subscriber) {
$subscriber->set('status', 'subscribed');
if($subscriber->save() === true) {
$subscribers_count++;
}
}
return array(
'subscribers' => $subscribers_count
);
}
return false;
}
static function resendConfirmationEmail($listing) {
$subscriber_ids = $listing->getSelectionIds();
$subscribers = Subscriber::whereIn('id', $subscriber_ids)
->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();
@ -314,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
View 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());
}
}

View File

@ -66,7 +66,7 @@ class Segments {
}
function getAll() {
$collection = Segment::find_array();
$collection = Segment::findArray();
wp_send_json($collection);
}
@ -81,38 +81,62 @@ class Segments {
}
function restore($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$segment->set_expr('deleted_at', 'NULL');
$result = $segment->save();
} else {
$result = false;
$result = $segment->restore();
}
wp_send_json($result);
}
function delete($data = array()) {
$segment = Segment::findOne($data['id']);
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
if($segment !== false) {
if($confirm_delete) {
$segment->delete();
$result = true;
} else {
$segment->set_expr('deleted_at', 'NOW()');
$result = $segment->save();
}
} else {
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 = Segment::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',

View File

@ -60,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);
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 {
function trash($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== 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',

View File

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

16
views/forms.html Normal file
View 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 %>

View File

@ -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: [