3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette contributions controller
10 * Copyright © 2020 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
27 * @category Controllers
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2020 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.9.4dev - 2020-05-08
37 namespace Galette\Controllers\Crud
;
39 use Galette\Controllers\CrudController
;
40 use Slim\Http\Request
;
41 use Slim\Http\Response
;
42 use Galette\Entity\Adherent
;
43 use Galette\Entity\Contribution
;
44 use Galette\Entity\Transaction
;
45 use Galette\Repository\Contributions
;
46 use Galette\Repository\Transactions
;
47 use Galette\Repository\Members
;
48 use Galette\Entity\ContributionsTypes
;
49 use Galette\Core\GaletteMail
;
50 use Galette\Entity\Texts
;
51 use Galette\IO\PdfMembersCards
;
52 use Galette\Repository\PaymentTypes
;
53 use Galette\Core\Links
;
57 * Galette contributions controller
59 * @category Controllers
60 * @name ContributionsController
62 * @author Johan Cwiklinski <johan@x-tnd.be>
63 * @copyright 2020 The Galette Team
64 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
65 * @link http://galette.tuxfamily.org
66 * @since Available since 0.9.4dev - 2020-05-02
69 class ContributionsController
extends CrudController
76 * Only a few things changes in add and edit pages,
77 * boths methods will use this common one.
79 * @param Request $request PSR Request
80 * @param Response $response PSR Response
81 * @param array $args Request arguments
82 * @param Contribution $contrib Contribution instance
86 public function addEditPage(
93 $ct = new ContributionsTypes($this->zdb
);
94 $contributions_types = $ct->getList($args['type'] === 'fee');
98 if (!is_int($contrib->id
)) {
99 // initialiser la structure contribution à vide (nouvelle contribution)
100 $contribution['duree_mois_cotis'] = $this->preferences
->pref_membership_ext
;
103 // template variable declaration
105 if ($args['type'] === 'fee') {
106 $title = _T("Membership fee");
108 $title = _T("Donation");
111 if ($contrib->id
!= '') {
112 $title .= ' (' . _T("modification") . ')';
114 $title .= ' (' . _T("creation") . ')';
119 'id_type_cotis' => 1,
122 'date_debut_cotis' => 1,
123 'date_fin_cotis' => $contrib->isCotis(),
124 'montant_cotis' => $contrib->isCotis() ?
1 : 0
128 'page_title' => $title,
129 'required' => $required,
130 'disabled' => $disabled,
131 'contribution' => $contrib,
132 'adh_selected' => $contrib->member
,
133 'type' => $args['type']
136 // contribution types
137 $params['type_cotis_options'] = $contributions_types;
141 $members = $m->getSelectizedMembers(
143 isset($contrib) && $contrib->member
> 0 ?
$contrib->member
: null
146 $params['members'] = [
147 'filters' => $m->getFilters(),
148 'count' => $m->getCount()
151 if (count($members)) {
152 $params['members']['list'] = $members;
155 $ext_membership = '';
156 if (isset($contrib) && $contrib->isCotis() ||
!isset($contrib) && $args['type'] === 'fee') {
157 $ext_membership = $this->preferences
->pref_membership_ext
;
159 $params['pref_membership_ext'] = $ext_membership;
160 $params['autocomplete'] = true;
165 'ajouter_contribution.tpl',
174 * @param Request $request PSR Request
175 * @param Response $response PSR Response
176 * @param array $args Request arguments
180 public function add(Request
$request, Response
$response, array $args = []): Response
182 if ($this->session
->contribution
!== null) {
183 $contrib = $this->session
->contribution
;
184 $this->session
->contribution
= null;
186 $get = $request->getQueryParams();
188 $ct = new ContributionsTypes($this->zdb
);
189 $contributions_types = $ct->getList($args['type'] === 'fee');
191 $cparams = ['type' => array_keys($contributions_types)[0]];
194 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
195 $cparams['adh'] = (int)$get[Adherent
::PK
];
199 if (isset($get[Transaction
::PK
]) && $get[Transaction
::PK
] > 0) {
200 $cparams['trans'] = $get[Transaction
::PK
];
203 $contrib = new Contribution(
206 (count($cparams) > 0 ?
$cparams : null)
209 if (isset($cparams['adh'])) {
210 $contrib->member
= $cparams['adh'];
213 if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0) {
214 $contrib->amount
= $get['montant_cotis'];
218 return $this->addEditPage($request, $response, $args, $contrib);
224 * @param Request $request PSR Request
225 * @param Response $response PSR Response
226 * @param array $args Request arguments
230 public function doAdd(Request
$request, Response
$response, array $args = []): Response
232 $args['action'] = 'add';
233 return $this->store($request, $response, $args);
242 * @param Request $request PSR Request
243 * @param Response $response PSR Response
244 * @param array $args Request arguments
248 public function list(Request
$request, Response
$response, array $args = []): Response
253 ||
isset($request->getQueryParams()['ajax'])
254 && $request->getQueryParams()['ajax'] == 'true'
258 $get = $request->getQueryParams();
260 $option = $args['option'] ??
null;
261 $value = $args['value'] ??
null;
264 switch ($args['type']) {
266 $raw_type = 'transactions';
268 case 'contributions':
269 $raw_type = 'contributions';
273 $filter_name = 'filter_' . $raw_type;
275 if (isset($this->session
->$filter_name) && $ajax === false) {
276 $filters = $this->session
->$filter_name;
278 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
279 $filters = new $filter_class();
283 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
284 $filters->filtre_cotis_adh
= (int)$get[Adherent
::PK
];
287 $filters->filtre_transactions
= false;
288 if (isset($request->getQueryParams()['max_amount'])) {
289 $filters->filtre_transactions
= true;
290 $filters->max_amount
= (int)$request->getQueryParams()['max_amount'];
293 if ($option !== null) {
296 $filters->current_page
= (int)$value;
299 $filters->orderby
= $value;
303 ($this->login
->isAdmin()
304 ||
$this->login
->isStaff())
306 if ($value == 'all') {
307 $filters->filtre_cotis_adh
= null;
309 $filters->filtre_cotis_adh
= $value;
316 if (!$this->login
->isAdmin() && !$this->login
->isStaff()) {
317 $filters->filtre_cotis_adh
= $this->login
->id
;
320 $class = '\\Galette\\Repository\\' . ucwords($raw_type);
321 $contrib = new $class($this->zdb
, $this->login
, $filters);
322 $contribs_list = $contrib->getList(true);
324 //store filters into session
325 if ($ajax === false) {
326 $this->session
->$filter_name = $filters;
329 //assign pagination variables to the template and add pagination links
330 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty());
333 'page_title' => $raw_type === 'contributions' ?
334 _T("Contributions management") : _T("Transactions management"),
335 'contribs' => $contrib,
336 'list' => $contribs_list,
337 'nb' => $contrib->getCount(),
338 'filters' => $filters,
339 'mode' => ($ajax === true ?
'ajax' : 'std')
342 if ($filters->filtre_cotis_adh
!= null) {
343 $member = new Adherent($this->zdb
);
344 $member->load($filters->filtre_cotis_adh
);
345 $tpl_vars['member'] = $member;
351 'gestion_' . $raw_type . '.tpl',
360 * @param Request $request PSR Request
361 * @param Response $response PSR Response
362 * @param array $args Request arguments
366 public function filter(Request
$request, Response
$response, array $args = []): Response
369 switch ($args['type']) {
371 $raw_type = 'transactions';
373 case 'contributions':
374 $raw_type = 'contributions';
378 $type = 'filter_' . $raw_type;
379 $post = $request->getParsedBody();
380 $error_detected = [];
382 if ($this->session
->$type !== null) {
383 $filters = $this->session
->$type;
385 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
386 $filters = new $filter_class();
389 if (isset($post['clear_filter'])) {
392 if (isset($post['max_amount'])) {
393 $filters->max_amount
= null;
397 (isset($post['nbshow']) && is_numeric($post['nbshow']))
399 $filters->show
= $post['nbshow'];
402 if (isset($post['end_date_filter']) ||
isset($post['start_date_filter'])) {
404 if (isset($post['start_date_filter'])) {
405 $filters->start_date_filter
= $post['start_date_filter'];
407 if (isset($post['end_date_filter'])) {
408 $filters->end_date_filter
= $post['end_date_filter'];
410 } catch (\Exception
$e) {
411 $error_detected[] = $e->getMessage();
415 if (isset($post['payment_type_filter'])) {
416 $ptf = (int)$post['payment_type_filter'];
417 $ptypes = new PaymentTypes(
422 $ptlist = $ptypes->getList();
423 if (isset($ptlist[$ptf])) {
424 $filters->payment_type_filter
= $ptf;
425 } elseif ($ptf == -1) {
426 $filters->payment_type_filter
= null;
428 $error_detected[] = _T("- Unknown payment type!");
433 $this->session
->$type = $filters;
435 if (count($error_detected) > 0) {
437 foreach ($error_detected as $error) {
438 $this->flash
->addMessage(
447 ->withHeader('Location', $this->router
->pathFor('contributions', ['type' => $raw_type]));
456 * @param Request $request PSR Request
457 * @param Response $response PSR Response
458 * @param array $args Request arguments
462 public function edit(Request
$request, Response
$response, array $args = []): Response
464 if ($this->session
->contribution
!== null) {
465 $contrib = $this->session
->contribution
;
466 $this->session
->contribution
= null;
468 $contrib = new Contribution($this->zdb
, $this->login
, (int)$args['id']);
469 if ($contrib->id
== '') {
470 //not possible to load contribution, exit
471 $this->flash
->addMessage(
476 _T("Unable to load contribution #%id!")
481 ->withHeader('Location', $this->router
->pathFor(
483 ['type' => 'contributions']
488 return $this->addEditPage($request, $response, $args, $contrib);
494 * @param Request $request PSR Request
495 * @param Response $response PSR Response
496 * @param array $args Request arguments
500 public function doEdit(Request
$request, Response
$response, array $args = []): Response
502 $args['action'] = 'edit';
503 return $this->store($request, $response, $args);
507 * Store contribution (new or existing)
509 * @param Request $request PSR Request
510 * @param Response $response PSR Response
511 * @param array $args Request arguments
515 public function store(Request
$request, Response
$response, array $args = []): Response
517 $post = $request->getParsedBody();
518 $action = $args['action'];
520 if ($action == 'edit' && isset($post['btnreload'])) {
521 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
522 $redirect_url .= '?' . Adherent
::PK
. '=' . $post[Adherent
::PK
] . '&' .
523 ContributionsTypes
::PK
. '=' . $post[ContributionsTypes
::PK
] . '&' .
524 'montant_cotis=' . $post['montant_cotis'];
527 ->withHeader('Location', $redirect_url);
530 $success_detected = [];
531 $error_detected = [];
532 $warning_detected = [];
533 $redirect_url = null;
536 if (isset($args['id'])) {
537 $id_cotis = $args['id'];
540 if ($this->session
->contribution
!== null) {
541 $contrib = $this->session
->contribution
;
542 $this->session
->contribution
= null;
544 if ($id_cotis === null) {
545 $contrib = new Contribution($this->zdb
, $this->login
);
547 $contrib = new Contribution($this->zdb
, $this->login
, (int)$id_cotis);
551 // flagging required fields for first step only
553 'id_type_cotis' => 1,
556 'montant_cotis' => 1, //TODO: not always required, see #196
557 'date_debut_cotis' => 1,
558 'date_fin_cotis' => ($args['type'] === 'fee')
563 $valid = $contrib->check($post, $required, $disabled);
564 if ($valid !== true) {
565 $error_detected = array_merge($error_detected, $valid);
568 if (count($error_detected) == 0) {
569 //all goes well, we can proceed
571 if ($contrib->id
== '') {
575 if (count($error_detected) == 0) {
576 $store = $contrib->store();
577 if ($store === true) {
578 $success_detected[] = _T('Contribution has been successfully stored');
579 //contribution has been stored :)
581 //if an external script has been configured, we call it
582 if ($this->preferences
->pref_new_contrib_script
) {
583 $es = new \Galette\IO\
ExternalScript($this->preferences
);
584 $res = $contrib->executePostScript($es);
587 //send admin an email with all details
588 if ($this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
589 $mail = new GaletteMail($this->preferences
);
591 _T("Post contribution script failed")
595 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
596 $recipients[$pref_email] = $pref_email;
598 $mail->setRecipients($recipients);
600 $message = _T("The configured post contribution script has failed.");
601 $message .= "\n" . _T("You can find contribution information and script output below.");
605 $mail->setMessage($message);
606 $sent = $mail->send();
609 $txt = _T('Post contribution script has failed.');
610 $this->history
->add($txt, $message);
611 $warning_detected[] = $txt;
612 //Mails are disabled... We log (not safe, but)...
614 'Email to admin has not been sent. Here was the data: ' .
615 "\n" . print_r($res, true),
620 //Mails are disabled... We log (not safe, but)...
622 'Post contribution script has failed. Here was the data: ' .
623 "\n" . print_r($res, true),
631 //something went wrong :'(
632 $error_detected[] = _T("An error occurred while storing the contribution.");
637 if (count($error_detected) == 0) {
638 // Get member information
639 $adh = new Adherent($this->zdb
);
640 $adh->load($contrib->member
);
642 if ($this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
647 'name_adh' => custom_html_entity_decode($adh->sname
),
648 'firstname_adh' => custom_html_entity_decode($adh->surname
),
649 'lastname_adh' => custom_html_entity_decode($adh->name
),
650 'mail_adh' => custom_html_entity_decode($adh->getEmail()),
651 'login_adh' => custom_html_entity_decode($adh->login
),
652 'deadline' => custom_html_entity_decode($contrib->end_date
),
653 'contrib_info' => custom_html_entity_decode($contrib->info
),
654 'contrib_amount' => custom_html_entity_decode($contrib->amount
),
655 'contrib_type' => custom_html_entity_decode($contrib->type
->libelle
)
659 $new && isset($_POST['mail_confirm'])
660 && $_POST['mail_confirm'] == '1'
662 if (GaletteMail
::isValidEmail($adh->getEmail())) {
664 if (!$contrib->isCotis()) {
667 $mtxt = $texts->getTexts($text, $adh->language
);
669 $mail = new GaletteMail($this->preferences
);
670 $mail->setSubject($texts->getSubject());
671 $mail->setRecipients(
673 $adh->getEmail() => $adh->sname
678 if (strpos($mtxt->tbody
, '{LINK_MEMBERCARD}') !== false) {
679 //member card link is present in mail model, let's add it
680 $links = new Links($this->zdb
);
681 if ($hash = $links->generateNewLink(Links
::TARGET_MEMBERCARD
, $contrib->member
)) {
682 $link_card = $this->preferences
->getURL() .
683 $this->router
->pathFor('directlink', ['hash' => $hash]);
688 if (strpos($mtxt->tbody
, '{LINK_CONTRIBPDF}') !== false) {
689 //contribution receipt link is present in mail model, let's add it
690 $links = new Links($this->zdb
);
691 $ltype = $contrib->type
->isExtension() ? Links
::TARGET_INVOICE
: Links
::TARGET_RECEIPT
;
692 if ($hash = $links->generateNewLink($ltype, $contrib->id
)) {
693 $link_pdf = $this->preferences
->getURL() .
694 $this->router
->pathFor('directlink', ['hash' => $hash]);
698 //set replacements, even if empty, to be sure.
699 $texts->setReplaces([
700 'link_membercard' => $link_card,
701 'link_contribpdf' => $link_pdf
704 $mail->setMessage($texts->getBody());
705 $sent = $mail->send();
710 array('/%name/', '/%email/'),
711 array($adh->sname
, $adh->getEmail()),
712 _T("Email sent to user %name (%email)")
717 array('/%name/', '/%email/'),
718 array($adh->sname
, $adh->getEmail()),
719 _T("A problem happened while sending contribution receipt to user %name (%email)")
721 $this->history
->add($txt);
722 $error_detected[] = $txt;
726 array('/%name/', '/%email/'),
727 array($adh->sname
, $adh->getEmail()),
728 _T("Trying to send an email to a member (%name) with an invalid address: %email")
730 $this->history
->add($txt);
731 $warning_detected[] = $txt;
735 // Sent email to admin if pref checked
736 if ($new && $this->preferences
->pref_bool_mailadh
) {
737 // Get email text in database
739 if (!$contrib->isCotis()) {
740 $text = 'newdonation';
742 $texts->getTexts($text, $this->preferences
->pref_lang
);
744 $mail = new GaletteMail($this->preferences
);
745 $mail->setSubject($texts->getSubject());
748 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
749 $recipients[$pref_email] = $pref_email;
751 $mail->setRecipients($recipients);
753 $mail->setMessage($texts->getBody());
754 $sent = $mail->send();
759 array('/%name/', '/%email/'),
760 array($adh->sname
, $adh->getEmail()),
761 _T("Email sent to admin for user %name (%email)")
766 array('/%name/', '/%email/'),
767 array($adh->sname
, $adh->getEmail()),
768 _T("A problem happened while sending to admin notification for user %name (%email) contribution")
770 $this->history
->add($txt);
771 $warning_detected[] = $txt;
776 if (count($success_detected) > 0) {
777 foreach ($success_detected as $success) {
778 $this->flash
->addMessage(
786 if (count($warning_detected) > 0) {
787 foreach ($warning_detected as $warning) {
788 $this->flash
->addMessage(
795 if (count($error_detected) == 0) {
796 if ($contrib->isTransactionPart() && $contrib->transaction
->getMissingAmount() > 0) {
798 $redirect_url = $this->router
->pathFor(
801 'type' => $post['contrib_type']
803 ) . '?' . Transaction
::PK
. '=' . $contrib->transaction
->id
.
804 '&' . Adherent
::PK
. '=' . $contrib->member
;
806 //contributions list for member
807 $redirect_url = $this->router
->pathFor(
810 'type' => 'contributions'
812 ) . '?' . Adherent
::PK
. '=' . $contrib->member
;
817 if (count($error_detected) > 0) {
818 //something went wrong.
819 //store entity in session
820 $this->session
->contribution
= $contrib;
821 $redirect_url = $this->router
->pathFor($args['action'] . 'Contribution', $args);
824 foreach ($error_detected as $error) {
825 $this->flash
->addMessage(
831 $this->session
->contribution
= null;
832 if ($redirect_url === null) {
833 $redirect_url = $this->router
->pathFor('contributions', ['type' => $args['type']]);
837 //redirect to calling action
840 ->withHeader('Location', $redirect_url);
847 * Get redirection URI
849 * @param array $args Route arguments
853 public function redirectUri(array $args = [])
855 return $this->router
->pathFor('contributions', ['type' => $args['type']]);
861 * @param array $args Route arguments
865 public function formUri(array $args = [])
867 return $this->router
->pathFor(
868 'doRemoveContribution',
874 * Get confirmation removal page title
876 * @param array $args Route arguments
880 public function confirmRemoveTitle(array $args = [])
884 switch ($args['type']) {
886 $raw_type = 'transactions';
888 case 'contributions':
889 $raw_type = 'contributions';
893 if (isset($args['ids'])) {
895 _T('Remove %1$s %2$s'),
897 ($raw_type === 'contributions') ?
_T('contributions') : _T('transactions')
901 _T('Remove %1$s #%2$s'),
902 ($raw_type === 'contributions') ?
_T('contribution') : _T('transaction'),
911 * @param array $args Route arguments
912 * @param array $post POST values
916 protected function doDelete(array $args, array $post)
919 switch ($args['type']) {
921 $raw_type = 'transactions';
923 case 'contributions':
924 $raw_type = 'contributions';
928 $class = '\\Galette\Repository\\' . ucwords($raw_type);
929 $contribs = new $class($this->zdb
, $this->login
);
930 $rm = $contribs->remove($args['ids'] ??
$args['id'], $this->history
);