diff --git a/lib/Config/Initializer.php b/lib/Config/Initializer.php index 1b31f5772d..e64019e7f8 100644 --- a/lib/Config/Initializer.php +++ b/lib/Config/Initializer.php @@ -43,6 +43,8 @@ class Initializer { $newsletter_segment = Env::$db_prefix . 'newsletter_segment'; $custom_fields = Env::$db_prefix . 'custom_fields'; $subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field'; + $newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields'; + $newsletter_option = Env::$db_prefix . 'newsletter_option'; define('MP_SUBSCRIBERS_TABLE', $subscribers); define('MP_SETTINGS_TABLE', $settings); @@ -53,6 +55,8 @@ class Initializer { define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment); define('MP_CUSTOM_FIELDS_TABLE', $custom_fields); define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field); + define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields); + define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option); } function setupActivator() { @@ -92,4 +96,4 @@ class Initializer { $permissions = new Permissions(); $permissions->init(); } -} \ No newline at end of file +} diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index a410121d99..d115967702 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -18,7 +18,9 @@ class Migrator { 'subscriber_segment', 'newsletter_segment', 'custom_fields', - 'subscriber_custom_field' + 'subscriber_custom_field', + 'newsletter_option_fields', + 'newsletter_option', ); } @@ -161,6 +163,32 @@ class Migrator { return $this->sqlify(__FUNCTION__, $attributes); } + function newsletter_option_fields() { + $attributes = array( + 'id mediumint(9) NOT NULL AUTO_INCREMENT,', + 'name varchar(90) NOT NULL,', + 'newsletter_type 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_newsletter_type (newsletter_type, name)' + ); + return $this->sqlify(__FUNCTION__, $attributes); + } + + function newsletter_option() { + $attributes = array( + 'id mediumint(9) NOT NULL AUTO_INCREMENT,', + 'newsletter_id mediumint(9) NOT NULL,', + 'option_field_id mediumint(9) NOT NULL,', + 'value varchar(255) 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/Newsletter.php b/lib/Models/Newsletter.php index b3b77fa006..59f5fabda2 100644 --- a/lib/Models/Newsletter.php +++ b/lib/Models/Newsletter.php @@ -19,6 +19,15 @@ class Newsletter extends Model { ); } + function options() { + return $this->has_many_through( + __NAMESPACE__.'\NewsletterOptionField', + __NAMESPACE__.'\NewsletterOption', + 'newsletter_id', + 'option_field_id' + )->select_expr(MP_NEWSLETTER_OPTIONS_TABLE.'.value'); + } + static function search($orm, $search = '') { return $orm->where_like('subject', '%' . $search . '%'); } @@ -76,6 +85,28 @@ class Newsletter extends Model { return $orm; } + static function filterWithOptions($orm) { + $orm = $orm->select(MP_NEWSLETTERS_TABLE.'.*'); + $optionFields = NewsletterOptionField::findArray(); + foreach ($optionFields as $optionField) { + $orm = $orm->select_expr( + 'IFNULL(GROUP_CONCAT(CASE WHEN ' . + MP_NEWSLETTER_OPTION_FIELDS_TABLE . '.id=' . $optionField['id'] . ' THEN ' . + MP_NEWSLETTER_OPTION_TABLE . '.value END), NULL) as "' . $optionField['name'].'"'); + } + $orm = $orm + ->left_outer_join( + MP_NEWSLETTER_OPTION_TABLE, + array(MP_NEWSLETTERS_TABLE.'.id', '=', + MP_NEWSLETTER_OPTION_TABLE.'.newsletter_id')) + ->left_outer_join( + MP_NEWSLETTER_OPTION_FIELDS_TABLE, + array(MP_NEWSLETTER_OPTION_FIELDS_TABLE.'.id','=', + MP_NEWSLETTER_OPTION_TABLE.'.option_field_id')) + ->group_by(MP_NEWSLETTERS_TABLE.'.id'); + return $orm; + } + static function groups() { return array( array( diff --git a/lib/Models/NewsletterOption.php b/lib/Models/NewsletterOption.php new file mode 100644 index 0000000000..43f3770872 --- /dev/null +++ b/lib/Models/NewsletterOption.php @@ -0,0 +1,12 @@ +addValidations('name', array( + 'required' => __('You need to specify a name.') + )); + $this->addValidations('newsletter_type', array( + 'required' => __('You need to specify a newsletter type.') + )); + } + + function newsletters() { + return $this->has_many_through( + __NAMESPACE__ . '\Newsletter', + __NAMESPACE__ . '\NewsletterOption', + 'option_field_id', + 'newsletter_id' + )->select_expr(MP_NEWSLETTER_OPTION_TABLE.'.value'); + } +} diff --git a/tests/unit/Models/NewsletterCest.php b/tests/unit/Models/NewsletterCest.php index e171a04e4b..dc61d6f6df 100644 --- a/tests/unit/Models/NewsletterCest.php +++ b/tests/unit/Models/NewsletterCest.php @@ -3,6 +3,8 @@ use MailPoet\Models\Newsletter; use MailPoet\Models\Segment; use MailPoet\Models\NewsletterSegment; +use MailPoet\Models\NewsletterOptionField; +use MailPoet\Models\NewsletterOption; class NewsletterCest { function _before() { @@ -16,6 +18,7 @@ class NewsletterCest { $newsletter = Newsletter::create(); $newsletter->hydrate($this->data); + $this->newsletter = $newsletter; $this->result = $newsletter->save(); } @@ -104,7 +107,101 @@ class NewsletterCest { expect($newsletter->subject)->contains('pineapple'); } + function itCanHaveOptions() { + $newsletterOptionFieldData = array( + 'name' => 'Event', + 'newsletter_type' => 'welcome', + ); + $optionField = NewsletterOptionField::create(); + $optionField->hydrate($newsletterOptionFieldData); + $optionField->save(); + $association = NewsletterOption::create(); + $association->newsletter_id = $this->newsletter->id; + $association->option_field_id = $optionField->id; + $association->value = 'list'; + $association->save(); + $newsletter = Newsletter::filter('filterWithOptions') + ->findOne($this->newsletter->id); + expect($newsletter->Event)->equals($association->value); + } + + function itCanFilterOptions() { + $newsletterOptionFieldData = array( + array( + 'name' => 'Event', + 'newsletter_type' => 'welcome', + ), + array( + 'name' => 'List', + 'newsletter_type' => 'welcome', + ) + ); + foreach ($newsletterOptionFieldData as $data) { + $optionField = NewsletterOptionField::create(); + $optionField->hydrate($data); + $optionField->save(); + $createdOptionFields[] = $optionField->asArray(); + } + $newsletterOptionData = array( + array( + 'newsletter_id' => $this->newsletter->id, + 'option_field_id' => $createdOptionFields[0]['id'], + 'value' => 'list' + ), + array( + 'newsletter_id' => $this->newsletter->id, + 'option_field_id' => $createdOptionFields[1]['id'], + 'value' => '1' + ) + ); + foreach ($newsletterOptionData as $data) { + $association = NewsletterOption::create(); + $association->hydrate($data); + $association->save(); + $createdAssociations[] = $association->asArray(); + } + $newsletter = Newsletter::filter('filterWithOptions') + ->filter('filterSearchCustomFields', array( + array( + 'name' => 'Event', + 'value' => 'list' + ) + )) + ->findArray(); + expect(empty($newsletter))->false(); + $newsletter = Newsletter::filter('filterWithOptions') + ->filter('filterSearchCustomFields', array( + array( + 'name' => 'Event', + 'value' => 'list' + ), + array( + 'name' => 'List', + 'value' => '1' + ) + )) + ->findArray(); + expect(empty($newsletter))->false(); + $newsletter = Newsletter::filter('filterWithOptions') + ->filter('filterSearchCustomFields', array( + array( + 'name' => 'Event', + 'value' => 'list' + ), + array( + 'name' => 'List', + 'value' => '2' + ) + )) + ->findArray(); + expect(empty($newsletter))->true(); + } + function _after() { + ORM::forTable(NewsletterOption::$_table) + ->deleteMany(); + ORM::forTable(NewsletterOptionField::$_table) + ->deleteMany(); ORM::for_table(Newsletter::$_table) ->deleteMany(); } diff --git a/tests/unit/Models/NewsletterOptionFieldCest.php b/tests/unit/Models/NewsletterOptionFieldCest.php new file mode 100644 index 0000000000..40462446b3 --- /dev/null +++ b/tests/unit/Models/NewsletterOptionFieldCest.php @@ -0,0 +1,127 @@ +before_time = time(); + $this->data = array( + 'name' => 'Event', + 'newsletter_type' => 'welcome', + ); + $this->optionField = NewsletterOptionField::create(); + $this->optionField->hydrate($this->data); + $this->saved = $this->optionField->save(); + $this->newslettersData = array( + array( + 'subject' => 'Test newsletter 1', + 'preheader' => '', + 'body' => '{}' + ), + array( + 'subject' => 'Test newsletter 2', + 'preheader' => 'A newsletter', + 'body' => '{}' + ) + ); + } + + function itCanBeCreated() { + expect($this->saved)->equals(true); + } + + function itHasName() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + expect($optionField->name)->equals($this->data['name']); + } + + function itHasNewsletterType() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + expect($optionField->newsletter_type)->equals($this->data['newsletter_type']); + } + + function itHasToBeValid() { + expect($this->saved)->equals(true); + $empty_model = NewsletterOptionField::create(); + expect($empty_model->save())->notEquals(true); + $validations = $empty_model->getValidationErrors(); + expect(count($validations))->equals(2); + } + + function itHasACreatedAtOnCreation() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + $time_difference = strtotime($optionField->created_at) >= $this->before_time; + expect($time_difference)->equals(true); + } + + function itHasAnUpdatedAtOnCreation() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + $time_difference = strtotime($optionField->updated_at) >= $this->before_time; + expect($time_difference)->equals(true); + } + + function itKeepsTheCreatedAtOnUpdate() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + $old_created_at = $optionField->created_at; + $optionField->name = 'new name'; + $optionField->save(); + expect($old_created_at)->equals($optionField->created_at); + } + + function itUpdatesTheUpdatedAtOnUpdate() { + $optionField = NewsletterOptionField::where('name', $this->data['name']) + ->findOne(); + $update_time = time(); + $optionField->name = 'new name'; + $optionField->save(); + $time_difference = strtotime($optionField->updated_at) >= $update_time; + expect($time_difference)->equals(true); + } + + function itCanHaveManyNewsletters() { + foreach ($this->newslettersData as $data) { + $newsletter = Newsletter::create(); + $newsletter->hydrate($data); + $newsletter->save(); + $association = NewsletterOption::create(); + $association->newsletter_id = $newsletter->id; + $association->option_field_id = $this->optionField->id; + $association->save(); + } + $optionField = NewsletterOptionField::findOne($this->optionField->id); + $newsletters = $optionField->newsletters() + ->findArray(); + expect(count($newsletters))->equals(2); + } + + function itCanStoreOptionValue() { + $newsletter = Newsletter::create(); + $newsletter->hydrate($this->newslettersData[0]); + $newsletter->save(); + $association = NewsletterOption::create(); + $association->newsletter_id = $newsletter->id; + $association->option_field_id = $this->optionField->id; + $association->value = 'list'; + $association->save(); + $optionField = NewsletterOptionField::findOne($this->optionField->id); + $newsletter = $optionField->newsletters() + ->findOne(); + expect($newsletter->value)->equals($association->value); + } + + function _after() { + ORM::forTable(NewsletterOption::$_table) + ->deleteMany(); + ORM::forTable(NewsletterOptionField::$_table) + ->deleteMany(); + ORM::forTable(Newsletter::$_table) + ->deleteMany(); + } +}