diff --git a/assets/js/src/segments/list.jsx b/assets/js/src/segments/list.jsx index bb3f39ad97..65a9ea5a12 100644 --- a/assets/js/src/segments/list.jsx +++ b/assets/js/src/segments/list.jsx @@ -28,6 +28,10 @@ var columns = [ name: 'unsubscribed', label: MailPoet.I18n.t('unsubscribed') }, + { + name: 'bounced', + label: MailPoet.I18n.t('bounced') + }, { name: 'created_at', label: MailPoet.I18n.t('createdOn'), @@ -192,6 +196,7 @@ const SegmentList = React.createClass({ const subscribed = ~~(segment.subscribers_count.subscribed || 0); const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0); const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0); + const bounced = ~~(segment.subscribers_count.bounced || 0); let segment_name; @@ -229,6 +234,9 @@ const SegmentList = React.createClass({ { unsubscribed.toLocaleString() } + + { bounced.toLocaleString() } + { MailPoet.Date.format(segment.created_at) } diff --git a/assets/js/src/subscribers/form.jsx b/assets/js/src/subscribers/form.jsx index 78d504ccc8..cdccd7e310 100644 --- a/assets/js/src/subscribers/form.jsx +++ b/assets/js/src/subscribers/form.jsx @@ -45,7 +45,8 @@ define( values: { 'subscribed': MailPoet.I18n.t('subscribed'), 'unconfirmed': MailPoet.I18n.t('unconfirmed'), - 'unsubscribed': MailPoet.I18n.t('unsubscribed') + 'unsubscribed': MailPoet.I18n.t('unsubscribed'), + 'bounced': MailPoet.I18n.t('bounced') }, filter: function(subscriber, value) { if (~~(subscriber.wp_user_id) > 0 && value === 'unconfirmed') { diff --git a/assets/js/src/subscribers/list.jsx b/assets/js/src/subscribers/list.jsx index 4dc09ca7d8..11f756b8b6 100644 --- a/assets/js/src/subscribers/list.jsx +++ b/assets/js/src/subscribers/list.jsx @@ -255,6 +255,10 @@ const SubscriberList = React.createClass({ case 'unsubscribed': status = MailPoet.I18n.t('unsubscribed'); break; + + case 'bounced': + status = MailPoet.I18n.t('bounced'); + break; } let segments = false; diff --git a/lib/Models/Segment.php b/lib/Models/Segment.php index 6f3752746c..a336d68279 100644 --- a/lib/Models/Segment.php +++ b/lib/Models/Segment.php @@ -87,6 +87,10 @@ class Segment extends Model { 'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_UNCONFIRMED . '" THEN 1 ELSE 0 END)', Subscriber::STATUS_UNCONFIRMED ) + ->select_expr( + 'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_BOUNCED . '" THEN 1 ELSE 0 END)', + Subscriber::STATUS_BOUNCED + ) ->findOne() ->asArray(); diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 82b2311422..cc5e076619 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -13,6 +13,7 @@ class Subscriber extends Model { const STATUS_SUBSCRIBED = 'subscribed'; const STATUS_UNSUBSCRIBED = 'unsubscribed'; const STATUS_UNCONFIRMED = 'unconfirmed'; + const STATUS_BOUNCED = 'bounced'; const SUBSCRIPTION_LIMIT_COOLDOWN = 60; @@ -335,6 +336,11 @@ class Subscriber extends Model { 'label' => __('Unsubscribed', 'mailpoet'), 'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count() ), + array( + 'name' => self::STATUS_BOUNCED, + 'label' => __('Bounced', 'mailpoet'), + 'count' => self::filter(self::STATUS_BOUNCED)->count() + ), array( 'name' => 'trash', 'label' => __('Trash', 'mailpoet'), @@ -720,6 +726,12 @@ class Subscriber extends Model { ->where('status', self::STATUS_UNCONFIRMED); } + static function bounced($orm) { + return $orm + ->whereNull('deleted_at') + ->where('status', self::STATUS_BOUNCED); + } + static function withoutSegments($orm) { return $orm->select(MP_SUBSCRIBERS_TABLE.'.*') ->leftOuterJoin( diff --git a/lib/Subscribers/ImportExport/Import/Import.php b/lib/Subscribers/ImportExport/Import/Import.php index 459b1d4868..0d1fe00314 100644 --- a/lib/Subscribers/ImportExport/Import/Import.php +++ b/lib/Subscribers/ImportExport/Import/Import.php @@ -302,18 +302,24 @@ class Import { -1, '-1', 'false' + ), + 'bounced' => array( + 'bounced' ) ); $subscribers_data['status'] = array_map(function($state) use ($statuses) { - if(in_array(strtolower($state), $statuses['subscribed'])) { + if(in_array(strtolower($state), $statuses['subscribed'], true)) { return 'subscribed'; } - if(in_array(strtolower($state), $statuses['unsubscribed'])) { + if(in_array(strtolower($state), $statuses['unsubscribed'], true)) { return 'unsubscribed'; } - if(in_array(strtolower($state), $statuses['unconfirmed'])) { + if(in_array(strtolower($state), $statuses['unconfirmed'], true)) { return 'unconfirmed'; } + if(in_array(strtolower($state), $statuses['bounced'], true)) { + return 'bounced'; + } return 'subscribed'; // make "subscribed" a default status }, $subscribers_data['status']); return array( diff --git a/lib/Subscription/Pages.php b/lib/Subscription/Pages.php index 6497e0f720..f5c4072b45 100644 --- a/lib/Subscription/Pages.php +++ b/lib/Subscription/Pages.php @@ -290,6 +290,14 @@ class Pages { 'is_checked' => ( $subscriber->status === Subscriber::STATUS_UNSUBSCRIBED ) + ), + array( + 'value' => array( + Subscriber::STATUS_BOUNCED => __('Bounced', 'mailpoet') + ), + 'is_checked' => ( + $subscriber->status === Subscriber::STATUS_BOUNCED + ) ) ) ) diff --git a/testItHasGroupFilter b/testItHasGroupFilter new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/Helpscout/BeaconTest.php b/tests/unit/Helpscout/BeaconTest.php index e57c815749..4663856bb7 100644 --- a/tests/unit/Helpscout/BeaconTest.php +++ b/tests/unit/Helpscout/BeaconTest.php @@ -5,7 +5,7 @@ use \MailPoet\Models\Subscriber; class BeaconTest extends MailPoetTest { function _before() { - // create 3 users (1 confirmed, 1 subscribed, 1 unsubscribed) + // create 4 users (1 confirmed, 1 subscribed, 1 unsubscribed, 1 bounced) Subscriber::createOrUpdate(array( 'email' => 'user1@mailpoet.com', 'status' => Subscriber::STATUS_SUBSCRIBED @@ -18,6 +18,10 @@ class BeaconTest extends MailPoetTest { 'email' => 'user3@mailpoet.com', 'status' => Subscriber::STATUS_UNSUBSCRIBED )); + Subscriber::createOrUpdate(array( + 'email' => 'user4@mailpoet.com', + 'status' => Subscriber::STATUS_BOUNCED + )); $this->beacon_data = Beacon::getData(); } diff --git a/tests/unit/Models/SubscriberTest.php b/tests/unit/Models/SubscriberTest.php index 84437e8f19..a8dd944aef 100644 --- a/tests/unit/Models/SubscriberTest.php +++ b/tests/unit/Models/SubscriberTest.php @@ -114,16 +114,30 @@ class SubscriberTest extends MailPoetTest { foreach($subscribers as $subscriber) { expect($subscriber->status)->equals(Subscriber::STATUS_UNCONFIRMED); } + + $this->subscriber->status = Subscriber::STATUS_SUBSCRIBED; + $this->subscriber->save(); $subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_SUBSCRIBED) ->findMany(); foreach($subscribers as $subscriber) { expect($subscriber->status)->equals(Subscriber::STATUS_SUBSCRIBED); } + + $this->subscriber->status = Subscriber::STATUS_UNSUBSCRIBED; + $this->subscriber->save(); $subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_UNSUBSCRIBED) ->findMany(); foreach($subscribers as $subscriber) { expect($subscriber->status)->equals(Subscriber::STATUS_UNSUBSCRIBED); } + + $this->subscriber->status = Subscriber::STATUS_BOUNCED; + $this->subscriber->save(); + $subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_BOUNCED) + ->findMany(); + foreach($subscribers as $subscriber) { + expect($subscriber->status)->equals(Subscriber::STATUS_BOUNCED); + } } function testItCanHaveSegment() { @@ -507,6 +521,11 @@ class SubscriberTest extends MailPoetTest { 'deleted_at' => Carbon::now()->toDateTimeString() )); + $subscriber_5 = Subscriber::createOrUpdate(array( + 'email' => 'subscriber_5@mailpoet.com', + 'status' => Subscriber::STATUS_BOUNCED + )); + // counts only subscribed & unconfirmed users $total = Subscriber::getTotalSubscribers(); expect($total)->equals(2); diff --git a/tests/unit/Newsletter/ShortcodesTest.php b/tests/unit/Newsletter/ShortcodesTest.php index 4b19a42881..4590d1479f 100644 --- a/tests/unit/Newsletter/ShortcodesTest.php +++ b/tests/unit/Newsletter/ShortcodesTest.php @@ -153,7 +153,12 @@ class ShortcodesTest extends MailPoetTest { $this->subscriber->save(); $result = $shortcodes_object->process(array('[subscriber:count]')); - expect($result[0])->equals(--$subscriber_count); + expect($result[0])->equals($subscriber_count - 1); + $this->subscriber->status = 'bounced'; + $this->subscriber->save(); + $result = + $shortcodes_object->process(array('[subscriber:count]')); + expect($result[0])->equals($subscriber_count - 1); } function testItCanProcessSubscriberCustomFieldShortcodes() { diff --git a/tests/unit/Subscribers/ImportExport/Import/ImportTest.php b/tests/unit/Subscribers/ImportExport/Import/ImportTest.php index adf783c92c..f73f360304 100644 --- a/tests/unit/Subscribers/ImportExport/Import/ImportTest.php +++ b/tests/unit/Subscribers/ImportExport/Import/ImportTest.php @@ -186,7 +186,12 @@ class ImportTest extends MailPoetTest { 'unsubscribed', -1, '-1', - 'false' + 'false', + #bounced + 'bounced', + #unexpected + 'qwerty', + null ), ); list($subscribers_data, $subsciber_fields) = @@ -205,7 +210,10 @@ class ImportTest extends MailPoetTest { 'unsubscribed', 'unsubscribed', 'unsubscribed', - 'unsubscribed' + 'unsubscribed', + 'bounced', + 'subscribed', + 'subscribed' ) ) ); diff --git a/views/segments.html b/views/segments.html index 585e216c3d..f36fca4dc8 100644 --- a/views/segments.html +++ b/views/segments.html @@ -30,6 +30,7 @@ 'subscribed': __('Subscribed'), 'unconfirmed': __('Unconfirmed'), 'unsubscribed': __('Unsubscribed'), + 'bounced': __('Bounced'), 'createdOn': __('Created on'), 'oneSegmentTrashed': __('1 list was moved to the trash'), 'multipleSegmentsTrashed': __('%$1d lists were moved to the trash'), diff --git a/views/subscribers/subscribers.html b/views/subscribers/subscribers.html index 9f570a5202..f85b22c523 100644 --- a/views/subscribers/subscribers.html +++ b/views/subscribers/subscribers.html @@ -50,6 +50,7 @@ 'unconfirmed': __('Unconfirmed'), 'subscribed': __('Subscribed'), 'unsubscribed': __('Unsubscribed'), + 'bounced': __('Bounced'), 'selectList': __('Select a list'), 'unsubscribedOn': __('Unsubscribed on %$1s'), 'subscriberUpdated': __('Subscriber was updated successfully!'),