basic listing files
This commit is contained in:
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 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: [
|
||||||
|
Router.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, Link } 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);
|
||||||
|
}
|
202
assets/js/src/forms/list.jsx
Normal file
202
assets/js/src/forms/list.jsx
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
define(
|
||||||
|
[
|
||||||
|
'react',
|
||||||
|
'react-router',
|
||||||
|
'listing/listing.jsx',
|
||||||
|
'classnames',
|
||||||
|
'mailpoet'
|
||||||
|
],
|
||||||
|
function(
|
||||||
|
React,
|
||||||
|
Router,
|
||||||
|
Listing,
|
||||||
|
classNames,
|
||||||
|
MailPoet
|
||||||
|
) {
|
||||||
|
var columns = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'created_at',
|
||||||
|
label: 'Created on',
|
||||||
|
sortable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var messages = {
|
||||||
|
onDelete: function(response) {
|
||||||
|
var count = ~~response.segments;
|
||||||
|
var message = null;
|
||||||
|
|
||||||
|
if(count === 1 || response === true) {
|
||||||
|
message = (
|
||||||
|
'1 segment was moved to the trash.'
|
||||||
|
);
|
||||||
|
} else if(count > 1) {
|
||||||
|
message = (
|
||||||
|
'%$1d segments were moved to the trash.'
|
||||||
|
).replace('%$1d', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message !== null) {
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onConfirmDelete: function(response) {
|
||||||
|
var count = ~~response.segments;
|
||||||
|
var message = null;
|
||||||
|
|
||||||
|
if(count === 1 || response === true) {
|
||||||
|
message = (
|
||||||
|
'1 segment was permanently deleted.'
|
||||||
|
);
|
||||||
|
} else if(count > 1) {
|
||||||
|
message = (
|
||||||
|
'%$1d segments were permanently deleted.'
|
||||||
|
).replace('%$1d', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message !== null) {
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRestore: function(response) {
|
||||||
|
var count = ~~response.segments;
|
||||||
|
var message = null;
|
||||||
|
|
||||||
|
if(count === 1 || response === true) {
|
||||||
|
message = (
|
||||||
|
'1 segment has been restored from the trash.'
|
||||||
|
);
|
||||||
|
} else if(count > 1) {
|
||||||
|
message = (
|
||||||
|
'%$1d segments have been restored from the trash.'
|
||||||
|
).replace('%$1d', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message !== null) {
|
||||||
|
MailPoet.Notice.success(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var Link = Router.Link;
|
||||||
|
var item_actions = [
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
link: function(item) {
|
||||||
|
return (
|
||||||
|
<Link to={ `/edit/${item.id}` }>Edit</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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({
|
||||||
|
endpoint: 'segments',
|
||||||
|
action: 'duplicate',
|
||||||
|
data: item.id
|
||||||
|
}).done(function() {
|
||||||
|
MailPoet.Notice.success(
|
||||||
|
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'view_subscribers',
|
||||||
|
link: function(item) {
|
||||||
|
return (
|
||||||
|
<a href={ item.subscribers_url }>View subscribers</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var bulk_actions = [
|
||||||
|
{
|
||||||
|
name: 'trash',
|
||||||
|
label: 'Trash',
|
||||||
|
getData: function() {
|
||||||
|
return {
|
||||||
|
confirm: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: messages.onDelete
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var Link = Router.Link;
|
||||||
|
|
||||||
|
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
|
||||||
|
messages={ messages }
|
||||||
|
search={ false }
|
||||||
|
limit={ 1000 }
|
||||||
|
endpoint="segments"
|
||||||
|
onRenderItem={ this.renderItem }
|
||||||
|
columns={ columns }
|
||||||
|
bulk_actions={ bulk_actions }
|
||||||
|
item_actions={ item_actions }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return SegmentList;
|
||||||
|
}
|
||||||
|
);
|
@ -39,6 +39,7 @@ class Initializer {
|
|||||||
$newsletters = Env::$db_prefix . 'newsletters';
|
$newsletters = Env::$db_prefix . 'newsletters';
|
||||||
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
|
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
|
||||||
$segments = Env::$db_prefix . 'segments';
|
$segments = Env::$db_prefix . 'segments';
|
||||||
|
$forms = Env::$db_prefix . 'forms';
|
||||||
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
|
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
|
||||||
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
|
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
|
||||||
$custom_fields = Env::$db_prefix . 'custom_fields';
|
$custom_fields = Env::$db_prefix . 'custom_fields';
|
||||||
@ -50,6 +51,7 @@ class Initializer {
|
|||||||
define('MP_SETTINGS_TABLE', $settings);
|
define('MP_SETTINGS_TABLE', $settings);
|
||||||
define('MP_NEWSLETTERS_TABLE', $newsletters);
|
define('MP_NEWSLETTERS_TABLE', $newsletters);
|
||||||
define('MP_SEGMENTS_TABLE', $segments);
|
define('MP_SEGMENTS_TABLE', $segments);
|
||||||
|
define('MP_FORMS_TABLE', $forms);
|
||||||
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
|
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
|
||||||
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
|
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
|
||||||
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
|
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
|
||||||
|
@ -41,6 +41,14 @@ class Menu {
|
|||||||
'mailpoet-newsletters',
|
'mailpoet-newsletters',
|
||||||
array($this, 'newsletters')
|
array($this, 'newsletters')
|
||||||
);
|
);
|
||||||
|
add_submenu_page(
|
||||||
|
'mailpoet',
|
||||||
|
__('Forms'),
|
||||||
|
__('Forms'),
|
||||||
|
'manage_options',
|
||||||
|
'mailpoet-forms',
|
||||||
|
array($this, 'forms')
|
||||||
|
);
|
||||||
add_submenu_page(
|
add_submenu_page(
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
__('Subscribers'),
|
__('Subscribers'),
|
||||||
@ -163,6 +171,13 @@ class Menu {
|
|||||||
echo $this->renderer->render('segments.html', $data);
|
echo $this->renderer->render('segments.html', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forms() {
|
||||||
|
$data = array();
|
||||||
|
$data['segments'] = Segment::findArray();
|
||||||
|
|
||||||
|
echo $this->renderer->render('forms.html', $data);
|
||||||
|
}
|
||||||
|
|
||||||
function newsletters() {
|
function newsletters() {
|
||||||
global $wp_roles;
|
global $wp_roles;
|
||||||
|
|
||||||
|
@ -202,8 +202,7 @@ class Migrator {
|
|||||||
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
|
||||||
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
|
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
|
||||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||||
'PRIMARY KEY (id),',
|
'PRIMARY KEY (id)'
|
||||||
'UNIQUE KEY name (name)'
|
|
||||||
);
|
);
|
||||||
return $this->sqlify(__FUNCTION__, $attributes);
|
return $this->sqlify(__FUNCTION__, $attributes);
|
||||||
}
|
}
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,29 @@ class Model extends \Sudzy\ValidModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function restore($orm) {
|
||||||
|
$models = $orm->findResultSet();
|
||||||
|
if(empty($models)) return false;
|
||||||
|
|
||||||
|
$models->setExpr('deleted_at', 'NULL')->save();
|
||||||
|
return $models->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
static function trash($orm, $confirm = false) {
|
||||||
|
$models = $orm->findResultSet();
|
||||||
|
if(empty($models)) return false;
|
||||||
|
|
||||||
|
if($confirm === true) {
|
||||||
|
foreach($models as $model) {
|
||||||
|
$model->delete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$models = $orm->findResultSet();
|
||||||
|
$models->setExpr('deleted_at', 'NOW()')->save();
|
||||||
|
}
|
||||||
|
return $models->count();
|
||||||
|
}
|
||||||
|
|
||||||
private function setTimestamp() {
|
private function setTimestamp() {
|
||||||
if($this->created_at === null) {
|
if($this->created_at === null) {
|
||||||
$this->set_expr('created_at', 'NOW()');
|
$this->set_expr('created_at', 'NOW()');
|
||||||
|
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 %>
|
@ -69,6 +69,7 @@ config.push(_.extend({}, baseConfig, {
|
|||||||
'subscribers/subscribers.jsx',
|
'subscribers/subscribers.jsx',
|
||||||
'newsletters/newsletters.jsx',
|
'newsletters/newsletters.jsx',
|
||||||
'segments/segments.jsx',
|
'segments/segments.jsx',
|
||||||
|
'forms/forms.jsx',
|
||||||
'settings/tabs.js'
|
'settings/tabs.js'
|
||||||
],
|
],
|
||||||
newsletter_editor: [
|
newsletter_editor: [
|
||||||
|
Reference in New Issue
Block a user