diff --git a/assets/js/src/forms/form.jsx b/assets/js/src/forms/form.jsx new file mode 100644 index 0000000000..113b2f52fb --- /dev/null +++ b/assets/js/src/forms/form.jsx @@ -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 ( +
+

+ Form Back to list +

+ +
+
+ ); + } +}); + +module.exports = FormForm \ No newline at end of file diff --git a/assets/js/src/forms/forms.jsx b/assets/js/src/forms/forms.jsx new file mode 100644 index 0000000000..2388b491c6 --- /dev/null +++ b/assets/js/src/forms/forms.jsx @@ -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(( + + + + + + + + + ), container); +} \ No newline at end of file diff --git a/assets/js/src/forms/list.jsx b/assets/js/src/forms/list.jsx new file mode 100644 index 0000000000..a830f65f06 --- /dev/null +++ b/assets/js/src/forms/list.jsx @@ -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 ( + Edit + ); + } + }, + { + name: 'duplicate_form', + refresh: true, + link: function(item) { + return ( + Duplicate + ); + }, + 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 ( +
+ + + { form.name } + + { actions } + + + { segments } + + + { form.created_at } + +
+ ); + }, + render() { + return ( +
+

+ Forms New +

+ + +
+ ); + } +}); + +module.exports = FormList; \ No newline at end of file diff --git a/assets/js/src/listing/bulk_actions.jsx b/assets/js/src/listing/bulk_actions.jsx index b7d44a2359..fcc154d966 100644 --- a/assets/js/src/listing/bulk_actions.jsx +++ b/assets/js/src/listing/bulk_actions.jsx @@ -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({ diff --git a/assets/js/src/listing/filters.jsx b/assets/js/src/listing/filters.jsx index 5617619e07..4bf174db19 100644 --- a/assets/js/src/listing/filters.jsx +++ b/assets/js/src/listing/filters.jsx @@ -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(''); } diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx index e8ac166d1a..f5d192779d 100644 --- a/assets/js/src/listing/listing.jsx +++ b/assets/js/src/listing/listing.jsx @@ -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)) ? ' | ' : ''} ); + } else if(action.link) { + return ( + + { action.link(this.props.item) } + {(index < (custom_actions.length - 1)) ? ' | ' : ''} + + ); } else { return ( - - { action.link(this.props.item) } + + { action.label } {(index < (custom_actions.length - 1)) ? ' | ' : ''} ); @@ -123,8 +142,7 @@ define( href="javascript:;" onClick={ this.handleDeleteItem.bind( null, - this.props.item.id, - true + this.props.item.id )} >Delete permanently @@ -145,10 +163,9 @@ define( Trash @@ -233,7 +250,7 @@ define( - {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); - } + 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 } diff --git a/assets/js/src/newsletters/list.jsx b/assets/js/src/newsletters/list.jsx index ec1e670e54..bc17b093b1 100644 --- a/assets/js/src/newsletters/list.jsx +++ b/assets/js/src/newsletters/list.jsx @@ -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 } ]; diff --git a/assets/js/src/segments/list.jsx b/assets/js/src/segments/list.jsx index d6c7d4b809..cf8861b5d0 100644 --- a/assets/js/src/segments/list.jsx +++ b/assets/js/src/segments/list.jsx @@ -41,58 +41,58 @@ var columns = [ ]; var messages = { - onDelete: function(response) { - var count = ~~response.segments; - var message = null; + 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(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); + if(message !== null) { + MailPoet.Notice.success(message); + } } }, - onConfirmDelete: function(response) { - var count = ~~response.segments; - var message = null; + 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(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); + if(message !== null) { + MailPoet.Notice.success(message); + } } }, onRestore: function(response) { - var count = ~~response.segments; - var message = null; + 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(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); + if(message !== null) { + MailPoet.Notice.success(message); + } } } }; @@ -100,6 +100,7 @@ var messages = { var item_actions = [ { name: 'edit', + label: 'Edit', link: function(item) { return ( Edit @@ -108,24 +109,17 @@ var item_actions = [ }, { name: 'duplicate_segment', - refresh: true, - link: function(item) { - return ( - Duplicate - ); - }, - 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 } ]; diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx index 894d454887..ba34980697 100644 --- a/assets/js/src/subscribers/list.jsx +++ b/assets/js/src/subscribers/list.jsx @@ -38,58 +38,58 @@ const columns = [ ]; const messages = { - onDelete: function(response) { - let count = ~~response.subscribers; - let message = null; + 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); + } - 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); - } - - if(message !== null) { - MailPoet.Notice.success(message); + if(message !== null) { + MailPoet.Notice.success(message); + } } }, - onConfirmDelete: function(response) { - let count = ~~response.subscribers; - let message = null; + 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(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); + if(message !== null) { + MailPoet.Notice.success(message); + } } }, onRestore: function(response) { - let count = ~~response.subscribers; - let message = null; + 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(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); + if(message !== null) { + MailPoet.Notice.success(message); + } } } }; @@ -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 } ]; diff --git a/lib/Config/Initializer.php b/lib/Config/Initializer.php index e64019e7f8..d0ca7ef5bd 100644 --- a/lib/Config/Initializer.php +++ b/lib/Config/Initializer.php @@ -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); diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php index 493f4f6f02..3728188fc4 100644 --- a/lib/Config/Menu.php +++ b/lib/Config/Menu.php @@ -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; diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index 7ed64e4cff..c2a00f000b 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -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; diff --git a/lib/Listing/BulkAction.php b/lib/Listing/BulkAction.php index 6dc435fbed..ebf87bb23a 100644 --- a/lib/Listing/BulkAction.php +++ b/lib/Listing/BulkAction.php @@ -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(); } } \ No newline at end of file diff --git a/lib/Listing/Handler.php b/lib/Listing/Handler.php index 284753eabf..7f2fa588e3 100644 --- a/lib/Listing/Handler.php +++ b/lib/Listing/Handler.php @@ -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() { diff --git a/lib/Listing/ItemAction.php b/lib/Listing/ItemAction.php new file mode 100644 index 0000000000..537a503001 --- /dev/null +++ b/lib/Listing/ItemAction.php @@ -0,0 +1,36 @@ +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) + ); + } + } +} \ No newline at end of file diff --git a/lib/Models/Form.php b/lib/Models/Form.php new file mode 100644 index 0000000000..fcb7f92d76 --- /dev/null +++ b/lib/Models/Form.php @@ -0,0 +1,112 @@ +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() + ); + } +} diff --git a/lib/Models/Model.php b/lib/Models/Model.php index 93716e9be8..98cd7e2910 100644 --- a/lib/Models/Model.php +++ b/lib/Models/Model.php @@ -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()'); diff --git a/lib/Models/Newsletter.php b/lib/Models/Newsletter.php index b004c90b6c..627750ed63 100644 --- a/lib/Models/Newsletter.php +++ b/lib/Models/Newsletter.php @@ -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() - ); - } } diff --git a/lib/Models/Segment.php b/lib/Models/Segment.php index d3b562930b..7936e9427e 100644 --- a/lib/Models/Segment.php +++ b/lib/Models/Segment.php @@ -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++; - } - } - return array( - 'segments' => $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 false; - } else { - // soft delete - $segments = $listing->getSelection() - ->findResultSet() - ->set_expr('deleted_at', 'NOW()') - ->save(); - return array( - 'segments' => $segments->count() - ); + return $duplicate; } + return false; } - 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' ); } } diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index e68227b282..7b024eb04c 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -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) - ->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(); @@ -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) - ->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(); @@ -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() - ); - } } \ No newline at end of file diff --git a/lib/Router/Forms.php b/lib/Router/Forms.php new file mode 100644 index 0000000000..607aabeffe --- /dev/null +++ b/lib/Router/Forms.php @@ -0,0 +1,95 @@ +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()); + } +} diff --git a/lib/Router/Segments.php b/lib/Router/Segments.php index 2fdbdce4a4..ca27ee0f90 100644 --- a/lib/Router/Segments.php +++ b/lib/Router/Segments.php @@ -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); + function trash($id) { + $result = false; + + $segment = Segment::findOne($id); if($segment !== false) { - if($confirm_delete) { - $segment->delete(); - $result = true; - } else { - $segment->set_expr('deleted_at', 'NOW()'); - $result = $segment->save(); - } - } else { - $result = 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', diff --git a/lib/Router/Subscribers.php b/lib/Router/Subscribers.php index cfb8900012..1663f283e0 100644 --- a/lib/Router/Subscribers.php +++ b/lib/Router/Subscribers.php @@ -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); + 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', diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index 4b21026b0a..3cd690db70 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -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); diff --git a/views/forms.html b/views/forms.html new file mode 100644 index 0000000000..c3c4820797 --- /dev/null +++ b/views/forms.html @@ -0,0 +1,16 @@ +<% extends 'layout.html' %> + +<% block content %> +
+ + <%= localize({ + 'pageTitle': __('Forms'), + 'searchLabel': __('Search'), + 'loadingItems': __('Loading forms...'), + 'noItemsFound': __('No forms found.') + }) %> + + +<% endblock %> diff --git a/webpack.config.js b/webpack.config.js index 24e6d593d1..f6838db52d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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: [