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
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.9.4dev - 2020-05-08
38 namespace Galette\Controllers\Crud
;
40 use Galette\Controllers\CrudController
;
42 use Slim\Http\Request
;
43 use Slim\Http\Response
;
44 use Galette\Entity\Adherent
;
45 use Galette\Entity\Contribution
;
46 use Galette\Entity\Transaction
;
50 use Galette\Repository\Contributions
;
51 use Galette\Repository\Transactions
;
52 use Galette\Repository\Members
;
53 use Galette\Entity\ContributionsTypes
;
54 use Galette\Core\GaletteMail
;
55 use Galette\Entity\Texts
;
56 use Galette\IO\PdfMembersCards
;
57 use Galette\Repository\PaymentTypes
;
58 use Galette\Core\Links
;
65 * Galette contributions controller
67 * @category Controllers
68 * @name ContributionsController
70 * @author Johan Cwiklinski <johan@x-tnd.be>
71 * @copyright 2020 The Galette Team
72 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
73 * @link http://galette.tuxfamily.org
74 * @since Available since 0.9.4dev - 2020-05-02
77 class ContributionsController
extends CrudController
84 * Only a few things changes in add and edit pages,
85 * boths methods will use this common one.
87 * @param Request $request PSR Request
88 * @param Response $response PSR Response
89 * @param array $args Request arguments
90 * @param Contribution $contrib Contribution instance
94 public function addEditPage(
100 $get = $request->getQueryParams();
102 // contribution types
103 $ct = new ContributionsTypes($this->zdb
);
104 $contributions_types = $ct->getList($args['type'] === 'fee');
108 if (!is_int($contrib->id
)) {
109 // initialiser la structure contribution à vide (nouvelle contribution)
110 $contribution['duree_mois_cotis'] = $this->preferences
->pref_membership_ext
;
113 // template variable declaration
115 if ($args['type'] === 'fee') {
116 $title = _T("Membership fee");
118 $title = _T("Donation");
121 if ($contrib->id
!= '') {
122 $title .= ' (' . _T("modification") . ')';
124 $title .= ' (' . _T("creation") . ')';
129 'id_type_cotis' => 1,
132 'date_debut_cotis' => 1,
133 'date_fin_cotis' => $contrib->isCotis(),
134 'montant_cotis' => $contrib->isCotis() ?
1 : 0
138 'page_title' => $title,
139 'required' => $required,
140 'disabled' => $disabled,
141 'contribution' => $contrib,
142 'adh_selected' => $contrib->member
,
143 'type' => $args['type']
146 // contribution types
147 $params['type_cotis_options'] = $contributions_types;
151 $members = $m->getSelectizedMembers(
153 isset($contrib) && $contrib->member
> 0 ?
$contrib->member
: null
156 $params['members'] = [
157 'filters' => $m->getFilters(),
158 'count' => $m->getCount()
161 if (count($members)) {
162 $params['members']['list'] = $members;
165 $ext_membership = '';
166 if (isset($contrib) && $contrib->isCotis() ||
!isset($contrib) && $args['type'] === 'fee') {
167 $ext_membership = $this->preferences
->pref_membership_ext
;
169 $params['pref_membership_ext'] = $ext_membership;
170 $params['autocomplete'] = true;
175 'ajouter_contribution.tpl',
184 * @param Request $request PSR Request
185 * @param Response $response PSR Response
186 * @param array $args Request arguments
190 public function add(Request
$request, Response
$response, array $args = []) :Response
192 if ($this->session
->contribution
!== null) {
193 $contrib = $this->session
->contribution
;
194 $this->session
->contribution
= null;
196 $get = $request->getQueryParams();
198 $ct = new ContributionsTypes($this->zdb
);
199 $contributions_types = $ct->getList($args['type'] === 'fee');
201 $cparams = ['type' => array_keys($contributions_types)[0]];
204 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
205 $cparams['adh'] = (int)$get[Adherent
::PK
];
210 if (isset($get[Transaction
::PK
]) && $get[Transaction
::PK
] > 0) {
211 $cparams['trans'] = $get[Transaction
::PK
];
214 $contrib = new Contribution(
217 (count($cparams) > 0 ?
$cparams : null)
220 if (isset($cparams['adh'])) {
221 $contrib->member
= $cparams['adh'];
224 if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0) {
225 $contrib->amount
= $get['montant_cotis'];
229 return $this->addEditPage($request, $response, $args, $contrib);
235 * @param Request $request PSR Request
236 * @param Response $response PSR Response
237 * @param array $args Request arguments
241 public function doAdd(Request
$request, Response
$response, array $args = []) :Response
243 $args['action'] = 'add';
244 return $this->store($request, $response, $args);
253 * @param Request $request PSR Request
254 * @param Response $response PSR Response
255 * @param array $args Request arguments
259 public function list(Request
$request, Response
$response, array $args = []) :Response
262 if ($request->isXhr()
263 ||
isset($request->getQueryParams()['ajax'])
264 && $request->getQueryParams()['ajax'] == 'true'
268 $get = $request->getQueryParams();
270 $option = $args['option'] ??
null;
271 $value = $args['value'] ??
null;
274 switch ($args['type']) {
276 $raw_type = 'transactions';
278 case 'contributions':
279 $raw_type = 'contributions';
283 $filter_name = 'filter_' . $raw_type;
285 if (isset($this->session
->$filter_name) && $ajax === false) {
286 $filters = $this->session
->$filter_name;
288 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
289 $filters = new $filter_class();
293 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
294 $filters->filtre_cotis_adh
= (int)$get[Adherent
::PK
];
298 if (isset($request->getQueryParams()['max_amount'])) {
299 $filters->filtre_transactions
= true;
300 $filters->max_amount
= (int)$request->getQueryParams()['max_amount'];
302 $filters->filtre_transactions
= false;
303 $filters->max_amount
= null;
306 if ($option !== null) {
309 $filters->current_page
= (int)$value;
312 $filters->orderby
= $value;
315 if (($this->login
->isAdmin()
316 ||
$this->login
->isStaff())
318 if ($value == 'all') {
319 $filters->filtre_cotis_adh
= null;
321 $filters->filtre_cotis_adh
= $value;
328 if (!$this->login
->isAdmin() && !$this->login
->isStaff()) {
329 $filters->filtre_cotis_adh
= $this->login
->id
;
332 $class = '\\Galette\\Repository\\' . ucwords($raw_type);
333 $contrib = new $class($this->zdb
, $this->login
, $filters);
334 $contribs_list = $contrib->getList(true);
336 //store filters into session
337 if ($ajax === false) {
338 $this->session
->$filter_name = $filters;
341 //assign pagination variables to the template and add pagination links
342 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty());
345 'page_title' => $raw_type === 'contributions' ?
346 _T("Contributions management") : _T("Transactions management"),
347 'contribs' => $contrib,
348 'list' => $contribs_list,
349 'nb' => $contrib->getCount(),
350 'filters' => $filters,
351 'mode' => ($ajax === true ?
'ajax' : 'std')
354 if ($filters->filtre_cotis_adh
!= null) {
355 $member = new Adherent($this->zdb
);
356 $member->load($filters->filtre_cotis_adh
);
357 $tpl_vars['member'] = $member;
363 'gestion_' . $raw_type . '.tpl',
372 * @param Request $request PSR Request
373 * @param Response $response PSR Response
374 * @param array $args Request arguments
378 public function filter(Request
$request, Response
$response, array $args = []) :Response
381 switch ($args['type']) {
383 $raw_type = 'transactions';
385 case 'contributions':
386 $raw_type = 'contributions';
390 $type = 'filter_' . $raw_type;
391 $post = $request->getParsedBody();
392 $error_detected = [];
394 if ($this->session
->$type !== null) {
395 $filters = $this->session
->$type;
397 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
398 $filters = new $filter_class();
401 if (isset($post['clear_filter'])) {
404 if (isset($post['max_amount'])) {
405 $filters->max_amount
= null;
408 if ((isset($post['nbshow']) && is_numeric($post['nbshow']))
410 $filters->show
= $post['nbshow'];
413 if (isset($post['end_date_filter']) ||
isset($post['start_date_filter'])) {
415 if (isset($post['start_date_filter'])) {
416 $field = _T("start date filter");
417 $filters->start_date_filter
= $post['start_date_filter'];
419 if (isset($post['end_date_filter'])) {
420 $field = _T("end date filter");
421 $filters->end_date_filter
= $post['end_date_filter'];
423 } catch (\Exception
$e) {
424 $error_detected[] = $e->getMessage();
428 if (isset($post['payment_type_filter'])) {
429 $ptf = (int)$post['payment_type_filter'];
430 $ptypes = new PaymentTypes(
435 $ptlist = $ptypes->getList();
436 if (isset($ptlist[$ptf])) {
437 $filters->payment_type_filter
= $ptf;
438 } elseif ($ptf == -1) {
439 $filters->payment_type_filter
= null;
441 $error_detected[] = _T("- Unknown payment type!");
446 $this->session
->$type = $filters;
448 if (count($error_detected) > 0) {
450 foreach ($error_detected as $error) {
451 $this->flash
->addMessage(
460 ->withHeader('Location', $this->router
->pathFor('contributions', ['type' => $raw_type]));
469 * @param Request $request PSR Request
470 * @param Response $response PSR Response
471 * @param array $args Request arguments
475 public function edit(Request
$request, Response
$response, array $args = []) :Response
477 if ($this->session
->contribution
!== null) {
478 $contrib = $this->session
->contribution
;
479 $this->session
->contribution
= null;
481 $contrib = new Contribution($this->zdb
, $this->login
, (int)$args['id']);
482 if ($contrib->id
== '') {
483 //not possible to load contribution, exit
484 $this->flash
->addMessage(
489 _T("Unable to load contribution #%id!")
494 ->withHeader('Location', $this->router
->pathFor(
496 ['type' => 'contributions']
501 return $this->addEditPage($request, $response, $args, $contrib);
507 * @param Request $request PSR Request
508 * @param Response $response PSR Response
509 * @param array $args Request arguments
513 public function doEdit(Request
$request, Response
$response, array $args = []) :Response
515 $args['action'] = 'edit';
516 return $this->store($request, $response, $args);
520 * Store contribution (new or existing)
522 * @param Request $request PSR Request
523 * @param Response $response PSR Response
524 * @param array $args Request arguments
528 public function store(Request
$request, Response
$response, array $args = []) :Response
530 $post = $request->getParsedBody();
531 $action = $args['action'];
533 if ($action == 'edit' && isset($post['btnreload'])) {
534 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
535 $redirect_url .= '?' . Adherent
::PK
. '=' . $post[Adherent
::PK
] . '&' .
536 ContributionsTypes
::PK
. '=' . $post[ContributionsTypes
::PK
] . '&' .
537 'montant_cotis=' . $post['montant_cotis'];
540 ->withHeader('Location', $redirect_url);
543 $success_detected = [];
544 $error_detected = [];
545 $warning_detected = [];
546 $redirect_url = null;
549 if (isset($args['id'])) {
550 $id_cotis = $args['id'];
553 $id_adh = $post['id_adh'];
555 if ($this->session
->contribution
!== null) {
556 $contrib = $this->session
->contribution
;
557 $this->session
->contribution
= null;
559 if ($id_cotis === null) {
560 $contrib = new Contribution($this->zdb
, $this->login
);
562 $contrib = new Contribution($this->zdb
, $this->login
, (int)$id_cotis);
566 // flagging required fields for first step only
568 'id_type_cotis' => 1,
571 'montant_cotis' => 1, //TODO: not always required, see #196
572 'date_debut_cotis' => 1,
573 'date_fin_cotis' => ($args['type'] === 'fee')
578 $valid = $contrib->check($post, $required, $disabled);
579 if ($valid !== true) {
580 $error_detected = array_merge($error_detected, $valid);
583 if (count($error_detected) == 0) {
584 //all goes well, we can proceed
586 if ($contrib->id
== '') {
590 if (count($error_detected) == 0) {
591 $store = $contrib->store();
592 if ($store === true) {
593 $success_detected[] = _T('Contribution has been successfully stored');
594 //contribution has been stored :)
596 //if an external script has been configured, we call it
597 if ($this->preferences
->pref_new_contrib_script
) {
598 $es = new \Galette\IO\
ExternalScript($this->preferences
);
599 $res = $contrib->executePostScript($es);
602 //send admin an email with all details
603 if ($this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
604 $mail = new GaletteMail($this->preferences
);
606 _T("Post contribution script failed")
610 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
611 $recipients[$pref_email] = $pref_email;
613 $mail->setRecipients($recipients);
615 $message = _T("The configured post contribution script has failed.");
616 $message .= "\n" . _T("You can find contribution information and script output below.");
620 $mail->setMessage($message);
621 $sent = $mail->send();
624 $txt = _T('Post contribution script has failed.');
625 $this->history
->add($txt, $message);
626 $warning_detected[] = $txt;
627 //Mails are disabled... We log (not safe, but)...
629 'Email to admin has not been sent. Here was the data: ' .
630 "\n" . print_r($res, true),
635 //Mails are disabled... We log (not safe, but)...
637 'Post contribution script has failed. Here was the data: ' .
638 "\n" . print_r($res, true),
646 //something went wrong :'(
647 $error_detected[] = _T("An error occurred while storing the contribution.");
652 if (count($error_detected) == 0) {
653 // Get member information
654 $adh = new Adherent($this->zdb
);
655 $adh->load($contrib->member
);
657 if ($this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
662 'name_adh' => custom_html_entity_decode($adh->sname
),
663 'firstname_adh' => custom_html_entity_decode($adh->surname
),
664 'lastname_adh' => custom_html_entity_decode($adh->name
),
665 'mail_adh' => custom_html_entity_decode($adh->getEmail()),
666 'login_adh' => custom_html_entity_decode($adh->login
),
667 'deadline' => custom_html_entity_decode($contrib->end_date
),
668 'contrib_info' => custom_html_entity_decode($contrib->info
),
669 'contrib_amount' => custom_html_entity_decode($contrib->amount
),
670 'contrib_type' => custom_html_entity_decode($contrib->type
->libelle
)
673 if ($new && isset($_POST['mail_confirm'])
674 && $_POST['mail_confirm'] == '1'
676 if (GaletteMail
::isValidEmail($adh->getEmail())) {
678 if (!$contrib->isCotis()) {
681 $mtxt = $texts->getTexts($text, $adh->language
);
683 $mail = new GaletteMail($this->preferences
);
684 $mail->setSubject($texts->getSubject());
685 $mail->setRecipients(
687 $adh->getEmail() => $adh->sname
692 if (strpos($mtxt->tbody
, '{LINK_MEMBERCARD}') !== false) {
693 //member card link is present in mail model, let's add it
694 $links = new Links($this->zdb
);
695 if ($hash = $links->generateNewLink(Links
::TARGET_MEMBERCARD
, $contrib->member
)) {
696 $link_card = $this->preferences
->getURL() .
697 $this->router
->pathFor('directlink', ['hash' => $hash]);
702 if (strpos($mtxt->tbody
, '{LINK_MEMBERCARD}') !== false) {
703 //contribution receipt link is present in mail model, let's add it
704 $links = new Links($this->zdb
);
705 $ltype = $contrib->type
->isExtension() ? Links
::TARGET_INVOICE
: Links
::TARGET_RECEIPT
;
706 if ($hash = $links->generateNewLink($ltype, $contrib->id
)) {
707 $link_pdf = $this->preferences
->getURL() .
708 $this->router
->pathFor('directlink', ['hash' => $hash]);
712 //set replacements, even if empty, to be sure.
713 $texts->setReplaces([
714 'link_membercard' => $link_card,
715 'link_contribpdf' => $link_pdf
718 $mail->setMessage($texts->getBody());
719 $sent = $mail->send();
724 array('/%name/', '/%email/'),
725 array($adh->sname
, $adh->getEmail()),
726 _T("Email sent to user %name (%email)")
731 array('/%name/', '/%email/'),
732 array($adh->sname
, $adh->getEmail()),
733 _T("A problem happened while sending contribution receipt to user %name (%email)")
735 $this->history
->add($txt);
736 $error_detected[] = $txt;
740 array('/%name/', '/%email/'),
741 array($adh->sname
, $adh->getEmail()),
742 _T("Trying to send an email to a member (%name) with an invalid address: %email")
744 $this->history
->add($txt);
745 $warning_detected[] = $txt;
749 // Sent email to admin if pref checked
750 if ($new && $this->preferences
->pref_bool_mailadh
) {
751 // Get email text in database
753 if (!$contrib->isCotis()) {
754 $text = 'newdonation';
756 $mtxt = $texts->getTexts($text, $this->preferences
->pref_lang
);
758 $mail = new GaletteMail($this->preferences
);
759 $mail->setSubject($texts->getSubject());
762 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
763 $recipients[$pref_email] = $pref_email;
765 $mail->setRecipients($recipients);
767 $mail->setMessage($texts->getBody());
768 $sent = $mail->send();
773 array('/%name/', '/%email/'),
774 array($adh->sname
, $adh->getEmail()),
775 _T("Email sent to admin for user %name (%email)")
780 array('/%name/', '/%email/'),
781 array($adh->sname
, $adh->getEmail()),
782 _T("A problem happened while sending to admin notification for user %name (%email) contribution")
784 $this->history
->add($txt);
785 $warning_detected[] = $txt;
790 if (count($success_detected) > 0) {
791 foreach ($success_detected as $success) {
792 $this->flash
->addMessage(
800 if (count($warning_detected) > 0) {
801 foreach ($warning_detected as $warning) {
802 $this->flash
->addMessage(
809 if (count($error_detected) == 0) {
810 if ($contrib->isTransactionPart() && $contrib->transaction
->getMissingAmount() > 0) {
812 $redirect_url = $this->router
->pathFor(
815 'type' => $post['contrib_type']
817 ) . '?' . Transaction
::PK
. '=' . $contrib->transaction
->id
.
818 '&' . Adherent
::PK
. '=' . $contrib->member
;
820 //contributions list for member
821 $redirect_url = $this->router
->pathFor(
824 'type' => 'contributions'
826 ) . '?' . Adherent
::PK
. '=' . $contrib->member
;
831 if (count($error_detected) > 0) {
832 //something went wrong.
833 //store entity in session
834 $this->session
->contribution
= $contrib;
835 $redirect_url = $this->router
->pathFor($args['action'] . 'Contribution', $args);
838 foreach ($error_detected as $error) {
839 $this->flash
->addMessage(
845 $this->session
->contribution
= null;
846 if ($redirect_url === null) {
847 $redirect_url = $this->router
->pathFor('contributions', ['type' => $args['type']]);
851 //redirect to calling action
854 ->withHeader('Location', $redirect_url);
861 * Get redirection URI
863 * @param array $args Route arguments
867 public function redirectUri(array $args = [])
869 return $this->router
->pathFor('contributions', ['type' => $args['type']]);
875 * @param array $args Route arguments
879 public function formUri(array $args = [])
881 return $this->router
->pathFor(
882 'doRemoveContribution',
888 * Get confirmation removal page title
890 * @param array $args Route arguments
894 public function confirmRemoveTitle(array $args = [])
898 switch ($args['type']) {
900 $raw_type = 'transactions';
902 case 'contributions':
903 $raw_type = 'contributions';
907 if (isset($args['ids'])) {
909 _T('Remove %1$s %2$s'),
911 ($raw_type === 'contributions') ?
_T('contributions') : _T('transactions')
915 _T('Remove %1$s #%2$s'),
916 ($raw_type === 'contributions') ?
_T('contribution') : _T('transaction'),
925 * @param array $args Route arguments
926 * @param array $post POST values
930 protected function doDelete(array $args, array $post)
933 switch ($args['type']) {
935 $raw_type = 'transactions';
937 case 'contributions':
938 $raw_type = 'contributions';
942 $class = '\\Galette\Repository\\' . ucwords($raw_type);
943 $contribs = new $class($this->zdb
, $this->login
);
944 $rm = $contribs->remove($args['ids'] ??
$args['id'], $this->history
);