From b1135da1217181473d2d98a3355dea633d0829f9 Mon Sep 17 00:00:00 2001
From: Arthur Suzuki <arthur.suzuki@biblibre.com>
Date: Wed, 29 Apr 2020 16:48:19 +0200
Subject: [PATCH] dev#70265: newsletters : User Can disable any Newsletter
 [GPDR compliance]

---
 FEATURES/70265                                | 11 +++
 VERSIONS_WIP/70265                            |  1 +
 .../opac/controllers/AbonneController.php     | 48 ++++++++--
 cosmogramme/sql/patch/patch_391.php           | 12 +++
 library/Class/Newsletter/Blacklist.php        |  5 ++
 library/Class/Newsletter/Dispatch.php         |  5 ++
 library/Class/Users.php                       | 17 ++++
 .../View/Helper/Abonne/Newsletters.php        | 38 +++++---
 .../templates/Intonation/Library/Settings.php |  1 +
 .../Library/View/Wrapper/Newsletter.php       |  8 +-
 .../Intonation/View/RenderNewsletters.php     | 44 ++++++++++
 .../AbonneControllerNewslettersTest.php       | 85 ++++++++++++++++--
 tests/db/UpgradeDBTest.php                    | 18 +++-
 .../library/Class/NewsletterDispatchTest.php  | 88 ++++++++++++++++++-
 tests/scenarios/Templates/TemplatesTest.php   | 13 +++
 15 files changed, 365 insertions(+), 29 deletions(-)
 create mode 100644 FEATURES/70265
 create mode 100644 VERSIONS_WIP/70265
 create mode 100644 cosmogramme/sql/patch/patch_391.php

diff --git a/FEATURES/70265 b/FEATURES/70265
new file mode 100644
index 00000000000..57f2a4d2135
--- /dev/null
+++ b/FEATURES/70265
@@ -0,0 +1,11 @@
+        '70265' =>
+            ['Label' => $this->_('RGPD : Newsletter - Possibilité de désactiver les lettres d'\'information'),
+             'Desc' => 'Ajout d'une possibilité pour les utilisateurs de se désabonner de toute lettre d\'information via une checkbox',
+             'Image' => '',
+             'Video' => '',
+             'Category' => '',
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'https://wiki.bokeh-library-portal.org/index.php?title=Lettre_d'information',
+
+             'Test' => '',
+             'Date' => '2020-04-29'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/70265 b/VERSIONS_WIP/70265
new file mode 100644
index 00000000000..a016eb8d4da
--- /dev/null
+++ b/VERSIONS_WIP/70265
@@ -0,0 +1 @@
+ - ticket #70265 : Newsletter Conformité RGPD je ne souhaite recevoir aucune newsletter
\ No newline at end of file
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index d374a85f306..0d2107e3ed4 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -176,6 +176,26 @@ class AbonneController extends ZendAfi_Controller_Action {
   }
 
 
+  public function disableNewsletterAction() {
+    $this->_stayOnPage();
+
+    $this->_user->setDisableNewsletter(1);
+    $this->_user->save()
+      ? $this->_helper->notify($this->_('Vous êtes désinscrit de toutes les listes de diffusion'))
+      : $this->_helper->notify($this->_('Une erreur est survenue lors de l\'enregistrement'));
+  }
+
+
+  public function enableNewsletterAction() {
+    $this->_stayOnPage();
+
+    $this->_user->setDisableNewsletter(0);
+    $this->_user->save()
+      ? $this->_helper->notify($this->_('Vous pouvez vous inscrire à des listes de diffusion'))
+      : $this->_helper->notify($this->_('Une erreur est survenue lors de l\'enregistrement'));
+  }
+
+
   public function viewavisAction(){
     $this->_forward('viewauteur', 'blog', 'opac', ['id' => $this->_user->getId()]);
   }
@@ -587,7 +607,6 @@ class AbonneController extends ZendAfi_Controller_Action {
       $form->addDisplayGroup(['mode_contact'], 'mode_de_contact', ['legend' => 'Recevoir mes notificactions de réservation et de rappel']);
     }
 
-
     /* Abonnements aux newsletters*/
     $subscriptions = new Zend_Form_Element_MultiCheckbox('subscriptions');
     $subscriptions->setLabel($this->_("Abonnement aux lettres d'information"));
@@ -603,14 +622,28 @@ class AbonneController extends ZendAfi_Controller_Action {
           $checked_subscriptions []= $nl->getId();
       }
       $subscriptions->setValue($checked_subscriptions);
+
       $form
         ->addElement($subscriptions)
-        ->addDisplayGroup(['subscriptions'],
+        ->addElement('checkbox', 'disable_newsletter',
+                     ['label' => $this->_('Je ne veux plus recevoir du tout de lettre d\'information'),
+                      'class' => 'important',
+                      'value' => true,
+                      'checked' => $user->isNewsletterDisabled()
+                     ])
+
+        ->addDisplayGroup(['subscriptions','disable_newsletter'],
                           'newsletters',
                           ['legend' => 'Lettres d\'information']);
     }
 
 
+
+    if ($user->isNewsletterDisabled()){
+      $form->addSummary($this->_("Vous avez désactivé la réception des lettres d'information"));
+      Class_ScriptLoader::getInstance()->addJQueryReady('$("subscriptions[]").prop("disabled", true);');
+    }
+
     $form
       ->populate($user->toArray());
 
@@ -640,15 +673,18 @@ class AbonneController extends ZendAfi_Controller_Action {
       $password = $this->_user->getPassword();
 
     $fields_to_save = Class_AdminVar::getChampsFicheUtilisateur();
-    $attributes = [];
+    $fields_to_save []= 'disable_newsletter';
+    $attributes = [ 'disable_newsletter' => false];
     foreach($fields_to_save as $field)
       $attributes[$field] = $this->_request->getParam($field);
 
     $previous_password = $this->_user->getPassword();
     $this->_user
       ->updateAttributes($attributes)
-      ->setPassword($password)
-      ->setNewsletters($newsletters);
+      ->setPassword($password);
+
+    if ($this->_user->isNewsletterEnabled())
+      $this->_user->setNewsletters($newsletters);
 
     $patron = $this->_user->getEmprunteur();
     $patron->updateFromUser($this->_user);
@@ -1947,4 +1983,4 @@ class AbonneController extends ZendAfi_Controller_Action {
     $this->_helper->csv('Mes-donnees-Bokeh.csv', $this->view->userDatasCsv(Class_Users::getIdentity()));
 
   }
-}
\ No newline at end of file
+}
diff --git a/cosmogramme/sql/patch/patch_391.php b/cosmogramme/sql/patch/patch_391.php
new file mode 100644
index 00000000000..a404c29c76f
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_391.php
@@ -0,0 +1,12 @@
+<?php
+
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+
+try {
+  $adapter->query(
+                  'ALTER TABLE `bib_admin_users` '
+                  . 'ADD COLUMN `disable_newsletter` tinyint(1)  default "0",'
+                  . 'ADD KEY `disable_newsletter` (`disable_newsletter`)'
+  );
+} catch(Exception $e) {}
+
diff --git a/library/Class/Newsletter/Blacklist.php b/library/Class/Newsletter/Blacklist.php
index b0431c37891..9d3001baf0c 100644
--- a/library/Class/Newsletter/Blacklist.php
+++ b/library/Class/Newsletter/Blacklist.php
@@ -29,8 +29,13 @@ class Newsletter_BlacklistLoader extends Storm_Model_Loader {
 
 
   public function unsubscribeByMail($mail, $newsletter_id) {
+    if (0 < Class_Newsletter_Blacklist::countBy(['newsletter_id' => $newsletter_id,
+                                                 'mail' => $mail]))
+        return;
+
     $blacklist = Class_Newsletter_Blacklist::newInstance(['mail' => $mail,
                                                           'newsletter_id' => $newsletter_id]);
+
     return $blacklist->save();
   }
 
diff --git a/library/Class/Newsletter/Dispatch.php b/library/Class/Newsletter/Dispatch.php
index e81dbf69d26..d3f9b371ad7 100644
--- a/library/Class/Newsletter/Dispatch.php
+++ b/library/Class/Newsletter/Dispatch.php
@@ -115,6 +115,11 @@ class Class_Newsletter_Dispatch extends Storm_Model_Abstract {
       return;
 
     $blacklist_mails = Class_Newsletter_Blacklist::getMailsForNewsletter($this->getNewsletter()->getId());
+
+    $disabled_mails = Class_Users::getDisabledNewsletterUserMails();
+
+    $blacklist_mails = array_unique(array_merge($blacklist_mails, $disabled_mails));
+
     $closure = function($model) use ($blacklist_mails) {
       if (!$model->hasMail())
         return;
diff --git a/library/Class/Users.php b/library/Class/Users.php
index 61840f66778..1b80a0a6f5a 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -22,6 +22,12 @@
 class UsersLoader extends Storm_Model_Loader {
   use Trait_Translator;
 
+  public function getDisabledNewsletterUserMails(){
+    $request= 'select distinct(bib_admin_users.mail) from bib_admin_users where disable_newsletter=1 and mail<>"" and mail is not null;';
+    return array_map(function($element) { return $element[0]; },
+                     Zend_Registry::get('sql')->fetchAllByColumn($request));
+  }
+
   public function getNewslettersReceivers($id_newsletter, $recipient_size) {
     // do not use Storm as it must be ordered by mail AND filtered by joined column
     $req = 'select bib_admin_users.* from bib_admin_users join newsletters_users on bib_admin_users.id_user = newsletters_users.user_id where newsletter_id = ' . $id_newsletter . ' and newsletters_users.send is false order by bib_admin_users.mail limit ' . $recipient_size;
@@ -500,6 +506,7 @@ class Class_Users extends Storm_Model_Abstract {
                                   'id_panier_courant' => 0,
                                   'settings' => '',
                                   'statut' => 0,
+                                  'disable_newsletter' => 0,
                                   'id_int_bib' => null];
 
   public static function currentUserId() {
@@ -1964,4 +1971,14 @@ class Class_Users extends Storm_Model_Abstract {
       return $this->getDateFin();
     return $this->addDaysToCurrentDate(30);
   }
+
+
+  public function isNewsletterDisabled() {
+    return true === (boolean) $this->getDisableNewsletter();
+  }
+
+
+  public function isNewsletterEnabled() {
+    return !$this->isNewsletterDisabled();
+  }
 }
diff --git a/library/ZendAfi/View/Helper/Abonne/Newsletters.php b/library/ZendAfi/View/Helper/Abonne/Newsletters.php
index 08a3c65c0ac..4b1a64760e1 100644
--- a/library/ZendAfi/View/Helper/Abonne/Newsletters.php
+++ b/library/ZendAfi/View/Helper/Abonne/Newsletters.php
@@ -20,33 +20,49 @@
  */
 class ZendAfi_View_Helper_Abonne_Newsletters extends ZendAfi_View_Helper_Abonne_Abstract {
   public function abonne_newsletters($user) {
-    if (count(Class_Newsletter::findAll()) == 0)
+    if (Class_Newsletter::count() == 0)
       return '';
 
     $newsletter_info = $this->_newsletterInfo($user);
 
     $action_url = $this->view->url(['controller' => 'abonne', 'action' => 'edit']);
+
     return $this->tagFicheAbonne(
-                                 '<p>' . $newsletter_info . '</p>'.
+                                 $this->_newsletterInfo($user)
+                                 .
                                  $this->view->tagAnchor($action_url,
-                                                        $this->view->_('Modifier mes abonnements')),
+                                                        $this->_('Modifier mes abonnements')),
                                  'newsletter',
                                  $action_url);
   }
 
 
   protected function _newsletterInfo($user) {
-    if (!$newsletters = $user->getNewsletters())
-      return $this->view->_("Vous n'êtes abonné à aucune lettre d'information");
+    if ($user->isNewsletterDisabled())
+      return
+        $this->_tag('p',
+                    $this->_("Vous avez désactivé la réception des lettres d'information"))
+        .$this->_tag('p',
+        $this->view->tagAnchor($this->view->url(['controller' => 'abonne', 'action' => 'enable-newsletter']),
+                               $this->_('Activer l\'envoi des lettres d\'information')))
+;
+
+    $newsletters = $user->getNewsletters();
 
     $titres = [];
-    foreach($newsletters as $newsletter) {
+    foreach($newsletters as $newsletter)
       $titres[] = $newsletter->getTitre();
-    }
-
-    $newsletter_info = $this->view->_('Vous êtes abonné');
-    $newsletter_info .= count($titres) > 1 ? $this->view->_(" aux lettres d'information: ") : $this->view->_(" à la lettre d'information: ");
-    return $newsletter_info .= implode(', ', $titres);
 
+    $information_string =  $this->_plural(count($titres),
+                          "Vous n'êtes abonné à aucune lettre d'information",
+                          "Vous êtes abonné à la lettre d'information: %s",
+                          "Vous êtes abonné aux lettres d'information: %s",
+                          implode(', ', $titres));
+    return  $this->_tag('p',
+                        $information_string)
+      .$this->_tag('p',
+            $this->view->tagAnchor($this->view->url(['controller' => 'abonne', 'action' => 'disable-newsletter'])
+,
+                                   $this->_('Ne plus recevoir de lettres d\'information')));
   }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Settings.php b/library/templates/Intonation/Library/Settings.php
index 1abe027b39a..15c88eab63c 100644
--- a/library/templates/Intonation/Library/Settings.php
+++ b/library/templates/Intonation/Library/Settings.php
@@ -272,6 +272,7 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
 
                                                 'accessibility' => 'class fas fa-low-vision',
                                                 'login' => 'class fas fa-sign-in-alt',
+                                                'ban' => 'class fas fa-ban',
 
                                                 'previous' => 'class fas fa-chevron-left',
                                                 'next' => 'class fas fa-chevron-right',
diff --git a/library/templates/Intonation/Library/View/Wrapper/Newsletter.php b/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
index 63c4ccd9b4f..bbccfa79700 100644
--- a/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
+++ b/library/templates/Intonation/Library/View/Wrapper/Newsletter.php
@@ -38,7 +38,13 @@ class Intonation_Library_View_Wrapper_Newsletter extends Intonation_Library_View
 
 
   public function getMainLink() {
-    $params = $this->_model->hasRecipient(Class_Users::getIdentity())
+    if (!$current_user = Class_Users::getIdentity())
+      return '';
+
+    if ($current_user->isNewsletterDisabled())
+      return '';
+
+    $params = $this->_model->hasRecipient($current_user)
       ? ['Url' => $this->_view->url(['controller' => 'abonne',
                                      'action' => 'unsubscribe-newsletter',
                                      'id' => $this->_model->getId()]),
diff --git a/library/templates/Intonation/View/RenderNewsletters.php b/library/templates/Intonation/View/RenderNewsletters.php
index 5a440ed8913..2c815a3d1d1 100644
--- a/library/templates/Intonation/View/RenderNewsletters.php
+++ b/library/templates/Intonation/View/RenderNewsletters.php
@@ -38,6 +38,50 @@ class Intonation_View_RenderNewsletters extends ZendAfi_View_Helper_BaseHelper {
 
     return
       $this->_tag('h3', $this->_('Les lettres d\'information'))
+      . $this->_renderNewsletterAction()
       . $this->view->renderTruncateList(new Storm_Collection($newsletters), $callback);
   }
+
+
+  protected function _renderNewsletterAction() {
+    if (!$user = Class_Users::getIdentity())
+      return '';
+
+    return $user->isNewsletterEnabled()
+      ? $this->_disableNewsletterAction()
+      : $this->_enableNewsletterAction();
+  }
+
+
+  protected function _disableNewsletterAction() {
+    $ico = Class_Template::current()->getIco($this->view, 'ban', 'utils');
+    return $this->_renderAction('disable-newsletter',
+                                $this->_('Ne plus recevoir de lettre d\'information'),
+                                'btn-warning',
+                                $ico);
+  }
+
+
+  protected function _enableNewsletterAction() {
+    $ico = Class_Template::current()->getIco($this->view, 'add', 'utils');
+    return $this->_renderAction('enable-newsletter',
+                                $this->_('Pouvoir recevoir des lettres d\'information'),
+                                'btn-success',
+                                $ico);
+  }
+
+
+  protected function _renderAction($action, $text, $class, $ico) {
+    $url = $this->view->url(['controller' => 'abonne',
+                             'action' => $action]);
+
+    $attribs = ['Tag' => 'url',
+                'Text' => $text,
+                'OnlyClasses' => "card-link btn btn-sm text-light ".$class,
+                'Url' => $url,
+                'Title' => $text,
+                'Image' => $ico];
+
+    return $this->view->tagAction(new Intonation_Library_Link($attribs));
+  }
 }
diff --git a/tests/application/modules/opac/controllers/AbonneControllerNewslettersTest.php b/tests/application/modules/opac/controllers/AbonneControllerNewslettersTest.php
index 9d26cdbd28f..4dc92ed226a 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerNewslettersTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerNewslettersTest.php
@@ -71,10 +71,10 @@ abstract class AbonneControllerWithTwoNewslettersTestCase extends AbstractAbonne
                                      'contenu' => 'du patrimoine']);
 
 
-    $this->marcus->subscribeNewsletter($this->concerts);
 
-    $mock = $this
-      ->mock()
+
+    $mock = $this->mock();
+    $mock
       ->whenCalled('fetchAll')
       ->with('select user_id as id from user_group_memberships where user_group_id=' . $this->concerts->getDedicatedGroup()->getId())
       ->answers([['id' => $this->marcus->getId()]])
@@ -83,9 +83,16 @@ abstract class AbonneControllerWithTwoNewslettersTestCase extends AbstractAbonne
       ->with('select user_id as id from user_group_memberships where user_group_id=' . $this->visites->getDedicatedGroup()->getId())
       ->answers([])
 
-      ;
+      ->whenCalled('fetchAll')
+      ->with('select user_id as id from user_group_memberships where user_group_id=3')
+      ->answers([])
+
+      ->beStrict();
+
 
     Zend_Registry::set('sql', $mock);
+
+    $this->marcus->subscribeNewsletter($this->concerts);
   }
 }
 
@@ -158,16 +165,34 @@ class AbonneControllerNewslettersFicheActionWithOneSubscriptionTest extends Abon
 }
 
 
+
+
+class AbonneControllerNewsletterDisableActionTest extends AbonneControllerWithTwoNewslettersTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/opac/abonne/disable-newsletter');
+  }
+
+
+  /** @test */
+  public function userSettingsNewsletterDisabledShouldBeTrue() {
+    $this->assertTrue($this->marcus->isNewsletterDisabled());
+  }
+}
+
+
+
+
 class AbonneControllerNewslettersFicheActionWithTwoSubscriptionsTest extends AbonneControllerWithTwoNewslettersTestCase {
   public function setUp() {
     parent::setUp();
     $this->marcus->subscribeNewsletter($this->visites);
 
     Zend_Registry::get('sql')
+
       ->whenCalled('fetchAll')
       ->with('select user_id as id from user_group_memberships where user_group_id=' . $this->visites->getDedicatedGroup()->getId())
-      ->answers([['id' => $this->marcus->getId()]])
-      ;
+      ->answers([['id' => $this->marcus->getId()]]);
 
     $this->dispatch('/opac/abonne/fiche');
   }
@@ -198,6 +223,54 @@ class AbonneControllerNewslettersFicheActionWithNoSubscriptionTest extends Abonn
 }
 
 
+
+
+class AbonneControllerNewslettersEditWithDisabledNewsletterActionTest
+  extends AbonneControllerWithTwoNewslettersTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $mock = $this->mock();
+    $mock
+      ->whenCalled('fetchAll')
+      ->with('select distinct(bib_admin_users.mail) from bib_admin_users where disable_newsletter=1 and mail<>"" and mail is not null;',true,false)
+      ->answers([])
+
+      ->whenCalled('fetchAll')
+      ->with("select user_id as id from user_group_memberships where user_group_id=2")
+      ->answers([])
+
+      ->beStrict()
+      ;
+
+    Zend_Registry::set('sql', $mock);
+
+    $this->marcus->unsubscribeNewsletter($this->concerts);
+    $this->marcus->setDisableNewsletter(true)->save();
+
+    $this->dispatch('/opac/abonne/edit');
+  }
+
+
+  /** @test */
+  public function noNewsletterSubscriptionShouldBeDisplayed() {
+    $this->assertXPathContentContains('//label[@for="disable_newsletter"]',
+                                      "Je ne veux plus recevoir du tout de lettre d'information",$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function noNewsletterSubscriptionShouldBeChecked() {
+    $this->assertXPath('//input[@name="disable_newsletter"][@checked="checked"]',
+                       $this->_response->getBody());
+  }
+}
+
+
+
+
+
 class AbonneControllerNewslettersEditActionTest extends AbonneControllerWithTwoNewslettersTestCase {
   public function setUp() {
     parent::setUp();
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index 56f0ae89617..35e09dca96e 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -3343,4 +3343,20 @@ class UpgradeDB_390_Test extends UpgradeDBTestCase {
   public function tableDriveCheckoutShouldContainsCreatedAtColumn() {
     $this->assertFieldType('drive_checkout', 'created_at', 'datetime');
   }
-}
\ No newline at end of file
+}
+
+
+
+
+class UpgradeDB_391_Test extends UpgradeDBTestCase {
+  public function prepare() {
+    $this->dropIndexedFieldFrom('bib_admin_users', 'disable_newsletter');
+    $this->silentQuery('ALTER TABLE bib_admin_users drop column disable_newsletter;');
+  }
+
+  /** @test */
+  public function  bibAdminUsersShouldHaveNewFieldDisableNewsletterIndexed() {
+    $this->assertFieldType('bib_admin_users', 'disable_newsletter', 'tinyint(1)');
+    $this->assertIndex('bib_admin_users', 'disable_newsletter');
+  }
+}
diff --git a/tests/library/Class/NewsletterDispatchTest.php b/tests/library/Class/NewsletterDispatchTest.php
index 2bde3a3cc8b..f21739ce050 100644
--- a/tests/library/Class/NewsletterDispatchTest.php
+++ b/tests/library/Class/NewsletterDispatchTest.php
@@ -19,8 +19,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-
-abstract class Class_NewsletterDispatchTestCase extends ModelTestCase {
+abstract class Class_NewsletterDispatchFixturesTestCase extends ModelTestCase {
   protected $_storm_default_to_volatile = true;
 
   public function setUp() {
@@ -71,7 +70,6 @@ abstract class Class_NewsletterDispatchTestCase extends ModelTestCase {
     Class_UserGroup::setMemoryCleaner(function() {});
     Class_Newsletter_Dispatch::setTimeSource(new TimeSourceForTest('2016-07-21 15:54:25'));
     Class_Newsletter::setTimeSource(new TimeSourceForTest('2016-07-21 15:54:25'));
-    $this->_dispatch = Class_Newsletter_Dispatch::newFrom($newsletter);
 
     $this->mock_transport = new MockMailTransport();
     Zend_Mail::setDefaultTransport($this->mock_transport);
@@ -80,6 +78,16 @@ abstract class Class_NewsletterDispatchTestCase extends ModelTestCase {
 
 
 
+abstract class Class_NewsletterDispatchTestCase extends Class_NewsletterDispatchFixturesTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->_dispatch = Class_Newsletter_Dispatch::newFrom(Class_Newsletter::find(1));
+  }
+}
+
+
+
+
 class Class_NewsletterDispatchFirstRunTest extends Class_NewsletterDispatchTestCase {
   public function setUp() {
     parent::setUp();
@@ -161,4 +169,76 @@ class Class_NewsletterDispatchReRunTest extends Class_NewsletterDispatchTestCase
                         Class_Newsletter_DispatchUser::findFirstBy(['user_id' => 22])
                         ->getSent());
   }
-}
\ No newline at end of file
+}
+
+
+
+
+class Class_NewsletterDispatchWithMultipleSubscriptionBlacklistForOneUserTest extends Class_NewsletterDispatchFixturesTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $newsletter = Class_Newsletter::find(1);
+    $newsletter->unsubscribeUser(Class_Users::find(23));
+    $newsletter->unsubscribeUser(Class_Users::find(23));
+  }
+
+
+  /** @test */
+  public function countBlacklistEntryForUser23ShouldBeOne() {
+    $this->assertEquals(1, Class_Newsletter_Blacklist::countBy(['newsletter_id'=>1,'mail'=>"pat@server.com"]));
+  }
+}
+
+
+
+
+class Class_NewsletterDispatchWithDisabledUserForNewNewsLetterTest extends Class_NewsletterDispatchFixturesTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $mock_sql = $this->mock()
+                     ->whenCalled('fetchAllByColumn')
+                     ->with("select distinct(bib_admin_users.mail) from bib_admin_users where disable_newsletter=1 and mail<>\"\" and mail is not null;")
+                     ->answers([['pat@server.com']])
+                     ->beStrict();
+
+    Zend_Registry::set('sql', $mock_sql);
+
+    Class_Users::find(23)->setDisableNewsLetter(true);
+
+    $newsletter = $this->fixture('Class_Newsletter',
+                                 ['id' => 3,
+                                  'titre' => 'Nouveautés fiction Jeunesse',
+                                  'mail_subject' => 'Nouveautés fictives',
+                                  'contenu' => 'Notre sélection du mois',
+                                  'id_catalogue' => '',
+                                  'id_panier' => '',
+                                  'expediteur' => 'papanoel@pole-nord.com',
+                                  'user_groups' => [Class_UserGroup::find(14),
+                                                    Class_UserGroup::find(15)]]);
+
+    $this->_dispatch = Class_Newsletter_Dispatch::newFrom($newsletter);
+
+    $this->_dispatch->collectRecipients();
+    $this->_dispatch->sendBy(1);
+  }
+
+
+  /** @test */
+  public function mailTransportShouldHaveSent2Mails() {
+    $this->assertEquals(2, $this->mock_transport->count());
+  }
+
+
+  /** @test */
+  public function dispatchToUser23ShouldBeNull() {
+    $this->assertNull(Class_Newsletter_DispatchUser::findfirstBy(['user_id'=>23]));
+  }
+
+
+  /** @test */
+  public function dispatchToEmailPatAtServerDotComShouldBeNull() {
+    $this->assertNull(Class_Newsletter_DispatchUser::findfirstBy(['mail'=>'pat@server.com']));
+  }
+}
diff --git a/tests/scenarios/Templates/TemplatesTest.php b/tests/scenarios/Templates/TemplatesTest.php
index f860a8e8c94..e97083bd70d 100644
--- a/tests/scenarios/Templates/TemplatesTest.php
+++ b/tests/scenarios/Templates/TemplatesTest.php
@@ -5067,6 +5067,19 @@ class TemplatesDispatchNewsletterWidgetTest extends TemplatesIntonationTestCase
     $this->dispatch('/admin/widget/edit-widget/id/25/id_profil/72', true);
     $this->assertXpath('//input[@name="titre"][@type="text"]');
   }
+
+  /** @test */
+  public function buttonDisableNewsletterShouldBePresent() {
+    $this->fixture('Class_Newsletter',
+                   ['id' => 3,
+                    'titre' => 'Jeunesse',
+                    'mail_subject' => 'Jeunesse',
+                    'contenu' => 'les nouveautés jeunesse']);
+
+    $this->dispatch('/opac/abonne/fiche', true);
+    $this->assertXPathContentContains('//a[@href="/abonne/disable-newsletter"]',  'Ne plus recevoir de lettre d\'information', $this->_response->getBody());
+
+  }
 }
 
 
-- 
GitLab