From 46919123e9fcb8a80cb62f723d659b236a4eb3d6 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Mon, 31 Aug 2020 11:44:30 +0200 Subject: [PATCH] Use events to send contributions administrative emails Factorize Cleanup code refs #1355 --- galette/includes/dependencies.php | 12 + .../Crud/ContributionsController.php | 262 ++--------- galette/lib/Galette/Entity/Adherent.php | 2 +- galette/lib/Galette/Entity/Contribution.php | 25 ++ .../lib/Galette/Events/ContribListener.php | 425 ++++++++++++++++++ galette/lib/Galette/Repository/Members.php | 2 +- 6 files changed, 490 insertions(+), 238 deletions(-) create mode 100644 galette/lib/Galette/Events/ContribListener.php diff --git a/galette/includes/dependencies.php b/galette/includes/dependencies.php index a0c2950a0..3e1902db5 100644 --- a/galette/includes/dependencies.php +++ b/galette/includes/dependencies.php @@ -642,6 +642,18 @@ $container['event_manager'] = function ($c) { ) ); + $emitter->useListenerProvider( + new Galette\Events\ContribListener( + $c->get('preferences'), + $c->get('router'), + $c->get('history'), + $c->get('flash'), + $c->get('login'), + $c->get('zdb') + ) + ); + + return $emitter; }; diff --git a/galette/lib/Galette/Controllers/Crud/ContributionsController.php b/galette/lib/Galette/Controllers/Crud/ContributionsController.php index 89efb565c..5b82ba629 100644 --- a/galette/lib/Galette/Controllers/Crud/ContributionsController.php +++ b/galette/lib/Galette/Controllers/Crud/ContributionsController.php @@ -527,9 +527,7 @@ class ContributionsController extends CrudController ->withHeader('Location', $redirect_url); } - $success_detected = []; $error_detected = []; - $warning_detected = []; $redirect_url = null; $id_cotis = null; @@ -575,58 +573,10 @@ class ContributionsController extends CrudController if (count($error_detected) == 0) { $store = $contrib->store(); if ($store === true) { - $success_detected[] = _T('Contribution has been successfully stored'); - //contribution has been stored :) - if ($new) { - //if an external script has been configured, we call it - if ($this->preferences->pref_new_contrib_script) { - $es = new \Galette\IO\ExternalScript($this->preferences); - $res = $contrib->executePostScript($es); - - if ($res !== true) { - //send admin an email with all details - if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) { - $mail = new GaletteMail($this->preferences); - $mail->setSubject( - _T("Post contribution script failed") - ); - - $recipients = []; - foreach ($this->preferences->vpref_email_newadh as $pref_email) { - $recipients[$pref_email] = $pref_email; - } - $mail->setRecipients($recipients); - - $message = _T("The configured post contribution script has failed."); - $message .= "\n" . _T("You can find contribution information and script output below."); - $message .= "\n\n"; - $message .= $res; - - $mail->setMessage($message); - $sent = $mail->send(); - - if (!$sent) { - $txt = _T('Post contribution script has failed.'); - $this->history->add($txt, $message); - $warning_detected[] = $txt; - //Mails are disabled... We log (not safe, but)... - Analog::log( - 'Email to admin has not been sent. Here was the data: ' . - "\n" . print_r($res, true), - Analog::ERROR - ); - } - } else { - //Mails are disabled... We log (not safe, but)... - Analog::log( - 'Post contribution script has failed. Here was the data: ' . - "\n" . print_r($res, true), - Analog::ERROR - ); - } - } - } - } + $this->flash->addMessage( + 'success_detected', + _T('Contribution has been successfully stored') + ); } else { //something went wrong :'( $error_detected[] = _T("An error occurred while storing the contribution."); @@ -635,186 +585,31 @@ class ContributionsController extends CrudController } if (count($error_detected) == 0) { - // Get member information - $adh = new Adherent($this->zdb); - $adh->load($contrib->member); - - if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) { - $texts = new Texts( - $this->preferences, - $this->router, - array( - 'name_adh' => custom_html_entity_decode($adh->sname), - 'firstname_adh' => custom_html_entity_decode($adh->surname), - 'lastname_adh' => custom_html_entity_decode($adh->name), - 'mail_adh' => custom_html_entity_decode($adh->getEmail()), - 'login_adh' => custom_html_entity_decode($adh->login), - 'deadline' => custom_html_entity_decode($contrib->end_date), - 'contrib_info' => custom_html_entity_decode($contrib->info), - 'contrib_amount' => custom_html_entity_decode($contrib->amount), - 'contrib_type' => custom_html_entity_decode($contrib->type->libelle) - ) - ); - if ( - $new && isset($_POST['mail_confirm']) - && $_POST['mail_confirm'] == '1' - ) { - if (GaletteMail::isValidEmail($adh->getEmail())) { - $text = 'contrib'; - if (!$contrib->isCotis()) { - $text = 'donation'; - } - $mtxt = $texts->getTexts($text, $adh->language); - - $mail = new GaletteMail($this->preferences); - $mail->setSubject($texts->getSubject()); - $mail->setRecipients( - array( - $adh->getEmail() => $adh->sname - ) - ); - - $link_card = ''; - if (strpos($mtxt->tbody, '{LINK_MEMBERCARD}') !== false) { - //member card link is present in mail model, let's add it - $links = new Links($this->zdb); - if ($hash = $links->generateNewLink(Links::TARGET_MEMBERCARD, $contrib->member)) { - $link_card = $this->preferences->getURL() . - $this->router->pathFor('directlink', ['hash' => $hash]); - } - } - - $link_pdf = ''; - if (strpos($mtxt->tbody, '{LINK_CONTRIBPDF}') !== false) { - //contribution receipt link is present in mail model, let's add it - $links = new Links($this->zdb); - $ltype = $contrib->type->isExtension() ? Links::TARGET_INVOICE : Links::TARGET_RECEIPT; - if ($hash = $links->generateNewLink($ltype, $contrib->id)) { - $link_pdf = $this->preferences->getURL() . - $this->router->pathFor('directlink', ['hash' => $hash]); - } - } - - //set replacements, even if empty, to be sure. - $texts->setReplaces([ - 'link_membercard' => $link_card, - 'link_contribpdf' => $link_pdf - ]); - - $mail->setMessage($texts->getBody()); - $sent = $mail->send(); - - if ($sent) { - $this->history->add( - preg_replace( - array('/%name/', '/%email/'), - array($adh->sname, $adh->getEmail()), - _T("Email sent to user %name (%email)") - ) - ); - } else { - $txt = preg_replace( - array('/%name/', '/%email/'), - array($adh->sname, $adh->getEmail()), - _T("A problem happened while sending contribution receipt to user %name (%email)") - ); - $this->history->add($txt); - $error_detected[] = $txt; - } - } else { - $txt = preg_replace( - array('/%name/', '/%email/'), - array($adh->sname, $adh->getEmail()), - _T("Trying to send an email to a member (%name) with an invalid address: %email") - ); - $this->history->add($txt); - $warning_detected[] = $txt; - } - } - - // Sent email to admin if pref checked - if ($new && $this->preferences->pref_bool_mailadh) { - // Get email text in database - $text = 'newcont'; - if (!$contrib->isCotis()) { - $text = 'newdonation'; - } - $texts->getTexts($text, $this->preferences->pref_lang); - - $mail = new GaletteMail($this->preferences); - $mail->setSubject($texts->getSubject()); - - $recipients = []; - foreach ($this->preferences->vpref_email_newadh as $pref_email) { - $recipients[$pref_email] = $pref_email; - } - $mail->setRecipients($recipients); - - $mail->setMessage($texts->getBody()); - $sent = $mail->send(); - - if ($sent) { - $this->history->add( - preg_replace( - array('/%name/', '/%email/'), - array($adh->sname, $adh->getEmail()), - _T("Email sent to admin for user %name (%email)") - ) - ); - } else { - $txt = preg_replace( - array('/%name/', '/%email/'), - array($adh->sname, $adh->getEmail()), - _T("A problem happened while sending to admin notification for user %name (%email) contribution") - ); - $this->history->add($txt); - $warning_detected[] = $txt; - } - } + // send email to member + if (isset($post['mail_confirm']) && $post['mail_confirm'] == '1') { + $contrib->setSendmail(); //flag to send creation email } - if (count($success_detected) > 0) { - foreach ($success_detected as $success) { - $this->flash->addMessage( - 'success_detected', - $success - ); - } - } - - - if (count($warning_detected) > 0) { - foreach ($warning_detected as $warning) { - $this->flash->addMessage( - 'warning_detected', - $warning - ); - } - } - - if (count($error_detected) == 0) { - if ($contrib->isTransactionPart() && $contrib->transaction->getMissingAmount() > 0) { - //new contribution - $redirect_url = $this->router->pathFor( - 'addContribution', - [ - 'type' => $post['contrib_type'] - ] - ) . '?' . Transaction::PK . '=' . $contrib->transaction->id . - '&' . Adherent::PK . '=' . $contrib->member; - } else { - //contributions list for member - $redirect_url = $this->router->pathFor( - 'contributions', - [ - 'type' => 'contributions' - ] - ) . '?' . Adherent::PK . '=' . $contrib->member; - } + $this->session->contribution = null; + if ($contrib->isTransactionPart() && $contrib->transaction->getMissingAmount() > 0) { + //new contribution + $redirect_url = $this->router->pathFor( + 'addContribution', + [ + 'type' => $post['contrib_type'] + ] + ) . '?' . Transaction::PK . '=' . $contrib->transaction->id . + '&' . Adherent::PK . '=' . $contrib->member; + } else { + //contributions list for member + $redirect_url = $this->router->pathFor( + 'contributions', + [ + 'type' => 'contributions' + ] + ) . '?' . Adherent::PK . '=' . $contrib->member; } - } - - if (count($error_detected) > 0) { + } else { //something went wrong. //store entity in session $this->session->contribution = $contrib; @@ -827,11 +622,6 @@ class ContributionsController extends CrudController $error ); } - } else { - $this->session->contribution = null; - if ($redirect_url === null) { - $redirect_url = $this->router->pathFor('contributions', ['type' => $args['type']]); - } } //redirect to calling action diff --git a/galette/lib/Galette/Entity/Adherent.php b/galette/lib/Galette/Entity/Adherent.php index 86a0600d7..c5a2b589d 100644 --- a/galette/lib/Galette/Entity/Adherent.php +++ b/galette/lib/Galette/Entity/Adherent.php @@ -2001,7 +2001,7 @@ class Adherent } /** - * Shoudl we send administrative emails to member? + * Should we send administrative emails to member? * * @return boolean */ diff --git a/galette/lib/Galette/Entity/Contribution.php b/galette/lib/Galette/Entity/Contribution.php index d1af0b1ce..84334049d 100644 --- a/galette/lib/Galette/Entity/Contribution.php +++ b/galette/lib/Galette/Entity/Contribution.php @@ -85,6 +85,8 @@ class Contribution private $errors; + private $sendmail = false; + /** * Default constructor * @@ -1318,4 +1320,27 @@ class Contribution } } } + + /** + * Flag creation mail sending + * + * @param boolean $send True (default) to send creation email + * + * @return Contribution + */ + public function setSendmail($send = true) + { + $this->sendmail = $send; + return $this; + } + + /** + * Should we send administrative emails to member? + * + * @return boolean + */ + public function sendEMail() + { + return $this->sendmail; + } } diff --git a/galette/lib/Galette/Events/ContribListener.php b/galette/lib/Galette/Events/ContribListener.php new file mode 100644 index 000000000..f609251e6 --- /dev/null +++ b/galette/lib/Galette/Events/ContribListener.php @@ -0,0 +1,425 @@ +. + * + * @category Events + * @package Galette + * + * @author Johan Cwiklinski + * @copyright 2020 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link http://galette.tuxfamily.org + * @since Available since 2020-08-25 + */ + +namespace Galette\Events; + +use Galette\Core\Db; +use Galette\Core\GaletteMail; +use Galette\Core\History; +use Galette\Core\Login; +use Galette\Core\Password; +use Galette\Core\Preferences; +use Galette\Entity\Adherent; +use Galette\Entity\Contribution; +use Galette\Entity\Texts; +use Analog\Analog; +use League\Event\Event; +use League\Event\ListenerAcceptorInterface; +use League\Event\ListenerProviderInterface; +use Slim\Flash\Messages; +use Slim\Router; + +/** + * Event listener for contributions + * + * @category Events + * @name MemberListener + * @package Galette + * @author Johan Cwiklinski + * @copyright 2020 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link https://galette.eu + * @since Available since 2020-08-25 + */ +class ContribListener implements ListenerProviderInterface +{ + /** @var Preferences */ + private $preferences; + /** @var Router */ + private $router; + /** @var History */ + private $history; + /** @var Messages */ + private $flash; + /** @var Login */ + private $login; + /** @var Db */ + private $zdb; + + /** + * Constructor + * + * @param Preferences $preferences Preferences instance + * @param Router $router Router instance + * @param History $history History instance + * @param Messages $flash Messages instance + * @param Login $login Login instance + * @param Db $zdb Db instance + */ + public function __construct( + Preferences $preferences, + Router $router, + History $history, + Messages $flash, + Login $login, + Db $zdb + ) { + $this->preferences = $preferences; + $this->router = $router; + $this->history = $history; + $this->flash = $flash; + $this->login = $login; + $this->zdb = $zdb; + } + + /** + * Set up contribution listeners + * + * @param ListenerAcceptorInterface $acceptor Listener + * + * @return void + */ + public function provideListeners(ListenerAcceptorInterface $acceptor) + { + $acceptor->addListener( + 'contribution.add', + function ($event, $contrib) { + $this->contributionAdded($event, $contrib); + } + ); + } + + /** + * Contribution added listener + * + * @param Event $event Raised event + * @param Contribution $contrib Added contribution + * + * @return void + */ + public function contributionAdded(Event $event, Contribution $contrib) + { + Analog::log( + '[' . get_class($this) . '] Event contribution.add emitted for #' . $contrib->id, + Analog::DEBUG + ); + + $this->callPostContributionScript($contrib); + + if ($contrib->sendEMail()) { + $this->sendContribEmail($contrib, true); + } + $this->sendAdminEmail($contrib, true); + } + + /** + * Get texts replacements array for member + * + * @param Contribution $contrib Contribution instance + * @param Adherent $member Member instance + * + * @return array + */ + private function getReplacements(Contribution $contrib, Adherent $member): array + { + $mreplaces = []; + $mreplaces = [ + 'name_adh' => custom_html_entity_decode( + $member->sname + ), + 'firstname_adh' => custom_html_entity_decode( + $member->surname + ), + 'lastname_adh' => custom_html_entity_decode( + $member->name + ), + 'mail_adh' => custom_html_entity_decode( + $member->getEmail() + ), + 'login_adh' => custom_html_entity_decode( + $member->login + ), + 'deadline' => custom_html_entity_decode( + $contrib->end_date + ), + 'contrib_info' => custom_html_entity_decode( + $contrib->info + ), + 'contrib_amount' => custom_html_entity_decode( + $contrib->amount + ), + 'contrib_type' => custom_html_entity_decode( + $contrib->type->libelle + ) + + ]; + return $mreplaces; + } + + /** + * Send account email to member + * + * @param Adherent $member Member + * @param boolean $new New member or editing existing one + * + * @return void + */ + private function sendContribEmail(Adherent $member, $new) + { + if ($this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED) { + //if email has been disabled in the preferences, we should not be here ; + //we do not throw an error, just a simple warning that will be show later + $msg = _T("You asked Galette to send a confirmation email to the member, but email has been disabled in the preferences."); + $this->flash->addMessage( + 'warning_detected', + $msg + ); + return; + } + + // Get member information + $member = new Adherent($this->zdb); + $member->load($contrib->member); + + if ($member->getEmail() == '' && !$member->self_adh) { + $this->flash->addMessage( + 'error_detected', + _T("- You can't send a confirmation by email if the member hasn't got an address!") + ); + return; + } + + $texts = new Texts( + $this->preferences, + $this->router, + $this->getReplacements($contrib, $member) + ); + + $text = 'contrib'; + if (!$contrib->isCotis()) { + $text = 'donation'; + } + $mtxt = $texts->getTexts($text, $member->language); + + $mail = new GaletteMail($this->preferences); + $mail->setSubject($texts->getSubject()); + $mail->setRecipients( + array( + $member->getEmail() => $member->sname + ) + ); + + $link_card = ''; + if (strpos($mtxt->tbody, '{LINK_MEMBERCARD}') !== false) { + //member card link is present in mail model, let's add it + $links = new Links($this->zdb); + if ($hash = $links->generateNewLink(Links::TARGET_MEMBERCARD, $contrib->member)) { + $link_card = $this->preferences->getURL() . + $this->router->pathFor('directlink', ['hash' => $hash]); + } + } + + $link_pdf = ''; + if (strpos($mtxt->tbody, '{LINK_CONTRIBPDF}') !== false) { + //contribution receipt link is present in mail model, let's add it + $links = new Links($this->zdb); + $ltype = $contrib->type->isExtension() ? Links::TARGET_INVOICE : Links::TARGET_RECEIPT; + if ($hash = $links->generateNewLink($ltype, $contrib->id)) { + $link_pdf = $this->preferences->getURL() . + $this->router->pathFor('directlink', ['hash' => $hash]); + } + } + + //set replacements, even if empty, to be sure. + $texts->setReplaces([ + 'link_membercard' => $link_card, + 'link_contribpdf' => $link_pdf + ]); + + $mail->setMessage($texts->getBody()); + $sent = $mail->send(); + + if ($sent) { + $this->history->add( + preg_replace( + array('/%name/', '/%email/'), + array($member->sname, $member->getEmail()), + _T("Email sent to user %name (%email)") + ) + ); + } else { + $txt = preg_replace( + array('/%name/', '/%email/'), + array($member->sname, $member->getEmail()), + _T("A problem happened while sending contribution receipt to user %name (%email)") + ); + $this->history->add($txt); + $this->flash->addMessage( + 'warning_detected', + $txt + ); + } + } + + /** + * Send new contribution email to admin + * + * @param Contribution $contrib Contribution + * @param boolean $new New contribution or editing existing one + * + * @return void + */ + private function sendAdminEmail(Contribution $contrib, $new) + { + if ( + $this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED + || !$this->preferences->pref_bool_mailadh + || (!$new && $contrib->member != $this->login->id) + ) { + return; + } + + // Get member information + $member = new Adherent($this->zdb); + $member->load($contrib->member); + + $texts = new Texts( + $this->preferences, + $this->router, + $this->getReplacements($contrib, $member) + ); + + // Sent email to admin if pref checked + // Get email text in database + $text = 'newcont'; + if (!$contrib->isCotis()) { + $text = 'newdonation'; + } + $texts->getTexts($text, $this->preferences->pref_lang); + + $mail = new GaletteMail($this->preferences); + $mail->setSubject($texts->getSubject()); + + $recipients = []; + foreach ($this->preferences->vpref_email_newadh as $pref_email) { + $recipients[$pref_email] = $pref_email; + } + $mail->setRecipients($recipients); + + $mail->setMessage($texts->getBody()); + $sent = $mail->send(); + + if ($sent) { + $this->history->add( + preg_replace( + array('/%name/', '/%email/'), + array($member->sname, $member->getEmail()), + _T("Email sent to admin for user %name (%email)") + ) + ); + } else { + $txt = preg_replace( + array('/%name/', '/%email/'), + array($member->sname, $member->getEmail()), + _T("A problem happened while sending to admin notification for user %name (%email) contribution") + ); + $this->history->add($txt); + $this->flash->addMessage( + 'warning_detected', + $txt + ); + } + } + + /** + * Call post contribution script from Preferences + * + * @param Contribution $contrib Added contribution + * + * @return void + */ + private function callPostContributionScript($contrib) + { + //if an external script has been configured, we call it + if ($this->preferences->pref_new_contrib_script) { + $es = new \Galette\IO\ExternalScript($this->preferences); + $res = $contrib->executePostScript($es); + + if ($res !== true) { + //send admin an email with all details + if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) { + $mail = new GaletteMail($this->preferences); + $mail->setSubject( + _T("Post contribution script failed") + ); + + $recipients = []; + foreach ($this->preferences->vpref_email_newadh as $pref_email) { + $recipients[$pref_email] = $pref_email; + } + $mail->setRecipients($recipients); + + $message = _T("The configured post contribution script has failed."); + $message .= "\n" . _T("You can find contribution information and script output below."); + $message .= "\n\n"; + $message .= $res; + + $mail->setMessage($message); + $sent = $mail->send(); + + if (!$sent) { + $txt = _T('Post contribution script has failed.'); + $this->history->add($txt, $message); + $warning_detected[] = $txt; + //Mails are disabled... We log (not safe, but)... + Analog::log( + 'Email to admin has not been sent. Here was the data: ' . + "\n" . print_r($res, true), + Analog::ERROR + ); + } + } else { + //Mails are disabled... We log (not safe, but)... + Analog::log( + 'Post contribution script has failed. Here was the data: ' . + "\n" . print_r($res, true), + Analog::ERROR + ); + } + } + } + } +} diff --git a/galette/lib/Galette/Repository/Members.php b/galette/lib/Galette/Repository/Members.php index e8f6eb054..062fd153f 100644 --- a/galette/lib/Galette/Repository/Members.php +++ b/galette/lib/Galette/Repository/Members.php @@ -1267,7 +1267,7 @@ class Members } } - //shoudl be retrieved from members_fields + //FIXME: should be retrieved from members_fields $dates = [ 'ddn_adh' => 'birth_date', 'date_crea_adh' => 'creation_date', -- 2.39.2