diff --git a/assets/css/src/admin.styl b/assets/css/src/admin.styl
index 32bed150e9..cbc530bd5e 100644
--- a/assets/css/src/admin.styl
+++ b/assets/css/src/admin.styl
@@ -17,3 +17,5 @@
@require 'settings'
@require 'progress_bar'
+
+@require 'subscribers'
\ No newline at end of file
diff --git a/assets/css/src/subscribers.styl b/assets/css/src/subscribers.styl
new file mode 100644
index 0000000000..511f7f5a57
--- /dev/null
+++ b/assets/css/src/subscribers.styl
@@ -0,0 +1,3 @@
+#subscribers_container
+ .mailpoet_segments_unsubscribed
+ color: lighten(#555, 33)
\ No newline at end of file
diff --git a/assets/js/src/form/fields/selection.jsx b/assets/js/src/form/fields/selection.jsx
index 8b50caf3c3..66fccbd56a 100644
--- a/assets/js/src/form/fields/selection.jsx
+++ b/assets/js/src/form/fields/selection.jsx
@@ -26,7 +26,7 @@ function(
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#'+this.refs.select.id)
- .val(this.props.item[this.props.field.name])
+ .val(this.getSelectedValues())
.trigger('change');
}
},
@@ -45,7 +45,11 @@ function(
if(item.element && item.element.selected) {
return null;
} else {
- return item.text;
+ if(item.title) {
+ return item.title;
+ } else {
+ return item.text;
+ }
}
}
});
@@ -65,15 +69,25 @@ function(
select2.select2(
'val',
- this.props.item[this.props.field.name]
+ this.getSelectedValues()
);
this.setState({ initialized: true });
},
+ getSelectedValues: function() {
+ if(this.props.field['selected'] !== undefined) {
+ return this.props.field['selected'](this.props.item);
+ } else if(this.props.item !== undefined && this.props.field.name !== undefined) {
+ return this.props.item[this.props.field.name];
+ } else {
+ return null;
+ }
+ },
loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint];
+
if(this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter);
}
@@ -98,31 +112,48 @@ function(
});
}
},
+ getLabel: function(item) {
+ if(this.props.field['getLabel'] !== undefined) {
+ return this.props.field.getLabel(item, this.props.item);
+ }
+ return item.name;
+ },
+ getSearchLabel: function(item) {
+ if(this.props.field['getSearchLabel'] !== undefined) {
+ return this.props.field.getSearchLabel(item, this.props.item);
+ }
+ return null;
+ },
+ getValue: function(item) {
+ if(this.props.field['getValue'] !== undefined) {
+ return this.props.field.getValue(item, this.props.item);
+ }
+ return item.id;
+ },
render: function() {
- var options = this.state.items.map(function(item, index) {
+ const options = this.state.items.map((item, index) => {
+ let label = this.getLabel(item);
+ let searchLabel = this.getSearchLabel(item);
+ let value = this.getValue(item);
+
return (
);
});
- var default_value = (
- (this.props.item !== undefined && this.props.field.name !== undefined)
- ? this.props.item[this.props.field.name]
- : null
- );
-
return (
);
diff --git a/assets/js/src/segments/list.jsx b/assets/js/src/segments/list.jsx
index 6574e62d8d..a98ebb0260 100644
--- a/assets/js/src/segments/list.jsx
+++ b/assets/js/src/segments/list.jsx
@@ -181,13 +181,13 @@ const SegmentList = React.createClass({
{ segment.description }
- { segment.subscribed || 0 }
+ { segment.subscribers_count.subscribed || 0 }
|
- { segment.unconfirmed || 0 }
+ { segment.subscribers_count.unconfirmed || 0 }
|
- { segment.unsubscribed || 0 }
+ { segment.subscribers_count.unsubscribed || 0 }
|
{ segment.created_at }
diff --git a/assets/js/src/subscribers/form.jsx b/assets/js/src/subscribers/form.jsx
index 203cad7923..94293250ae 100644
--- a/assets/js/src/subscribers/form.jsx
+++ b/assets/js/src/subscribers/form.jsx
@@ -3,15 +3,16 @@ define(
'react',
'react-router',
'mailpoet',
- 'form/form.jsx'
+ 'form/form.jsx',
+ 'moment'
],
function(
React,
Router,
MailPoet,
- Form
+ Form,
+ Moment
) {
-
var fields = [
{
name: 'email',
@@ -45,8 +46,38 @@ define(
placeholder: "Select a list",
endpoint: "segments",
multiple: true,
+ selected: function(subscriber) {
+ if (Array.isArray(subscriber.subscriptions) === false) {
+ return null;
+ }
+
+ return subscriber.subscriptions.map(function(subscription) {
+ if (subscription.status === 'subscribed') {
+ return subscription.segment_id;
+ }
+ });
+ },
filter: function(segment) {
return !!(!segment.deleted_at);
+ },
+ getSearchLabel: function(segment, subscriber) {
+ let label = '';
+
+ if (subscriber.subscriptions !== undefined) {
+ subscriber.subscriptions.map(function(subscription) {
+ if (segment.id === subscription.segment_id) {
+ label = segment.name;
+
+ if (subscription.status === 'unsubscribed') {
+ const unsubscribed_at = Moment(subscription.updated_at)
+ .utcOffset(parseInt(mailpoet_date_offset))
+ .format('ddd, D MMM YYYY HH:mm:ss');
+ label += ' (Unsubscribed on '+unsubscribed_at+')';
+ }
+ }
+ });
+ }
+ return label;
}
}
];
@@ -58,11 +89,11 @@ define(
label: custom_field.name,
type: custom_field.type
};
- if(custom_field.params) {
+ if (custom_field.params) {
field.params = custom_field.params;
}
- if(custom_field.params.values) {
+ if (custom_field.params.values) {
field.values = custom_field.params.values;
}
diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx
index 9993b4fd0c..a6c5120e73 100644
--- a/assets/js/src/subscribers/list.jsx
+++ b/assets/js/src/subscribers/list.jsx
@@ -231,6 +231,15 @@ const item_actions = [
];
const SubscriberList = React.createClass({
+ getSegmentFromId: function(segment_id) {
+ let result = false;
+ mailpoet_segments.map(function(segment) {
+ if (segment.id === segment_id) {
+ result = segment;
+ }
+ });
+ return result;
+ },
renderItem: function(subscriber, actions) {
let row_classes = classNames(
'manage-column',
@@ -255,11 +264,41 @@ const SubscriberList = React.createClass({
break;
}
- let segments = mailpoet_segments.filter(function(segment) {
- return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
- }).map(function(segment) {
- return segment.name;
- }).join(', ');
+ let segments = false;
+
+ if (subscriber.subscriptions.length > 0) {
+ let subscribed_segments = [];
+ let unsubscribed_segments = [];
+
+ subscriber.subscriptions.map((subscription) => {
+ const segment = this.getSegmentFromId(subscription.segment_id);
+ if (subscription.status === 'subscribed') {
+ subscribed_segments.push(segment.name);
+ } else {
+ unsubscribed_segments.push(segment.name);
+ }
+ });
+
+ segments = (
+
+
+ { subscribed_segments.join(', ') }
+ {
+ (
+ subscribed_segments.length > 0
+ && unsubscribed_segments.length > 0
+ ) ? ' / ' : ''
+ }
+
+
+ { unsubscribed_segments.join(', ') }
+
+
+ );
+ }
let avatar = false;
if(subscriber.avatar_url) {
diff --git a/lib/Form/Util/Export.php b/lib/Form/Util/Export.php
index 53edb33b0a..92c65d4a05 100644
--- a/lib/Form/Util/Export.php
+++ b/lib/Form/Util/Export.php
@@ -22,19 +22,21 @@ class Export {
), site_url());
// generate iframe
- return '';
+ return join(' ', array(
+ ''
+ ));
break;
case 'php':
diff --git a/lib/Listing/Handler.php b/lib/Listing/Handler.php
index 7656cbb491..ba10bb8438 100644
--- a/lib/Listing/Handler.php
+++ b/lib/Listing/Handler.php
@@ -89,7 +89,7 @@ class Handler {
$items = $this->model
->offset($this->data['offset'])
->limit($this->data['limit'])
- ->findArray();
+ ->findMany();
return array(
'count' => $count,
diff --git a/lib/Models/Newsletter.php b/lib/Models/Newsletter.php
index 74273934d0..628019fbca 100644
--- a/lib/Models/Newsletter.php
+++ b/lib/Models/Newsletter.php
@@ -52,6 +52,11 @@ class Newsletter extends Model {
);
}
+ function withSegments() {
+ $this->segments = $this->segments()->findArray();
+ return $this;
+ }
+
function options() {
return $this->has_many_through(
__NAMESPACE__.'\NewsletterOptionField',
@@ -67,6 +72,12 @@ class Newsletter extends Model {
->findOne();
}
+ function withSendingQueue() {
+ $this->queue = $this->getQueue();
+ return $this;
+ }
+
+
static function search($orm, $search = '') {
return $orm->where_like('subject', '%' . $search . '%');
}
diff --git a/lib/Models/Segment.php b/lib/Models/Segment.php
index 41ef874e1a..6e14ad8df3 100644
--- a/lib/Models/Segment.php
+++ b/lib/Models/Segment.php
@@ -38,15 +38,6 @@ class Segment extends Model {
);
}
- function segmentFilters() {
- return $this->has_many_through(
- __NAMESPACE__.'\Filter',
- __NAMESPACE__.'\SegmentFilter',
- 'segment_id',
- 'filter_id'
- );
- }
-
function duplicate($data = array()) {
$duplicate = parent::duplicate($data);
@@ -76,6 +67,32 @@ class Segment extends Model {
->delete();
}
+ function withSubscribersCount() {
+ $this->subscribers_count = SubscriberSegment::table_alias('relation')
+ ->where('relation.segment_id', $this->id)
+ ->join(
+ MP_SUBSCRIBERS_TABLE,
+ 'subscribers.id = relation.subscriber_id',
+ 'subscribers'
+ )
+ ->select_expr(
+ 'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
+ 'subscribed'
+ )
+ ->select_expr(
+ 'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
+ 'unsubscribed'
+ )
+ ->select_expr(
+ 'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
+ 'unconfirmed'
+ )
+ ->findOne()
+ ->asArray();
+
+ return $this;
+ }
+
static function getWPUsers() {
return self::where('type', 'wp_users')->findOne();
}
diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php
index 82f5d0ca82..d9de919ffe 100644
--- a/lib/Models/Subscriber.php
+++ b/lib/Models/Subscriber.php
@@ -291,7 +291,7 @@ class Subscriber extends Model {
}
}
if($segment_ids !== false) {
- $subscriber->addToSegments($segment_ids);
+ SubscriberSegment::setSubscriptions($subscriber, $segment_ids);
}
}
return $subscriber;
@@ -314,6 +314,17 @@ class Subscriber extends Model {
return $this;
}
+ function withSegments() {
+ $this->segments = $this->segments()->findArray();
+ return $this;
+ }
+
+ function withSubscriptions() {
+ $this->subscriptions = SubscriberSegment::where('subscriber_id', $this->id())
+ ->findArray();
+ return $this;
+ }
+
function getCustomField($custom_field_id, $default = null) {
$custom_field = SubscriberCustomField::select('value')
->where('custom_field_id', $custom_field_id)
diff --git a/lib/Models/SubscriberSegment.php b/lib/Models/SubscriberSegment.php
index c538c23602..90f3a6d419 100644
--- a/lib/Models/SubscriberSegment.php
+++ b/lib/Models/SubscriberSegment.php
@@ -12,6 +12,28 @@ class SubscriberSegment extends Model {
parent::__construct();
}
+ static function setSubscriptions($subscriber, $segment_ids = array()) {
+ if($subscriber->id > 0) {
+ // unsubscribe from current subscriptions
+ SubscriberSegment::where('subscriber_id', $subscriber->id)
+ ->whereNotIn('segment_id', $segment_ids)
+ ->findResultSet()
+ ->set('status', 'unsubscribed')
+ ->save();
+
+ // subscribe to segments
+ foreach($segment_ids as $segment_id) {
+ self::createOrUpdate(array(
+ 'subscriber_id' => $subscriber->id,
+ 'segment_id' => $segment_id,
+ 'status' => 'subscribed'
+ ));
+ }
+ }
+
+ return $subscriber;
+ }
+
static function filterWithCustomFields($orm) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
$customFields = CustomField::findArray();
@@ -37,6 +59,30 @@ class SubscriberSegment extends Model {
return $orm->where('status', 'subscribed');
}
+ static function createOrUpdate($data = array()) {
+ $subscription = false;
+
+ if(isset($data['id']) && (int)$data['id'] > 0) {
+ $subscription = self::findOne((int)$data['id']);
+ }
+
+ if(isset($data['subscriber_id']) && isset($data['segment_id'])) {
+ $subscription = self::where('subscriber_id', (int)$data['subscriber_id'])
+ ->where('segment_id', (int)$data['segment_id'])
+ ->findOne();
+ }
+
+ if($subscription === false) {
+ $subscription = self::create();
+ $subscription->hydrate($data);
+ } else {
+ unset($data['id']);
+ $subscription->set($data);
+ }
+
+ return $subscription->save();
+ }
+
static function createMultiple($segmnets, $subscribers) {
$values = Helpers::flattenArray(
array_map(function ($segment) use ($subscribers) {
diff --git a/lib/Router/Forms.php b/lib/Router/Forms.php
index c47869864a..af73bdcee8 100644
--- a/lib/Router/Forms.php
+++ b/lib/Router/Forms.php
@@ -13,11 +13,10 @@ class Forms {
function get($id = false) {
$form = Form::findOne($id);
- if($form === false) {
- return false;
- } else {
- return $form->asArray();
+ if($form !== false) {
+ $form = $form->asArray();
}
+ return $form;
}
function listing($data = array()) {
@@ -29,19 +28,14 @@ class Forms {
$listing_data = $listing->get();
// fetch segments relations for each returned item
- foreach($listing_data['items'] as &$item) {
- // form's segments
- $form_settings = (
- (is_serialized($item['settings']))
- ? unserialize($item['settings'])
- : array()
- );
-
- $item['segments'] = (
- !empty($form_settings['segments'])
- ? $form_settings['segments']
+ foreach($listing_data['items'] as $key => $form) {
+ $form = $form->asArray();
+ $form['segments'] = (
+ !empty($form['settings']['segments'])
+ ? $form['settings']['segments']
: array()
);
+ $listing_data['items'][$key] = $form;
}
return $listing_data;
diff --git a/lib/Router/Newsletters.php b/lib/Router/Newsletters.php
index 61084a444d..2f40669a52 100644
--- a/lib/Router/Newsletters.php
+++ b/lib/Router/Newsletters.php
@@ -206,20 +206,11 @@ class Newsletters {
$listing_data = $listing->get();
- foreach($listing_data['items'] as &$item) {
- // get segments
- $segments = NewsletterSegment::select('segment_id')
- ->where('newsletter_id', $item['id'])
- ->findMany();
- $item['segments'] = array_map(function($relation) {
- return $relation->segment_id;
- }, $segments);
-
- // get queue
- $queue = SendingQueue::where('newsletter_id', $item['id'])
- ->orderByDesc('updated_at')
- ->findOne();
- $item['queue'] = ($queue !== false) ? $queue->asArray() : null;
+ foreach($listing_data['items'] as $key => $newsletter) {
+ $listing_data['items'][$key] = $newsletter
+ ->withSegments()
+ ->withSendingQueue()
+ ->asArray();
}
return $listing_data;
diff --git a/lib/Router/Segments.php b/lib/Router/Segments.php
index 687f6dde86..93ffdfb11a 100644
--- a/lib/Router/Segments.php
+++ b/lib/Router/Segments.php
@@ -30,36 +30,14 @@ class Segments {
$listing_data = $listing->get();
// fetch segments relations for each returned item
- foreach($listing_data['items'] as &$item) {
- $stats = SubscriberSegment::table_alias('relation')
- ->where(
- 'relation.segment_id',
- $item['id']
- )
- ->join(
- MP_SUBSCRIBERS_TABLE,
- 'subscribers.id = relation.subscriber_id',
- 'subscribers'
- )
- ->select_expr(
- 'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
- 'subscribed'
- )
- ->select_expr(
- 'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
- 'unsubscribed'
- )
- ->select_expr(
- 'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
- 'unconfirmed'
- )
- ->findOne()->asArray();
-
- $item = array_merge($item, $stats);
-
- $item['subscribers_url'] = admin_url(
- 'admin.php?page=mailpoet-subscribers#/filter[segment='.$item['id'].']'
+ foreach($listing_data['items'] as $key => $segment) {
+ $segment->subscribers_url = admin_url(
+ 'admin.php?page=mailpoet-subscribers#/filter[segment='.$segment->id.']'
);
+
+ $listing_data['items'][$key] = $segment
+ ->withSubscribersCount()
+ ->asArray();
}
return $listing_data;
diff --git a/lib/Router/Subscribers.php b/lib/Router/Subscribers.php
index e3b8a2940f..a1aa12376d 100644
--- a/lib/Router/Subscribers.php
+++ b/lib/Router/Subscribers.php
@@ -17,15 +17,12 @@ class Subscribers {
function get($id = false) {
$subscriber = Subscriber::findOne($id);
- if($subscriber !== false && $subscriber->id() > 0) {
- $segments = $subscriber->segments()->findArray();
-
- $subscriber = $subscriber->withCustomFields()->asArray();
- $subscriber['segments'] = array_map(function($segment) {
- return $segment['id'];
- }, $segments);
+ if($subscriber !== false) {
+ $subscriber = $subscriber
+ ->withCustomFields()
+ ->withSubscriptions()
+ ->asArray();
}
-
return $subscriber;
}
@@ -38,19 +35,10 @@ class Subscribers {
$listing_data = $listing->get();
// fetch segments relations for each returned item
- foreach($listing_data['items'] as &$item) {
- // avatar
- $item['avatar_url'] = get_avatar_url($item['email'], array(
- 'size' => 32
- ));
-
- // subscriber's segments
- $relations = SubscriberSegment::select('segment_id')
- ->where('subscriber_id', $item['id'])
- ->findMany();
- $item['segments'] = array_map(function($relation) {
- return $relation->segment_id;
- }, $relations);
+ foreach($listing_data['items'] as $key => $subscriber) {
+ $listing_data['items'][$key] = $subscriber
+ ->withSubscriptions()
+ ->asArray();
}
return $listing_data;
diff --git a/lib/Twig/i18n.php b/lib/Twig/i18n.php
index 8ae1166bc0..b99f97a906 100644
--- a/lib/Twig/i18n.php
+++ b/lib/Twig/i18n.php
@@ -18,7 +18,13 @@ class i18n extends \Twig_Extension {
// twig custom functions
$twig_functions = array();
// list of WP functions to map
- $functions = array('localize', '__', '_n');
+ $functions = array(
+ 'localize',
+ '__',
+ '_n',
+ 'date',
+ 'date_format'
+ );
foreach($functions as $function) {
$twig_functions[] = new \Twig_SimpleFunction(
@@ -57,6 +63,23 @@ class i18n extends \Twig_Extension {
return call_user_func_array('_n', $this->setTextDomain($args));
}
+ function date() {
+ $args = func_get_args();
+ $date = (isset($args[0])) ? $args[0] : null;
+ $date_format = (isset($args[1])) ? $args[1] : get_option('date_format');
+
+ if(empty($date)) return;
+
+ // check if it's an int passed as a string
+ if((string)(int)$date === $date) {
+ $date = (int)$date;
+ } else if(!is_int($date)) {
+ $date = strtotime($date);
+ }
+
+ return get_date_from_gmt(date('Y-m-d H:i:s', $date), $date_format);
+ }
+
private function setTextDomain($args = array()) {
// make sure that the last argument is our text domain
if($args[count($args) - 1] !== $this->_text_domain) {
diff --git a/tests/unit/Router/NewslettersCest.php b/tests/unit/Router/NewslettersCest.php
index aa45164564..23b45c28e1 100644
--- a/tests/unit/Router/NewslettersCest.php
+++ b/tests/unit/Router/NewslettersCest.php
@@ -160,13 +160,18 @@ class NewslettersCest {
expect($response['items'][0]['subject'])->equals('My Standard Newsletter');
expect($response['items'][1]['subject'])->equals('My Post Notification');
- expect($response['items'][0]['segments'])->equals(array(
- $segment_1->id(),
- $segment_2->id()
- ));
- expect($response['items'][1]['segments'])->equals(array(
- $segment_2->id()
- ));
+
+ // 1st subscriber has 2 segments
+ expect($response['items'][0]['segments'])->count(2);
+ expect($response['items'][0]['segments'][0]['id'])
+ ->equals($segment_1->id);
+ expect($response['items'][0]['segments'][1]['id'])
+ ->equals($segment_2->id);
+
+ // 2nd subscriber has 1 segment
+ expect($response['items'][1]['segments'])->count(1);
+ expect($response['items'][1]['segments'][0]['id'])
+ ->equals($segment_2->id);
}
function itCanBulkDeleteNewsletters() {
@@ -193,6 +198,7 @@ class NewslettersCest {
function _after() {
Newsletter::deleteMany();
+ NewsletterSegment::deleteMany();
Segment::deleteMany();
}
}
\ No newline at end of file
diff --git a/views/subscribers/subscribers.html b/views/subscribers/subscribers.html
index 143ddbf2ad..3f668cd26e 100644
--- a/views/subscribers/subscribers.html
+++ b/views/subscribers/subscribers.html
@@ -20,5 +20,7 @@
var mailpoet_segments = <%= json_encode(segments) %>;
var mailpoet_custom_fields = <%= json_encode(custom_fields) %>;
var mailpoet_month_names = <%= json_encode(month_names) %>;
+ var mailpoet_date_format = "<%= get_option('date_format') %>";
+ var mailpoet_date_offset = "<%= get_option('gmt_offset') %>";
<% endblock %>
|