Commit 69bc1549 authored by Patrick Barroca's avatar Patrick Barroca 🐧

rel #46014 : sending with groups work in progress

parent 971e16fe
......@@ -11,6 +11,35 @@ $adapter->query('CREATE TABLE IF NOT EXISTS `newsletter_group_subscription` ('
.') engine=MyISAM default charset=utf8');
$adapter->query('CREATE TABLE IF NOT EXISTS `newsletter_dispatch` ('
. 'id int(11) unsigned not null auto_increment,'
. 'newsletter_id int(11) not null,'
. 'title varchar(255) not null,'
. 'body_text text null,'
. 'body_html text null,'
. 'sender varchar(255) not null,'
. 'collected tinyint(1) not null default 0,'
. 'created_on datetime not null,'
. 'ended_on datetime null,'
. 'PRIMARY KEY (`id`),'
. 'KEY `newsletter_id` (`newsletter_id`),'
. 'KEY `created_on` (`created_on`),'
. 'KEY `ended_on` (`ended_on`)'
.') engine=MyISAM default charset=utf8');
$adapter->query('CREATE TABLE IF NOT EXISTS `newsletter_dispatch_user` ('
. 'id int(11) unsigned not null auto_increment,'
. 'dispath_id int(11) unsigned not null,'
. 'user_id int(11) not null,'
. 'mail varchar(255) not null,'
. 'sent tinyint(1) not null default 0,'
. 'PRIMARY KEY (`id`),'
. 'KEY `dispath_id` (`dispath_id`),'
. 'KEY `user_id` (`user_id`)'
.') engine=MyISAM default charset=utf8');
try {
$adapter->query('ALTER TABLE user_groups '
. 'ADD COLUMN model_class varchar(255) null default \'\', '
......
......@@ -22,15 +22,17 @@
class Class_Batch_SendNewsletters extends Class_Batch_Abstract {
use Trait_StaticCommand;
protected
$_newsletter,
$_dispatch,
$_previous_mail,
$_time_limit,
$_template_mail;
public function __construct($newsletter) {
$this->_newsletter = $newsletter;
public function __construct($dispatch) {
$this->_dispatch = $dispatch;
}
......@@ -43,7 +45,7 @@ class Class_Batch_SendNewsletters extends Class_Batch_Abstract {
return [$_SERVER['HTTP_HOST'],
$_SERVER['SERVER_NAME'],
BASE_URL,
$this->_newsletter->getId()];
$this->_dispatch->getId()];
}
......@@ -53,50 +55,48 @@ class Class_Batch_SendNewsletters extends Class_Batch_Abstract {
public function run() {
exec('/usr/bin/php -f '
. realpath(dirname(__FILE__)) . '/../../../scripts/sendNewsletter.php '
. $this->getExecParams()
. ' > /dev/null &');
$this->getCommand()
->exec('/usr/bin/php -f '
. realpath(dirname(__FILE__)) . '/../../../scripts/sendNewsletter.php '
. $this->getExecParams()
. ' > /dev/null &');
return $this;
}
public function sendOne($email) {
if (!$this->_newsletter)
if (!$this->_dispatch)
return;
$mock_user = new Class_Entity();
$mock_user->setId(0)->setMail($email);
$this->_template_mail = $this->_newsletter->newTemplate();
$this->_template_mail = $this->_dispatch->getTemplate();
$this->_sendPage([$mock_user]);
}
public function sendAllBy($page_size) {
if (!$this->_newsletter)
if (!$this->_dispatch)
return;
$letter = $this->_newsletter;
Class_NewsletterSubscription::resetSendFlagForNewsletter($letter->getId());
$this->_previous_mail = '';
$this->_template_mail=$letter->newTemplate();
while (0 < count(array_filter($receivers = $letter->getReceivers($page_size)))) {
$this->_template_mail = $this->_dispatch->getTemplate();
while (0 < count(array_filter($recipients = $this->_dispatch->getRecipientsPage($page_size)))) {
$this->_clearMemory()
->_giveMeMoreTime(30)
->_sendPage($receivers);
->_sendPage($recipients);
$letter->setLastDistributionDateWithFormat();
Class_NewsletterSubscription::updateSendFlagForReceivers($letter->getId(),
$receivers);
Class_Newsletter_DispatchUser::beAllSent($recipients);
}
$this->getTimeLimit()->reset();
$this->sendOne($letter->getExpediteur());
$this->_dispatch->beEnded();
$this->getTimeLimit()->reset();
$this->sendOne($this->_dispatch->getSender());
}
......@@ -118,7 +118,7 @@ class Class_Batch_SendNewsletters extends Class_Batch_Abstract {
protected function _sendPage($receivers) {
foreach($receivers as $receiver) {
$mail = $this->_newsletter->newMailFromTemplate($this->_template_mail, $receiver);
$mail = $this->_template_mail->mailFor($receiver);
$this->_sendTo($receiver, $mail);
}
}
......@@ -126,13 +126,14 @@ class Class_Batch_SendNewsletters extends Class_Batch_Abstract {
protected function _sendTo($receiver,$mail) {
$receiver_mail = $receiver->getMail();
if ($this->_previous_mail != $receiver_mail) {
$this->_previous_mail = $receiver_mail;
$mail->addTo($receiver_mail);
if ($this->_previous_mail == $receiver_mail)
return;
if (count($mail->getRecipients())>0)
$mail->send();
}
$this->_previous_mail = $receiver_mail;
$mail->addTo($receiver_mail);
if (count($mail->getRecipients())>0)
$mail->send();
}
......
......@@ -65,6 +65,10 @@ class Class_Newsletter extends Storm_Model_Abstract {
'role' => 'newsletter',
'dependents' => 'delete'],
'dispatchs' => ['model' => 'Class_Newsletter_Dispatch',
'role' => 'newsletter',
'dependents' => 'delete'],
'newsletter_group_subscriptions' => ['model' => 'Class_NewsletterGroupSubscription',
'role' => 'newsletter',
'dependents' => 'delete'],
......@@ -94,7 +98,8 @@ class Class_Newsletter extends Storm_Model_Abstract {
public function send() {
return (new Class_Batch_SendNewsletters($this))->run();
$dispatch = Class_Newsletter_Dispatch::newFrom($this);
return (new Class_Batch_SendNewsletters($dispatch))->run();
}
......@@ -112,25 +117,6 @@ class Class_Newsletter extends Storm_Model_Abstract {
}
public function fillTemplate($user,$body) {
$body = str_replace("%user.id%",$user->getId(),$body);
$body = str_replace("%hash%",$this->hashForUser($user),$body);
return $body;
}
public function newMailFromTemplate($template,$user) {
$mail = new ZendAfi_Mail('utf8');
$mail
->setSubject($template->getTitre())
->setBodyText($this->fillTemplate($user,$template->getBodyText()))
->setBodyHTML($this->fillTemplate($user,$template->getBodyHTML()))
->setFrom($template->getExpediteur());
return $mail;
}
public function newTemplate() {
$template = new Class_Entity();
......@@ -147,12 +133,17 @@ class Class_Newsletter extends Storm_Model_Abstract {
public function getNumberOfUsers() {
return sprintf('%05d', count($this->getRecipientsUsersIds()));
}
public function getRecipientsUsersIds() {
$ids = [];
foreach($this->getRecipientsGroups() as $group)
foreach($group->getUsersIds() as $id)
$ids[] = $id;
return sprintf('%05d', count(array_unique($ids)));
return array_unique($ids);
}
......@@ -165,12 +156,6 @@ class Class_Newsletter extends Storm_Model_Abstract {
}
public function generateMails($recipient_size) {
(new Class_Batch_SendNewsletters($this))
->sendAllBy($recipient_size);
}
public function getReceivers($recipient_size) {
return Class_Users::getNewslettersReceivers($this->getId(), $recipient_size);
}
......@@ -332,10 +317,6 @@ class Class_Newsletter extends Storm_Model_Abstract {
'user_id' => $user_ids]);
}
public function hashForUser($user) {
return hash('crc32b', $this->getId().$user->getMail());
}
public function duplicate() {
if ($this->isNew())
......
<?php
/**
* Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
*
* AFI-OPAC 2.0 is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* AFI-OPAC 2.0 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with AFI-OPAC 2.0; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Newsletter_DispatchLoader extends Storm_Model_Loader {
public function newFrom($newsletter) {
$template = $newsletter->newTemplate();
$model = Class_Newsletter_Dispatch::newInstance(['newsletter' => $newsletter,
'title' => $template->getTitre(),
'body_text' => $template->getBodyText(),
'body_html' => $template->getBodyHTML(),
'sender' => $template->getExpediteur()]);
$model->save();
return $model;
}
}
class Class_Newsletter_Dispatch extends Storm_Model_Abstract {
use Trait_TimeSource;
protected $_table_name = 'newsletter_dispatch';
protected $_loader_class = 'Newsletter_DispatchLoader';
protected $_belongs_to = ['newsletter' => ['model' => 'Class_Newsletter']];
protected $_has_many = ['dispatch_users' => ['model' => 'Class_Newsletter_DispatchUser',
'role' => 'dispatch',
'dependents' => 'delete'],
'users' => ['through' => 'dispatch_users']];
protected $_default_attribute_values = ['collected' => 0];
public function beforeSave() {
if ($this->isNew())
$this->setCreatedOn($this->getCurrentDateTime());
}
public function getTemplate() {
return Class_Newsletter_Template::newFrom($this);
}
public function sendBy($recipient_size) {
$this->collectRecipients();
(new Class_Batch_SendNewsletters($this))
->sendAllBy($recipient_size);
}
public function collectRecipients() {
if ($this->getCollected())
return;
$closure = function($model) {
if (!$model->hasMail())
return;
$params = ['dispatch_id' => $this->getId(),
'user_id' => $model->getId()];
if (Class_Newsletter_DispatchUser::findFirstBy($params))
return;
Class_Newsletter_DispatchUser::newInstance(['dispatch_id' => $this->getId(),
'user_id' => $model->getId(),
'mail' => $model->getMail()])
->save();
Class_Newsletter_DispatchUser::clearCache();
};
$groups = $this->getNewsletter()->getRecipientsGroups();
foreach($groups as $group)
$group->withUsersDo($closure);
$this->setCollected(1)->save();
return $this;
}
public function getRecipientsPage($size) {
return Class_Newsletter_DispatchUser::findNotSentOf($this, $size);
}
public function beEnded() {
$this->setEndedOn($this->getCurrentDateTime())->save();
return $this;
}
}
<?php
/**
* Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
*
* AFI-OPAC 2.0 is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* AFI-OPAC 2.0 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with AFI-OPAC 2.0; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Newsletter_DispatchUserLoader extends Storm_Model_Loader {
public function findNotSentOf($dispatch, $limit) {
return Class_Newsletter_DispatchUser::findAllBy(['dispatch_id' => $dispatch->getId(),
'sent' => 0,
'order' => 'mail',
'limit' => $limit]);
}
public function beAllSent($models) {
foreach($models as $model)
$model->setSent(1)->save();
}
}
class Class_Newsletter_DispatchUser extends Storm_Model_Abstract {
protected $_table_name = 'newsletter_dispatch_user';
protected $_loader_class = 'Newsletter_DispatchUserLoader';
protected $_belongs_to = ['dispatch' => ['model' => 'Class_Newsletter_Dispatch'],
'user' => ['model' => 'Class_Users']];
protected $_default_attribute_values = ['sent' => 0];
}
<?php
/**
* Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class Class_Newsletter_Template extends Class_Entity {
public static function newFrom($dispatch) {
return (new static())
->setTitre($dispatch->getTitle())
->setBodyText($dispatch->getBodyText())
->setBodyHTML($dispatch->getBodyHtml())
->setExpediteur($dispatch->getSender())
->setNewsletterId($dispatch->getNewsletterId());
}
public function mailFor($dispatch_user) {
$mail = new ZendAfi_Mail('utf8');
$mail
->setSubject($this->getTitre())
->setBodyText($this->fillTemplate($dispatch_user, $this->getBodyText()))
->setBodyHTML($this->fillTemplate($dispatch_user, $this->getBodyHTML()))
->setFrom($this->getExpediteur());
return $mail;
}
public function fillTemplate($dispatch_user, $body) {
$body = str_replace("%user.id%", $dispatch_user->getUserId(), $body);
$body = str_replace("%hash%", $this->hashForUser($dispatch_user), $body);
return $body;
}
public function hashForUser($dispatch_user) {
return hash('crc32b', $this->getNewsletterId() . $dispatch_user->getMail());
}
}
\ No newline at end of file
......@@ -24,15 +24,23 @@ class UserGroupLoader extends Storm_Model_Loader {
public function getUsersIdsOf($group) {
$ids = [];
$closure = function($model) use (&$ids) {
$ids[] = $users->getId();
};
Class_UserGroup::withUsersOfDo($group, $closure);
return $ids;
}
public function withUsersOfDo($group, $closure) {
$page = 1;
while ($users = $group->getUsersPage($page, 1000)) {
$page++;
foreach($users as $user)
$ids[] = $user->getId();
$closure($user);
$this->_cleanMemory();
}
return $ids;
}
......@@ -565,4 +573,10 @@ class Class_UserGroup extends Storm_Model_Abstract {
$this->getLoader()->addAllUsersByIdsTo($ids, $this);
return $this;
}
public function withUsersDo($closure) {
$this->getLoader()->withUsersOfDo($this, $closure);
return $this;
}
}
\ No newline at end of file
......@@ -333,6 +333,11 @@ class Class_Users extends Storm_Model_Abstract {
'newsletters' => ['through' => 'subscriptions'],
'dispatch_users' => ['model' => 'Class_Newsletter_DispatchUser',
'role' => 'user'],
'dispatchs' => ['through' => 'dispatch_users'],
'avis' => ['model' => 'Class_AvisNotice',
'role' => 'user',
'order' => 'date_avis desc'],
......
......@@ -40,5 +40,4 @@ require_once "startup.php";
setupOpac();
Class_Newsletter::find($argv[4])->generateMails(20);
?>
\ No newline at end of file
Class_Newsletter_Dispatch::find($argv[4])->generateMails(20);
\ No newline at end of file
......@@ -93,6 +93,8 @@ abstract class Admin_NewsletterControllerTestCase
['id' => 1,
'titre' => 'Nouveautés classique',
'contenu' => 'Notre sélection du mois',
'id_catalogue' => '',
'id_panier' => '',
'user_groups' => [$my_group, $other_group]]);
Class_Newsletter::find(1)->setLastDistributionDateWithFormat();
......@@ -102,6 +104,9 @@ abstract class Admin_NewsletterControllerTestCase
'titre' => 'Animations',
'contenu' => 'Pour les jeunes',
'last_distribution_date' => null,
'id_catalogue' => '',
'id_panier' => '',
'expediteur' => 'bokehrulez@linuxfr.org',
'user_groups' => [$my_group]]);
}
}
......@@ -510,21 +515,69 @@ class Admin_NewsletterControllerDeleteActionTest extends Admin_AbstractControlle
class Admin_NewsletterControllerSendActionTest extends Admin_NewsletterControllerTestCase {
protected
$_command,
$_dispatch;
public function setUp() {
parent::setUp();
$this->newsletter = $this->mock();
$this->onLoaderOfModel('Class_Newsletter')
->whenCalled('find')
->with(4)
->answers($this->newsletter);
$this->_command = $this->mock()
->whenCalled('exec')
->answers(true);
Class_Batch_SendNewsletters::setCommand($this->_command);
Class_Newsletter_Dispatch::setTimeSource(new TimeSourceForTest('2016-07-21 11:21:38'));
$this->dispatch('/admin/newsletter/send/id/2', true);
$this->_dispatch = Class_Newsletter::find(2)->getDispatchs()[0];
}
public function tearDown() {
Class_Batch_SendNewsletters::setCommand(null);
parent::tearDown();
}
/** @test */
public function shouldHaveCreatedDispatch() {
$this->assertNotNull($this->_dispatch);
}
/** @test */
public function shouldHaveCalledCommand() {
$this->assertTrue($this->_command->methodHasBeenCalled('exec'));
}
/** @test */
public function commandShouldHaveBeenCalledWithDispatchId() {
$this->assertContains(' "' . $this->_dispatch->getId() . '" ',
$this->_command->getFirstAttributeForLastCallOn('exec'));
}
public function dispatchDatas() {
return
[
['title', 'Animations'],
['body_text', 'Pour les jeunes
Lien pour se désinscrire de cette lettre d\'information : http://localhost/afi-opac3/newsletter/unsubscribe/newsletter/2/user/%user.id%/hash/%hash%'],
['body_html', 'Pour les jeunes<br/><a href="http://localhost/afi-opac3/newsletter/unsubscribe/newsletter/2/user/%user.id%/hash/%hash%">Je ne veux plus recevoir cette lettre d\'information</a>'],
['sender', 'bokehrulez@linuxfr.org'],
['created_on', '2016-07-21 11:21:38']
];
}
$this->newsletter
->whenCalled('send')
->answers(true)
->beStrict();
$this->dispatch('/admin/newsletter/send/id/4');
/**
* @test
* @dataProvider dispatchDatas
*/
public function testDispatchTitleShouldBeFoo($name, $expected) {
$this->assertEquals($expected, $this->_dispatch->$name);
}
......
......@@ -869,7 +869,9 @@ class UpgradeDB_304_Test extends UpgradeDBTestCase {
$this
->dropTable('newsletter_group_subscription')
->dropIndexedFieldFrom('user_groups', 'model_class')
->dropIndexedFieldFrom('user_groups', 'model_id');
->dropIndexedFieldFrom('user_groups', 'model_id')
->dropTable('newsletter_dispatch')
->dropTable('newsletter_dispatch_user');
}
......@@ -901,4 +903,16 @@ class UpgradeDB_304_Test extends UpgradeDBTestCase {
public function userGroupModelIdShouldBeIndexed() {
$this->assertIndex('user_groups', 'model_id', 'BTREE');
}
/** @test */
public function tableNewsletterDispatchShouldExists() {
$this->assertTable('newsletter_dispatch');
}
/** @test */
public function tableNewsletterDispatchUserShouldExists() {
$this->assertTable('newsletter_dispatch_user');
}
}
\ No newline at end of file
......@@ -44,4 +44,9 @@ class MockMailTransport extends Zend_Mail_Transport_Abstract {
public function getSentMails() {
return $this->_sent_mails;
}
public function count() {
return count($this->_sent_mails);
}
}
\ No newline at end of file
<
<?php
/**
* Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
abstract class Class_NewsletterDispatchTestCase extends ModelTestCase {
protected $_storm_default_to_volatile = true;
public function setUp() {
parent::setUp();
$pat = $this->fixture('Class_Users',
['id' => 23,
'login' => 'pat',
'password' => 'tap',
'mail' => 'pat@server.com']);
$laurent = $this->fixture('Class_Users',
['id' => 22,
'login' => 'lla',
'password' => 'all',
'role_level' => ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB,
'idabon' => '0007',
'id_site' => 1,
'mail' => 'lla@server.com']);
$my_group = $this->fixture('Class_UserGroup',
['id' => 14,
'libelle' => 'My Group',
'users' => [$pat, $laurent]]);