diff --git a/assets/js/src/form/fields/selection.jsx b/assets/js/src/form/fields/selection.jsx
index 4a6560a12b..d91fa7c776 100644
--- a/assets/js/src/form/fields/selection.jsx
+++ b/assets/js/src/form/fields/selection.jsx
@@ -15,12 +15,14 @@ function(
}
},
componentWillMount: function() {
- this.loadCachedItems();
+ this.loadCachedItems();
},
componentDidMount: function() {
- jQuery('#'+this.props.id).select2({
- width: '25em'
- });
+ if(this.props.select2) {
+ jQuery('#'+this.props.id).select2({
+ width: '25em'
+ });
+ }
},
loadCachedItems: function() {
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() {
this.setState({
selected: jQuery('#'+this.props.id).val()
@@ -82,11 +54,9 @@ function(
return (
diff --git a/assets/js/src/jquery.serialize_object.js b/assets/js/src/jquery.serialize_object.js
new file mode 100644
index 0000000000..6db1ba9f23
--- /dev/null
+++ b/assets/js/src/jquery.serialize_object.js
@@ -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 $;
+ }
+);
\ No newline at end of file
diff --git a/assets/js/src/newsletters/send.jsx b/assets/js/src/newsletters/send.jsx
index 15054b6e9b..fb8cb82022 100644
--- a/assets/js/src/newsletters/send.jsx
+++ b/assets/js/src/newsletters/send.jsx
@@ -1,6 +1,7 @@
define(
[
'react',
+ 'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
@@ -8,6 +9,7 @@ define(
],
function(
React,
+ Router,
MailPoet,
Form,
Selection,
@@ -32,7 +34,9 @@ define(
+ endpoint="segments"
+ multiple={ true }
+ select2={ true } />
)
},
{
@@ -77,15 +81,42 @@ define(
var messages = {
updated: function() {
- MailPoet.Notice.success('Newsletter succesfully updated!');
+ MailPoet.Notice.success('The newsletter has been updated!');
}
};
var NewsletterSend = React.createClass({
+ mixins: [
+ Router.Navigation
+ ],
handleSend: function() {
- console.log('send.');
- console.log(jQuery('#mailpoet_newsletter').serializeArray());
- console.log(jQuery('#mailpoet_segments').val());
+ MailPoet.Ajax.post({
+ endpoint: 'newsletters',
+ 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("
")
+ );
+ } else {
+ MailPoet.Notice.error(
+ 'An error occurred while trying to send. '+
+ 'Check your settings.'
+ );
+ }
+ }
+ }.bind(this));
},
render: function() {
return (
diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx
index 3a10cbf993..f63b6dadf7 100644
--- a/assets/js/src/subscribers/list.jsx
+++ b/assets/js/src/subscribers/list.jsx
@@ -65,7 +65,7 @@ define(
label: 'Move to list...',
onSelect: function() {
return (
-
);
@@ -81,7 +81,7 @@ define(
label: 'Add to list...',
onSelect: function() {
return (
-
);
@@ -97,7 +97,7 @@ define(
label: 'Remove from list...',
onSelect: function() {
return (
-
);
diff --git a/lib/Mailer/Bridge.php b/lib/Mailer/Bridge.php
index bb429e55c7..c86f602f4a 100644
--- a/lib/Mailer/Bridge.php
+++ b/lib/Mailer/Bridge.php
@@ -6,21 +6,43 @@ use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
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) {
$this->newsletter = $newsletter;
$this->subscribers = $subscribers;
- $this->from_name =
- Setting::where('name', 'from_name')
- ->findOne()->value;
+ $this->from_address = (
+ isset($this->newsletter['from_address'])
+ )
+ ? $this->newsletter['from_address']
+ : Setting::getValue('from_address');
- $this->from_address =
- Setting::where('name', 'from_address')
- ->findOne()->value;
+ $this->from_name = (
+ isset($this->newsletter['from_name'])
+ )
+ ? $this->newsletter['from_name']
+ : Setting::getValue('from_name', '');
- $this->api_key =
- Setting::where('name', 'api_key')
- ->findOne()->value;
+ $this->reply_to_address = (
+ isset($this->newsletter['reply_to_address'])
+ )
+ ? $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() {
@@ -32,19 +54,27 @@ class Bridge {
}
function generateMessage($subscriber) {
- return array(
+ $message = array(
'subject' => $this->newsletter['subject'],
- 'to' => (array(
+ 'to' => array(
'address' => $subscriber['email'],
'name' => $subscriber['first_name'].' '.$subscriber['last_name']
- )),
- 'from' => (array(
+ ),
+ 'from' => array(
'address' => $this->from_address,
'name' => $this->from_name
- )),
+ ),
'text' => "",
'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() {
@@ -74,7 +104,7 @@ class Bridge {
);
$success =
- (wp_remote_retrieve_response_code($result)===201);
+ (wp_remote_retrieve_response_code($result) === 201);
return $success;
}
diff --git a/lib/Models/Setting.php b/lib/Models/Setting.php
index 18e108c40e..d50c75b31c 100644
--- a/lib/Models/Setting.php
+++ b/lib/Models/Setting.php
@@ -9,16 +9,21 @@ class Setting extends Model {
function __construct() {
parent::__construct();
- $this->addValidations("name", array(
- "required" => "name_is_blank",
- "isString" => "name_is_not_string"
- ));
- $this->addValidations("value", array(
- "required" => "value_is_blank",
- "isString" => "value_is_not_string"
+ $this->addValidations('name', array(
+ 'required' => 'name_is_blank',
+ 'isString' => 'name_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) {
$exists = self::where('name', $model['name'])
->find_one();
diff --git a/lib/Router/Newsletters.php b/lib/Router/Newsletters.php
index 63daf3436a..1a3ab07762 100644
--- a/lib/Router/Newsletters.php
+++ b/lib/Router/Newsletters.php
@@ -4,6 +4,7 @@ namespace MailPoet\Router;
use MailPoet\Listing;
use MailPoet\Mailer\Bridge;
use MailPoet\Models\Newsletter;
+use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Newsletter\Renderer\Renderer;
@@ -25,7 +26,7 @@ class Newsletters {
}
function getAll() {
- $collection = Newsletter::find_array();
+ $collection = Newsletter::findArray();
wp_send_json($collection);
}
@@ -48,11 +49,43 @@ class Newsletters {
wp_send_json($result);
}
- function send($id) {
- $newsletter = Newsletter::find_one($id)
- ->as_array();
- $subscribers = Subscriber::find_array();
- $mailer = new Bridge($newsletter, $subscribers);
+ function send($data = array()) {
+ $newsletter = Newsletter::findOne($data['id'])->asArray();
+
+ if(empty($data['segments'])) {
+ 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());
}
diff --git a/webpack.config.js b/webpack.config.js
index 13290009a9..b90589ea77 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -58,7 +58,13 @@ config.push(_.extend({}, baseConfig, {
name: 'admin',
entry: {
vendor: ['handlebars', 'handlebars_helpers'],
- mailpoet: ['mailpoet', 'ajax', 'modal', 'notice'],
+ mailpoet: [
+ 'mailpoet',
+ 'ajax',
+ 'modal',
+ 'notice',
+ 'jquery.serialize_object'
+ ],
admin: [
'settings.jsx',
'subscribers/subscribers.jsx',