diff --git a/assets/js/src/ajax.js b/assets/js/src/ajax.js index 06a2c86a6b..adc251e1e8 100644 --- a/assets/js/src/ajax.js +++ b/assets/js/src/ajax.js @@ -13,13 +13,13 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { onError: function(xhr, textStatus, errorThrown) {} }, get: function(options) { - this.request('get', options); + return this.request('get', options); }, post: function(options) { - this.request('post', options); + return this.request('post', options); }, delete: function(options) { - this.request('delete', options); + return this.request('delete', options); }, init: function(options) { // merge options @@ -46,18 +46,18 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { endpoint: this.options.endpoint, method: this.options.action, data: this.options.data || {} - }; + }, jqXHR; // make ajax request depending on method if(method === 'get') { - jQuery.get( + jqXHR = jQuery.get( this.options.url, params, this.options.onSuccess, 'json' ); } else { - jQuery.ajax({ + jqXHR = jQuery.ajax({ url: this.options.url, type : 'post', data: params, @@ -69,6 +69,8 @@ define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { // clear options this.options = {}; + + return jqXHR; } }; }); diff --git a/lib/Config/Initializer.php b/lib/Config/Initializer.php index b87ded06dd..2c9aed8f8a 100644 --- a/lib/Config/Initializer.php +++ b/lib/Config/Initializer.php @@ -33,10 +33,14 @@ class Initializer { $subscribers = Env::$db_prefix . 'subscribers'; $settings = Env::$db_prefix . 'settings'; $newsletters = Env::$db_prefix . 'newsletters'; + $segments = Env::$db_prefix . 'segments'; + $subscriber_segment = Env::$db_prefix . 'subscriber_segment'; define('MP_SUBSCRIBERS_TABLE', $subscribers); define('MP_SETTINGS_TABLE', $settings); define('MP_NEWSLETTERS_TABLE', $newsletters); + define('MP_SEGMENTS_TABLE', $segments); + define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment); } function setupActivator() { diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index 30f91b7875..430815cfab 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -12,7 +12,9 @@ class Migrator { $this->models = array( 'subscribers', 'settings', - 'newsletters' + 'newsletters', + 'segments', + 'subscriber_segment' ); } @@ -78,6 +80,30 @@ class Migrator { return $this->sqlify(__FUNCTION__, $attributes); } + function segments() { + $attributes = array( + 'id mediumint(9) NOT NULL AUTO_INCREMENT,', + 'name varchar(90) NOT NULL,', + 'created_at TIMESTAMP NOT NULL DEFAULT 0,', + 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', + 'PRIMARY KEY (id),', + 'UNIQUE KEY name (name)' + ); + return $this->sqlify(__FUNCTION__, $attributes); + } + + function subscriber_segment() { + $attributes = array( + 'id mediumint(9) NOT NULL AUTO_INCREMENT,', + 'subscriber_id mediumint(9) NOT NULL,', + 'segment_id mediumint(9) NOT NULL,', + 'created_at TIMESTAMP NOT NULL DEFAULT 0,', + '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/Models/Segment.php b/lib/Models/Segment.php new file mode 100644 index 0000000000..7716b10522 --- /dev/null +++ b/lib/Models/Segment.php @@ -0,0 +1,35 @@ +addValidations('name', array( + 'required' => 'name_is_blank', + 'isString' => 'name_is_not_string' + )); + } + + public static function createOrUpdate($model) { + $exists = self::where('name', $model['name']) + ->find_one(); + + if($exists === false) { + $new_model = self::create(); + $new_model->name = $model['name']; + return $new_model->save(); + } + + $exists->name = $model['name_updated']; + return $exists->save(); + } + + public function subscribers() { + return $this->has_many_through(__NAMESPACE__ . '\Subscriber', __NAMESPACE__ . '\SubscriberSegment', 'segment_id', 'subscriber_id'); + } +} diff --git a/lib/Models/Setting.php b/lib/Models/Setting.php index 34123395b3..18e108c40e 100644 --- a/lib/Models/Setting.php +++ b/lib/Models/Setting.php @@ -20,11 +20,11 @@ class Setting extends Model { } public static function createOrUpdate($model) { - $exists = Setting::where('name', $model['name']) + $exists = self::where('name', $model['name']) ->find_one(); if($exists === false) { - $new_model = Setting::create(); + $new_model = self::create(); $new_model->hydrate($model); return $new_model->save(); } diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 5f2fda7035..fe1a439ccc 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -1,64 +1,68 @@ -addValidations('email', array( - 'required' => __('You need to enter your email address.'), - 'isEmail' => __('Your email address is invalid.') - )); - } - - static function search($orm, $search = '') { - if(strlen(trim($search) === 0)) { - return $orm; - } - - return $orm->where_raw( - '(`email` LIKE ? OR `first_name` LIKE ? OR `last_name` LIKE ?)', - array('%'.$search.'%', '%'.$search.'%', '%'.$search.'%') - ); - } - - static function groups() { - return array( - array( - 'name' => 'all', - 'label' => __('All'), - 'count' => Subscriber::count() - ), - array( - 'name' => 'subscribed', - 'label' => __('Subscribed'), - 'count' => Subscriber::where('status', 'subscribed')->count() - ), - array( - 'name' => 'unconfirmed', - 'label' => __('Unconfirmed'), - 'count' => Subscriber::where('status', 'unconfirmed')->count() - ), - array( - 'name' => 'unsubscribed', - 'label' => __('Unsubscribed'), - 'count' => Subscriber::where('status', 'unsubscribed')->count() - ) - ); - } - - static function group($orm, $group = null) { - if($group === null or !in_array( - $group, - array('subscribed', 'unconfirmed', 'unsubscribed') - )) { - return $orm; - } - - return $orm->where('status', $group); - } -} +addValidations('email', array( + 'required' => __('You need to enter your email address.'), + 'isEmail' => __('Your email address is invalid.') + )); + } + + static function search($orm, $search = '') { + if(strlen(trim($search) === 0)) { + return $orm; + } + + return $orm->where_raw( + '(`email` LIKE ? OR `first_name` LIKE ? OR `last_name` LIKE ?)', + array('%'.$search.'%', '%'.$search.'%', '%'.$search.'%') + ); + } + + static function groups() { + return array( + array( + 'name' => 'all', + 'label' => __('All'), + 'count' => Subscriber::count() + ), + array( + 'name' => 'subscribed', + 'label' => __('Subscribed'), + 'count' => Subscriber::where('status', 'subscribed')->count() + ), + array( + 'name' => 'unconfirmed', + 'label' => __('Unconfirmed'), + 'count' => Subscriber::where('status', 'unconfirmed')->count() + ), + array( + 'name' => 'unsubscribed', + 'label' => __('Unsubscribed'), + 'count' => Subscriber::where('status', 'unsubscribed')->count() + ) + ); + } + + static function group($orm, $group = null) { + if($group === null or !in_array( + $group, + array('subscribed', 'unconfirmed', 'unsubscribed') + )) { + return $orm; + } + + return $orm->where('status', $group); + } + + public function segments() { + return $this->has_many_through(__NAMESPACE__ . '\Segment', __NAMESPACE__ . '\SubscriberSegment', 'subscriber_id', 'segment_id'); + } +} diff --git a/lib/Models/SubscriberSegment.php b/lib/Models/SubscriberSegment.php new file mode 100644 index 0000000000..7be467992b --- /dev/null +++ b/lib/Models/SubscriberSegment.php @@ -0,0 +1,12 @@ +writeln('Cleaning up database...'); $models = array( - "Subscriber", - "Setting", - "Newsletter" + 'Subscriber', + 'Setting', + 'Newsletter', + 'Segment', + 'SubscriberSegment' ); $destroy = function ($model) { - Model::factory("\MailPoet\Models\\" . $model) - ->delete_many(); + Model::factory('\MailPoet\Models\\' . $model) + ->delete_many(); }; array_map($destroy, $models); diff --git a/tests/unit/Models/SegmentCest.php b/tests/unit/Models/SegmentCest.php new file mode 100644 index 0000000000..6fa9c755ea --- /dev/null +++ b/tests/unit/Models/SegmentCest.php @@ -0,0 +1,120 @@ +before_time = time(); + $this->data = array( + 'name' => 'some name', + ); + + $this->segment = Segment::create(); + $this->segment->hydrate($this->data); + $this->saved = $this->segment->save(); + } + + function itCanBeCreated() { + expect($this->saved)->equals(true); + } + + function itHasToBeValid() { + expect($this->saved)->equals(true); + $empty_model = Segment::create(); + expect($empty_model->save())->equals(false); + $validations = $empty_model->getValidationErrors(); + expect(count($validations))->equals(2); + } + + function itHasACreatedAtOnCreation() { + $segment = Segment::where('name', $this->data['name']) + ->findOne(); + $time_difference = strtotime($segment->created_at) >= $this->before_time; + expect($time_difference)->equals(true); + } + + function itHasAnUpdatedAtOnCreation() { + $segment = Segment::where('name', $this->data['name']) + ->findOne(); + $time_difference = strtotime($segment->updated_at) >= $this->before_time; + expect($time_difference)->equals(true); + } + + function itKeepsTheCreatedAtOnUpdate() { + $segment = Segment::where('name', $this->data['name']) + ->findOne(); + $old_created_at = $segment->created_at; + $segment->name = 'new name'; + $segment->save(); + expect($old_created_at)->equals($segment->created_at); + } + + function itUpdatesTheUpdatedAtOnUpdate() { + $segment = Segment::where('name', $this->data['name']) + ->findOne(); + $update_time = time(); + $segment->name = 'new name'; + $segment->save(); + $time_difference = strtotime($segment->updated_at) >= $update_time; + expect($time_difference)->equals(true); + } + + function itCanCreateOrUpdate() { + $data = array( + 'name' => 'some other new name' + ); + $createNewRecord = Segment::createOrUpdate($data); + + $data = array( + 'name' => $this->data['name'], + 'name_updated' => 'updated name', + ); + $updateExistingRecord = Segment::createOrUpdate($data); + + $allRecords = Segment::find_array(); + expect(count($allRecords))->equals(2); + expect($allRecords[0]['name'])->equals($data['name_updated']); + } + + function itCanHaveMultipleSubscribers() { + $subscribersData = array( + array( + 'first_name' => 'John', + 'last_name' => 'Mailer', + 'email' => 'john@mailpoet.com' + ), + array( + 'first_name' => 'Mike', + 'last_name' => 'Smith', + 'email' => 'mike@maipoet.com' + ) + ); + foreach ($subscribersData as $subscriberData) { + $subscriber = Subscriber::create(); + $subscriber->hydrate($subscriberData); + $subscriber->save(); + $association = SubscriberSegment::create(); + $association->subscriber_id = $subscriber->id; + $association->segment_id = $this->segment->id; + $association->save(); + } + + $segment = Segment::find_one($this->segment->id); + $subscribers = $segment->subscribers() + ->find_array(); + expect(count($subscribers))->equals(2); + } + + function _after() { + ORM::for_table(Segment::$_table) + ->delete_many(); + ORM::for_table(Subscriber::$_table) + ->delete_many(); + ORM::for_table(SubscriberSegment::$_table) + ->delete_many(); + } + + +} diff --git a/tests/unit/Models/SubscriberCest.php b/tests/unit/Models/SubscriberCest.php index 4ebc948d6d..4480ed62be 100644 --- a/tests/unit/Models/SubscriberCest.php +++ b/tests/unit/Models/SubscriberCest.php @@ -1,119 +1,131 @@ -data = array( - 'first_name' => 'John', - 'last_name' => 'Mailer', - 'email' => 'jo@mailpoet.com' - ); - - $this->subscriber = Subscriber::create(); - $this->subscriber->hydrate($this->data); - $this->saved = $this->subscriber->save(); - - $subscribed = Subscriber::create(); - $subscribed->hydrate(array( - 'email' => 'marco@mailpoet.com', - 'status' => 'subscribed' - )); - $subscribed->save(); - - $unsubscribed = Subscriber::create(); - $unsubscribed->hydrate(array( - 'email' => 'marco@mailpoet.com', - 'status' => 'unsubscribed' - )); - $unsubscribed->save(); - } - - function itCanBeCreated() { - expect($this->saved)->equals(true); - } - - function itHasAFirstName() { - $subscriber = - Subscriber::where('email', $this->data['email']) - ->findOne(); - expect($subscriber->first_name) - ->equals($this->data['first_name']); - } - - function itHasALastName() { - $subscriber = - Subscriber::where('email', $this->data['email']) - ->findOne(); - expect($subscriber->last_name) - ->equals($this->data['last_name']); - } - - function itHasAnEmail() { - $subscriber = - Subscriber::where('email', $this->data['email']) - ->findOne(); - expect($subscriber->email) - ->equals($this->data['email']); - } - - function itHasAStatus() { - $subscriber = - Subscriber::where('email', $this->data['email']) - ->findOne(); - - expect($subscriber->status)->equals('unconfirmed'); - } - - function itCanChangeStatus() { - $subscriber = Subscriber::where('email', $this->data['email'])->findOne(); - $subscriber->status = 'subscribed'; - expect($subscriber->save())->equals(true); - - $subscriber_updated = Subscriber::where( - 'email', - $this->data['email'] - )->findOne(); - expect($subscriber_updated->status)->equals('subscribed'); - } - - function itHasASearchFilter() { - $subscriber = Subscriber::filter('search', 'john')->findOne(); - expect($subscriber->first_name)->equals($this->data['first_name']); - - $subscriber = Subscriber::filter('search', 'mailer')->findOne(); - expect($subscriber->last_name)->equals($this->data['last_name']); - - $subscriber = Subscriber::filter('search', 'mailpoet')->findOne(); - expect($subscriber->email)->equals($this->data['email']); - } - - function itHasAGroupFilter() { - $subscribers = Subscriber::filter('group', 'unconfirmed')->findMany(); - foreach($subscribers as $subscriber) { - expect($subscriber->status)->equals('unconfirmed'); - } - - $subscribers = Subscriber::filter('group', 'subscribed')->findMany(); - foreach($subscribers as $subscriber) { - expect($subscriber->status)->equals('subscribed'); - } - - $subscribers = Subscriber::filter('group', 'unsubscribed')->findMany(); - foreach($subscribers as $subscriber) { - expect($subscriber->status)->equals('unsubscribed'); - } - } - - function emailMustBeUnique() { - $conflict_subscriber = Subscriber::create(); - $conflict_subscriber->hydrate($this->data); - $saved = $conflict_subscriber->save(); - expect($saved)->equals(false); - } - - function _after() { - ORM::for_table(Subscriber::$_table) - ->delete_many(); - } -} +before_time = time(); + $this->data = array( + 'first_name' => 'John', + 'last_name' => 'Mailer', + 'email' => 'john@mailpoet.com' + ); + + $this->subscriber = Subscriber::create(); + $this->subscriber->hydrate($this->data); + $this->saved = $this->subscriber->save(); + } + + function itCanBeCreated() { + expect($this->saved)->equals(true); + } + + function itHasAFirstName() { + $subscriber = + Subscriber::where('email', $this->data['email']) + ->findOne(); + expect($subscriber->first_name) + ->equals($this->data['first_name']); + } + + function itHasALastName() { + $subscriber = + Subscriber::where('email', $this->data['email']) + ->findOne(); + expect($subscriber->last_name) + ->equals($this->data['last_name']); + } + + function itHasAnEmail() { + $subscriber = + Subscriber::where('email', $this->data['email']) + ->findOne(); + expect($subscriber->email) + ->equals($this->data['email']); + } + + function emailMustBeUnique() { + $conflict_subscriber = Subscriber::create(); + $conflict_subscriber->hydrate($this->data); + $saved = $conflict_subscriber->save(); + expect($saved)->equals(false); + } + + function itHasAStatus() { + $subscriber = + Subscriber::where('email', $this->data['email']) + ->findOne(); + + expect($subscriber->status)->equals('unconfirmed'); + } + + function itCanChangeStatus() { + $subscriber = Subscriber::where('email', $this->data['email'])->findOne(); + $subscriber->status = 'subscribed'; + expect($subscriber->save())->equals(true); + + $subscriber_updated = Subscriber::where( + 'email', + $this->data['email'] + )->findOne(); + expect($subscriber_updated->status)->equals('subscribed'); + } + + function itHasASearchFilter() { + $subscriber = Subscriber::filter('search', 'john')->findOne(); + expect($subscriber->first_name)->equals($this->data['first_name']); + + $subscriber = Subscriber::filter('search', 'mailer')->findOne(); + expect($subscriber->last_name)->equals($this->data['last_name']); + + $subscriber = Subscriber::filter('search', 'mailpoet')->findOne(); + expect($subscriber->email)->equals($this->data['email']); + } + + function itHasAGroupFilter() { + $subscribers = Subscriber::filter('group', 'unconfirmed')->findMany(); + foreach($subscribers as $subscriber) { + expect($subscriber->status)->equals('unconfirmed'); + } + + $subscribers = Subscriber::filter('group', 'subscribed')->findMany(); + foreach($subscribers as $subscriber) { + expect($subscriber->status)->equals('subscribed'); + } + + $subscribers = Subscriber::filter('group', 'unsubscribed')->findMany(); + foreach($subscribers as $subscriber) { + expect($subscriber->status)->equals('unsubscribed'); + } + } + + function itCanHaveASegment() { + $segmentData = array( + 'name' => 'some name' + ); + + $segment = Segment::create(); + $segment->hydrate($segmentData); + $segment->save(); + $association = SubscriberSegment::create(); + $association->subscriber_id = $this->subscriber->id; + $association->segment_id = $segment->id; + $association->save(); + + $subscriber = Subscriber::find_one($this->subscriber->id); + $subscriberSegment = $subscriber->segments() + ->find_one(); + expect($subscriberSegment->id)->equals($segment->id); + } + + function _after() { + ORM::for_table(Subscriber::$_table) + ->delete_many(); + ORM::for_table(Segment::$_table) + ->delete_many(); + ORM::for_table(SubscriberSegment::$_table) + ->delete_many(); + } +}