Send newsletter + Listing + Last Step

- fixed Selection React
- fixed bulk actions (side effect of muti selection)
- added actual sending of newsletter
- added Setting::getValue($key, $default) in order to get settings
- improved Bridge class to allow override of from/reply_to
- added jquery.serializeObject to ease the pain when posting form data
This commit is contained in:
Jonathan Labreuille
2015-10-02 13:01:27 +02:00
parent 1409167ab5
commit 15d3b8f051
8 changed files with 257 additions and 75 deletions

View File

@ -15,12 +15,14 @@ function(
} }
}, },
componentWillMount: function() { componentWillMount: function() {
this.loadCachedItems(); this.loadCachedItems();
}, },
componentDidMount: function() { componentDidMount: function() {
jQuery('#'+this.props.id).select2({ if(this.props.select2) {
width: '25em' jQuery('#'+this.props.id).select2({
}); width: '25em'
});
}
}, },
loadCachedItems: function() { loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.endpoint]) !== 'undefined') { if(typeof(window['mailpoet_'+this.props.endpoint]) !== 'undefined') {
@ -30,36 +32,6 @@ function(
}); });
} }
}, },
loadItems: function() {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'listing',
data: {
'offset': 0,
'limit': 100,
'search': '',
'sort_by': 'name',
'sort_order': 'asc'
}
})
.done(function(response) {
if(this.isMounted()) {
if(response === false) {
this.setState({
loading: false,
items: []
});
} else {
this.setState({
loading: false,
items: response.items
});
}
}
}.bind(this));
},
handleChange: function() { handleChange: function() {
this.setState({ this.setState({
selected: jQuery('#'+this.props.id).val() selected: jQuery('#'+this.props.id).val()
@ -82,11 +54,9 @@ function(
return ( return (
<select <select
ref="selection" ref="selection"
id={ this.props.id } id={ this.props.id || 'mailpoet_field_selection'}
value={ this.state.selected }
onChange={ this.handleChange }
placeholder={ this.props.placeholder } placeholder={ this.props.placeholder }
multiple multiple={ this.props.multiple }
> >
{ options } { options }
</select> </select>

View File

@ -0,0 +1,107 @@
define(
[
'jquery'
],
function(
$
) {
// Combination of jQuery.deparam and jQuery.serializeObject by Ben Alman.
/*!
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
/*!
* jQuery serializeObject - v0.2 - 1/20/2010
* http://benalman.com/projects/jquery-misc-plugins/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
$.fn.serializeObject = function(coerce) {
var obj = {},
coerce_types = { 'true': !0, 'false': !1, 'null': null };
// Iterate over all name=value pairs.
$.each( this.serializeArray(), function(j,v){
var key = v.name,
val = v.value,
cur = obj,
i = 0,
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts.
keys = key.split( '][' ),
keys_last = keys.length - 1;
// If the first keys part contains [ and the last ends with ], then []
// are correctly balanced.
if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
// Remove the trailing ] from the last keys part.
keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
// Split first keys part into two parts on the [ and add them back onto
// the beginning of the keys array.
keys = keys.shift().split('[').concat( keys );
keys_last = keys.length - 1;
} else {
// Basic 'foo' style key.
keys_last = 0;
}
// Coerce values.
if ( coerce ) {
val = val && !isNaN(val) ? +val // number
: val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string
}
if ( keys_last ) {
// Complex key, build deep object structure based on a few rules:
// * The 'cur' pointer starts at the object top-level.
// * [] = array push (n is set to array length), [n] = array if n is
// numeric, otherwise object.
// * If at the last keys part, set the value.
// * For each keys part, if the current level is undefined create an
// object or array based on the type of the next keys part.
// * Move the 'cur' pointer to the next level.
// * Rinse & repeat.
for ( ; i <= keys_last; i++ ) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
: val;
}
} else {
// Simple key, even simpler rules, since only scalars and shallow
// arrays are allowed.
if ( $.isArray( obj[key] ) ) {
// val is already an array, so push on the next value.
obj[key].push( val );
} else if ( obj[key] !== undefined ) {
// val isn't an array, but since a second value has been specified,
// convert val into an array.
obj[key] = [ obj[key], val ];
} else {
// val is a scalar.
obj[key] = val;
}
}
});
return obj;
};
return $;
}
);

View File

@ -1,6 +1,7 @@
define( define(
[ [
'react', 'react',
'react-router',
'mailpoet', 'mailpoet',
'form/form.jsx', 'form/form.jsx',
'form/fields/selection.jsx', 'form/fields/selection.jsx',
@ -8,6 +9,7 @@ define(
], ],
function( function(
React, React,
Router,
MailPoet, MailPoet,
Form, Form,
Selection, Selection,
@ -32,7 +34,9 @@ define(
<Selection <Selection
placeholder="Select a list" placeholder="Select a list"
id="mailpoet_segments" id="mailpoet_segments"
endpoint="segments" /> endpoint="segments"
multiple={ true }
select2={ true } />
) )
}, },
{ {
@ -77,15 +81,42 @@ define(
var messages = { var messages = {
updated: function() { updated: function() {
MailPoet.Notice.success('Newsletter succesfully updated!'); MailPoet.Notice.success('The newsletter has been updated!');
} }
}; };
var NewsletterSend = React.createClass({ var NewsletterSend = React.createClass({
mixins: [
Router.Navigation
],
handleSend: function() { handleSend: function() {
console.log('send.'); MailPoet.Ajax.post({
console.log(jQuery('#mailpoet_newsletter').serializeArray()); endpoint: 'newsletters',
console.log(jQuery('#mailpoet_segments').val()); action: 'send',
data: {
id: this.props.params.id,
newsletter: jQuery('#mailpoet_newsletter').serializeObject(),
segments: jQuery('#mailpoet_segments').val()
}
}).done(function(response) {
if(response === true) {
this.transitionTo('/');
MailPoet.Notice.success(
'The newsletter has been sent!'
);
} else {
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
}
}
}.bind(this));
}, },
render: function() { render: function() {
return ( return (

View File

@ -65,7 +65,7 @@ define(
label: 'Move to list...', label: 'Move to list...',
onSelect: function() { onSelect: function() {
return ( return (
<ItemSelection <Selection
endpoint="segments" endpoint="segments"
id="move_to_segment" /> id="move_to_segment" />
); );
@ -81,7 +81,7 @@ define(
label: 'Add to list...', label: 'Add to list...',
onSelect: function() { onSelect: function() {
return ( return (
<ItemSelection <Selection
endpoint="segments" endpoint="segments"
id="add_to_segment" /> id="add_to_segment" />
); );
@ -97,7 +97,7 @@ define(
label: 'Remove from list...', label: 'Remove from list...',
onSelect: function() { onSelect: function() {
return ( return (
<ItemSelection <Selection
endpoint="segments" endpoint="segments"
id="remove_from_segment" /> id="remove_from_segment" />
); );

View File

@ -6,21 +6,43 @@ use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Bridge { class Bridge {
protected $from_address = null;
protected $from_name = '';
protected $reply_to_address = null;
protected $reply_to_name = '';
protected $newsletter = null;
protected $subscribers = null;
protected $api_key = null;
function __construct($newsletter, $subscribers) { function __construct($newsletter, $subscribers) {
$this->newsletter = $newsletter; $this->newsletter = $newsletter;
$this->subscribers = $subscribers; $this->subscribers = $subscribers;
$this->from_name = $this->from_address = (
Setting::where('name', 'from_name') isset($this->newsletter['from_address'])
->findOne()->value; )
? $this->newsletter['from_address']
: Setting::getValue('from_address');
$this->from_address = $this->from_name = (
Setting::where('name', 'from_address') isset($this->newsletter['from_name'])
->findOne()->value; )
? $this->newsletter['from_name']
: Setting::getValue('from_name', '');
$this->api_key = $this->reply_to_address = (
Setting::where('name', 'api_key') isset($this->newsletter['reply_to_address'])
->findOne()->value; )
? $this->newsletter['reply_to_address']
: Setting::getValue('reply_to_address');
$this->reply_to_name = (
isset($this->newsletter['reply_to_name'])
)
? $this->newsletter['reply_to_name']
: Setting::getValue('reply_to_name', '');
$this->api_key = Setting::where('name', 'api_key')->findOne()->value;
} }
function messages() { function messages() {
@ -32,19 +54,27 @@ class Bridge {
} }
function generateMessage($subscriber) { function generateMessage($subscriber) {
return array( $message = array(
'subject' => $this->newsletter['subject'], 'subject' => $this->newsletter['subject'],
'to' => (array( 'to' => array(
'address' => $subscriber['email'], 'address' => $subscriber['email'],
'name' => $subscriber['first_name'].' '.$subscriber['last_name'] 'name' => $subscriber['first_name'].' '.$subscriber['last_name']
)), ),
'from' => (array( 'from' => array(
'address' => $this->from_address, 'address' => $this->from_address,
'name' => $this->from_name 'name' => $this->from_name
)), ),
'text' => "", 'text' => "",
'html' => $this->newsletter['body'] 'html' => $this->newsletter['body']
); );
if($this->reply_to_address !== null) {
$message['reply_to'] = array(
'address' => $this->reply_to_address,
'name' => $this->reply_to_name
);
}
return $message;
} }
function auth() { function auth() {
@ -74,7 +104,7 @@ class Bridge {
); );
$success = $success =
(wp_remote_retrieve_response_code($result)===201); (wp_remote_retrieve_response_code($result) === 201);
return $success; return $success;
} }

View File

@ -9,16 +9,21 @@ class Setting extends Model {
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
$this->addValidations("name", array( $this->addValidations('name', array(
"required" => "name_is_blank", 'required' => 'name_is_blank',
"isString" => "name_is_not_string" 'isString' => 'name_is_not_string'
));
$this->addValidations("value", array(
"required" => "value_is_blank",
"isString" => "value_is_not_string"
)); ));
} }
public static function getValue($key, $default = null) {
$setting = Setting::where('name', $key)->findOne();
if($setting === false) {
return $default;
} else {
return $setting->value;
}
}
public static function createOrUpdate($model) { public static function createOrUpdate($model) {
$exists = self::where('name', $model['name']) $exists = self::where('name', $model['name'])
->find_one(); ->find_one();

View File

@ -4,6 +4,7 @@ namespace MailPoet\Router;
use MailPoet\Listing; use MailPoet\Listing;
use MailPoet\Mailer\Bridge; use MailPoet\Mailer\Bridge;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Models\NewsletterTemplate; use MailPoet\Models\NewsletterTemplate;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
@ -25,7 +26,7 @@ class Newsletters {
} }
function getAll() { function getAll() {
$collection = Newsletter::find_array(); $collection = Newsletter::findArray();
wp_send_json($collection); wp_send_json($collection);
} }
@ -48,11 +49,43 @@ class Newsletters {
wp_send_json($result); wp_send_json($result);
} }
function send($id) { function send($data = array()) {
$newsletter = Newsletter::find_one($id) $newsletter = Newsletter::findOne($data['id'])->asArray();
->as_array();
$subscribers = Subscriber::find_array(); if(empty($data['segments'])) {
$mailer = new Bridge($newsletter, $subscribers); return wp_send_json(array(
'errors' => array(
__("You need to select a list.")
)
));
}
$segments = Segment::whereIdIn($data['segments'])->findMany();
$subscribers = array();
foreach($segments as $segment) {
$segment_subscribers = $segment->subscribers()->findMany();
foreach($segment_subscribers as $segment_subscriber) {
$subscribers[$segment_subscriber->email] = $segment_subscriber
->asArray();
}
}
if(empty($subscribers)) {
return wp_send_json(array(
'errors' => array(
__("No subscribers found.")
)
));
}
// TO REMOVE once we add the columns from/reply_to
$newsletter = array_merge($newsletter, $data['newsletter']);
// END - TO REMOVE
$renderer = new Renderer(json_decode($newsletter['body'], true));
$newsletter['body'] = $renderer->renderAll();
$mailer = new Bridge($newsletter, array_values($subscribers));
wp_send_json($mailer->send()); wp_send_json($mailer->send());
} }

View File

@ -58,7 +58,13 @@ config.push(_.extend({}, baseConfig, {
name: 'admin', name: 'admin',
entry: { entry: {
vendor: ['handlebars', 'handlebars_helpers'], vendor: ['handlebars', 'handlebars_helpers'],
mailpoet: ['mailpoet', 'ajax', 'modal', 'notice'], mailpoet: [
'mailpoet',
'ajax',
'modal',
'notice',
'jquery.serialize_object'
],
admin: [ admin: [
'settings.jsx', 'settings.jsx',
'subscribers/subscribers.jsx', 'subscribers/subscribers.jsx',