3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette Mailing controller
10 * Copyright © 2019-2023 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 2019-2023 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 - 2019-12-06
37 namespace Galette\Controllers\Crud
;
40 use Galette\Controllers\CrudController
;
41 use Galette\Core\Galette
;
42 use Slim\Psr7\Request
;
43 use Slim\Psr7\Response
;
44 use Galette\Core\GaletteMail
;
45 use Galette\Core\Mailing
;
46 use Galette\Core\MailingHistory
;
47 use Galette\Entity\Adherent
;
48 use Galette\Filters\MailingsList
;
49 use Galette\Filters\MembersList
;
50 use Galette\Repository\Members
;
54 * Galette Mailing controller
56 * @category Controllers
57 * @name MailingsController
59 * @author Johan Cwiklinski <johan@x-tnd.be>
60 * @copyright 2019-2023 The Galette Team
61 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
62 * @link http://galette.tuxfamily.org
63 * @since Available since 0.9.4dev - 2019-12-06
66 class MailingsController
extends CrudController
73 * @param Request $request PSR Request
74 * @param Response $response PSR Response
78 public function add(Request
$request, Response
$response): Response
80 $get = $request->getQueryParams();
84 isset($get['mailing_new'])
85 ||
isset($get['reminder'])
87 if ($this->session
->mailing
!== null) {
88 // check for temporary attachments to remove
89 $m = $this->session
->mailing
;
90 $m->removeAttachments(true);
92 $this->session
->mailing
= null;
93 unset($this->session
->mailing
);
94 $this->session
->redirect_mailing
= null;
95 unset($this->session
->redirect_mailing
);
101 $this->preferences
->pref_mail_method
== Mailing
::METHOD_DISABLED
102 && !Galette
::isDemo()
105 _T("Trying to load mailing while email is disabled in preferences.")
107 $this->flash
->addMessage(
109 _T("Trying to load mailing while email is disabled in preferences.")
113 ->withHeader('Location', $this->routeparser
->urlFor('slash'));
115 if (isset($this->session
->filter_members_sendmail
)) {
116 $filters = $this->session
->filter_members_sendmail
;
118 $filters = new MembersList();
122 $this->session
->mailing
!== null
123 && !isset($get['from'])
124 && !isset($get['reset'])
126 $mailing = $this->session
->mailing
;
127 } elseif (isset($get['from']) && is_numeric($get['from'])) {
128 $mailing = new Mailing($this->preferences
, [], (int)$get['from']);
129 MailingHistory
::loadFrom($this->zdb
, (int)$get['from'], $mailing);
130 } elseif (isset($get['reminder'])) {
131 //FIXME: use a constant!
133 $filters->membership_filter
= Members
::MEMBERSHIP_LATE
;
134 $filters->filter_account
= Members
::ACTIVE_ACCOUNT
;
135 $m = new Members($filters);
136 $members = $m->getList(true);
137 $mailing = new Mailing($this->preferences
, $members);
140 count($filters->selected
) == 0
141 && !isset($get['mailing_new'])
142 && !isset($get['reminder'])
145 '[Mailings] No member selected for mailing',
149 $this->flash
->addMessage(
151 _T('No member selected for mailing!')
154 $redirect_url = ($this->session
->redirect_mailing
!== null) ?
155 $this->session
->redirect_mailing
: $this->routeparser
->urlFor('members');
159 ->withHeader('Location', $redirect_url);
162 $members = $m->getArrayList($filters->selected
);
163 $mailing = new Mailing($this->preferences
, ($members !== false) ?
$members : []);
166 if (isset($get['remove_attachment'])) {
167 $mailing->removeAttachment($get['remove_attachment']);
170 if ($mailing->current_step
!== Mailing
::STEP_SENT
) {
171 $this->session
->mailing
= $mailing;
174 /** TODO: replace that... */
175 $this->session
->labels
= $mailing->unreachables
;
177 if (!$this->login
->isSuperAdmin()) {
178 $member = new Adherent($this->zdb
, (int)$this->login
->id
, false);
179 $params['sender_current'] = [
180 'name' => $member->sname
,
181 'email' => $member->getEmail()
185 $params = array_merge(
188 'mailing' => $mailing,
189 'attachments' => $mailing->attachments
,
190 'html_editor' => true,
191 'html_editor_active' => $this->preferences
->pref_editor_enabled
199 'pages/mailing_form.html.twig',
202 'page_title' => _T("Mailing")
213 * @param Request $request PSR Request
214 * @param Response $response PSR Response
218 public function doAdd(Request
$request, Response
$response): Response
220 $post = $request->getParsedBody();
221 $error_detected = [];
222 $success_detected = [];
224 $goto = $this->routeparser
->urlFor('mailings');
225 $redirect_url = $this->session
->redirect_mailing ??
$this->routeparser
->urlFor('members');
229 isset($post['mailing_done'])
230 ||
isset($post['mailing_cancel'])
232 if ($this->session
->mailing
!== null) {
233 // check for temporary attachments to remove
234 $m = $this->session
->mailing
;
235 $m->removeAttachments(true);
237 $this->session
->mailing
= null;
238 unset($this->session
->mailing
);
239 $this->session
->redirect_mailing
= null;
240 unset($this->session
->redirect_mailing
);
241 $this->session
->filter_members_sendmail
= null;
242 unset($this->session
->filter_members_sendmail
);
246 ->withHeader('Location', $redirect_url);
250 $this->preferences
->pref_mail_method
== Mailing
::METHOD_DISABLED
251 && !Galette
::isDemo()
254 _T("Trying to load mailing while email is disabled in preferences.")
256 $error_detected[] = _T("Trying to load mailing while email is disabled in preferences.");
257 $goto = $this->routeparser
->urlFor('slash');
259 $filters = $this->session
->filter_members_sendmail ??
new MembersList();
262 $this->session
->mailing
!== null
264 $mailing = $this->session
->mailing
;
266 if (count($filters->selected
) == 0) {
268 '[Mailings] No member selected for mailing',
272 $this->flash
->addMessage(
274 _T('No member selected for mailing!')
279 ->withHeader('Location', $redirect_url);
282 $members = $m->getArrayList($filters->selected
);
283 $mailing = new Mailing($this->preferences
, ($members !== false) ?
$members : []);
287 isset($post['mailing_go'])
288 ||
isset($post['mailing_reset'])
289 ||
isset($post['mailing_confirm'])
290 ||
isset($post['mailing_save'])
292 if (trim($post['mailing_objet']) == '') {
293 $error_detected[] = _T("Please type an object for the message.");
295 $mailing->subject
= $post['mailing_objet'];
298 if (trim($post['mailing_corps']) == '') {
299 $error_detected[] = _T("Please enter a message.");
301 $mailing->message
= $post['mailing_corps'];
304 switch ($post['sender'] ??
false) {
305 case GaletteMail
::SENDER_CURRENT
:
306 $member = new Adherent($this->zdb
, (int)$this->login
->id
, false);
312 case GaletteMail
::SENDER_OTHER
:
314 $post['sender_name'],
315 $post['sender_address']
318 case GaletteMail
::SENDER_PREFS
:
320 //nothing to do; this is the default :)
324 $mailing->html
= isset($post['mailing_html']);
327 if (isset($_FILES['attachment'])) {
328 $cnt_files = count($_FILES['attachment']['name']);
329 for ($i = 0; $i < $cnt_files; $i++
) {
330 if ($_FILES['attachment']['error'][$i] === UPLOAD_ERR_OK
) {
331 if ($_FILES['attachment']['tmp_name'][$i] != '') {
332 if (is_uploaded_file($_FILES['attachment']['tmp_name'][$i])) {
334 foreach (array_keys($_FILES['attachment']) as $key) {
335 $da_file[$key] = $_FILES['attachment'][$key][$i];
337 $res = $mailing->store($da_file);
339 //what to do if one of attachments fail? should other be removed?
340 $error_detected[] = $mailing->getAttachmentErrorMessage($res);
344 } elseif ($_FILES['attachment']['error'][$i] !== UPLOAD_ERR_NO_FILE
) {
346 $this->logo
->getPhpErrorMessage($_FILES['attachment']['error'][$i]),
349 $error_detected[] = $this->logo
->getPhpErrorMessage(
350 $_FILES['attachment']['error'][$i]
357 count($error_detected) == 0
358 && !isset($post['mailing_reset'])
359 && !isset($post['mailing_save'])
361 $mailing->current_step
= Mailing
::STEP_PREVIEW
;
363 $mailing->current_step
= Mailing
::STEP_START
;
365 //until mail is sent (above), we redirect to mailing page
366 $goto = $this->routeparser
->urlFor('mailing');
369 if (isset($post['mailing_confirm']) && count($error_detected) == 0) {
370 $mailing->current_step
= Mailing
::STEP_SEND
;
371 //ok... let's go for fun
372 $sent = $mailing->send();
373 if ($sent == Mailing
::MAIL_ERROR
) {
374 $mailing->current_step
= Mailing
::STEP_START
;
376 '[Mailings] Message was not sent. Errors: ' .
377 print_r($mailing->errors
, true),
380 foreach ($mailing->errors
as $e) {
381 $error_detected[] = $e;
384 $mlh = new MailingHistory($this->zdb
, $this->login
, $this->preferences
, null, $mailing);
385 $mlh->storeMailing(true);
387 '[Mailings] Message has been sent.',
390 $mailing->current_step
= Mailing
::STEP_SENT
;
392 $this->session
->filter_members_sendmail
= null;
393 $this->session
->mailing
= null;
394 $this->session
->redirect_mailing
= null;
395 $success_detected[] = _T("Mailing has been successfully sent!");
396 $goto = $redirect_url;
400 if ($mailing->current_step
!== Mailing
::STEP_SENT
) {
401 $this->session
->mailing
= $mailing;
404 /** TODO: replace that... */
405 $this->session
->labels
= $mailing->unreachables
;
408 !isset($post['html_editor_active'])
409 ||
trim($post['html_editor_active']) == ''
411 $post['html_editor_active'] = $this->preferences
->pref_editor_enabled
;
414 if (isset($post['mailing_save'])) {
415 //user requested to save the mailing
416 $histo = new MailingHistory($this->zdb
, $this->login
, $this->preferences
, null, $mailing);
417 if ($histo->storeMailing() !== false) {
418 $success_detected[] = _T("Mailing has been successfully saved.");
419 $this->session
->mailing
= null;
420 $this->session
->redirect_mailing
= null;
421 $goto = $this->routeparser
->urlFor('mailings');
426 //flash messages if any
427 if (count($error_detected) > 0) {
428 foreach ($error_detected as $error) {
429 $this->flash
->addMessage('error_detected', $error);
432 if (count($success_detected) > 0) {
433 foreach ($success_detected as $success) {
434 $this->flash
->addMessage('success_detected', $success);
440 ->withHeader('Location', $goto);
447 * Mailings history page
449 * @param Request $request PSR Request
450 * @param Response $response PSR Response
451 * @param string $option One of 'page' or 'order'
452 * @param string|integer $value Value of the option
456 public function list(Request
$request, Response
$response, $option = null, $value = null): Response
458 if (isset($this->session
->filter_mailings
)) {
459 $filters = $this->session
->filter_mailings
;
461 $filters = new MailingsList();
464 if (isset($request->getQueryParams()['nbshow'])) {
465 $filters->show
= $request->getQueryParams()['nbshow'];
468 $mailhist = new MailingHistory($this->zdb
, $this->login
, $this->preferences
, $filters);
470 if ($option !== null) {
473 $filters->current_page
= (int)$value;
476 $filters->orderby
= $value;
480 //reinitialize object after flush
481 $filters = new MailingsList();
482 $mailhist = new MailingHistory($this->zdb
, $this->login
, $this->preferences
, $filters);
487 $this->session
->filter_mailings
= $filters;
489 //assign pagination variables to the template and add pagination links
490 $mailhist->filters
->setViewPagination($this->routeparser
, $this->view
);
491 $history_list = $mailhist->getHistory();
492 //assign pagination variables to the template and add pagination links
493 $mailhist->filters
->setViewPagination($this->routeparser
, $this->view
);
498 'pages/mailings_list.html.twig',
500 'page_title' => _T("Mailings"),
501 'logs' => $history_list,
502 'history' => $mailhist,
503 'filters' => $filters
512 * @param Request $request PSR Request
513 * @param Response $response PSR Response
517 public function filter(Request
$request, Response
$response): Response
519 $post = $request->getParsedBody();
521 if ($this->session
->filter_mailings
!== null) {
522 $filters = $this->session
->filter_mailings
;
524 $filters = new MailingsList();
527 if (isset($post['clear_filter'])) {
531 (isset($post['nbshow']) && is_numeric($post['nbshow']))
533 $filters->show
= $post['nbshow'];
536 if (isset($post['end_date_filter']) ||
isset($post['start_date_filter'])) {
537 if (isset($post['start_date_filter'])) {
538 $filters->start_date_filter
= $post['start_date_filter'];
540 if (isset($post['end_date_filter'])) {
541 $filters->end_date_filter
= $post['end_date_filter'];
545 if (isset($post['sender_filter'])) {
546 $filters->sender_filter
= $post['sender_filter'];
549 if (isset($post['sent_filter'])) {
550 $filters->sent_filter
= $post['sent_filter'];
554 if (isset($post['subject_filter'])) {
555 $filters->subject_filter
= $post['subject_filter'];
559 $this->session
->filter_mailings
= $filters;
563 ->withHeader('Location', $this->routeparser
->urlFor('mailings'));
569 * @param Request $request PSR Request
570 * @param Response $response PSR Response
571 * @param integer $id Record id
575 public function edit(Request
$request, Response
$response, int $id): Response
577 //no edit page, just to satisfy inheritance
584 * @param Request $request PSR Request
585 * @param Response $response PSR Response
586 * @param integer $id Record id
590 public function doEdit(Request
$request, Response
$response, int $id): Response
592 //no edit page, just to satisfy inheritance
600 * Get redirection URI
602 * @param array $args Route arguments
606 public function redirectUri(array $args)
608 return $this->routeparser
->urlFor('mailings');
614 * @param array $args Route arguments
618 public function formUri(array $args)
620 return $this->routeparser
->urlFor(
622 ['id' => $args['id'] ??
null]
627 * Get confirmation removal page title
629 * @param array $args Route arguments
633 public function confirmRemoveTitle(array $args)
636 _T('Remove mailing #%1$s'),
644 * @param array $args Route arguments
645 * @param array $post POST values
649 protected function doDelete(array $args, array $post)
651 $mailhist = new MailingHistory($this->zdb
, $this->login
, $this->preferences
);
652 return $mailhist->removeEntries($args['id'], $this->history
);
660 * @param Request $request PSR Request
661 * @param Response $response PSR Response
662 * @param integer $id Mailing id
666 public function preview(Request
$request, Response
$response, int $id = null): Response
668 $post = $request->getParsedBody();
669 // check for ajax mode
672 ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')
673 ||
isset($post['ajax'])
674 && $post['ajax'] == 'true'
681 $mailing = new Mailing($this->preferences
);
682 MailingHistory
::loadFrom($this->zdb
, $id, $mailing, false);
683 $attachments = $mailing->attachments
;
685 $mailing = $this->session
->mailing
;
687 switch ($post['sender']) {
688 case GaletteMail
::SENDER_CURRENT
:
689 $member = new Adherent($this->zdb
, (int)$this->login
->id
, false);
695 case GaletteMail
::SENDER_OTHER
:
697 $post['sender_name'],
698 $post['sender_address']
701 case GaletteMail
::SENDER_PREFS
:
703 //nothing to do; this is the default :)
707 $mailing->subject
= $post['subject'];
708 $mailing->message
= $post['body'];
709 $mailing->html
= ($post['html'] === 'true');
710 $attachments = $mailing->attachments
;
716 'modals/mailing_preview.html.twig',
718 'page_title' => _T("Mailing preview"),
720 'mode' => ($ajax ?
'ajax' : ''),
721 'mailing' => $mailing,
722 'recipients' => $mailing->recipients
,
723 'sender' => $mailing->getSenderName() . ' <' .
724 $mailing->getSenderAddress() . '>',
725 'attachments' => $attachments
733 * Preview attachement action
735 * @param Request $request PSR Request
736 * @param Response $response PSR Response
737 * @param integer $id Mailiung id
738 * @param integer $pos Attachement position in list
742 public function previewAttachment(Request
$request, Response
$response, int $id, $pos): Response
744 $mailing = new Mailing($this->preferences
);
745 MailingHistory
::loadFrom($this->zdb
, $id, $mailing, false);
746 $attachments = $mailing->attachments
;
747 $attachment = $attachments[$pos];
748 $filepath = $attachment->getDestDir() . $attachment->getFileName();
750 $response = $response->withHeader('Content-type', $attachment->getMimeType($filepath));
752 $body = $response->getBody();
753 $body->write(file_get_contents($filepath));
758 * Set recipients action
760 * @param Request $request PSR Request
761 * @param Response $response PSR Response
765 public function setRecipients(Request
$request, Response
$response): Response
767 $post = $request->getParsedBody();
768 $mailing = $this->session
->mailing
;
773 if (isset($post['recipients'])) {
774 $members = $m->getArrayList(
785 $mailing->setRecipients($members);
787 $this->session
->mailing
= $mailing;
792 'elements/mailing_recipients.html.twig',
794 'mailing' => $mailing