]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/Crud/MailingsController.php
Use specific filters
[galette.git] / galette / lib / Galette / Controllers / Crud / MailingsController.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Galette Mailing controller
7 *
8 * PHP version 5
9 *
10 * Copyright © 2019-2023 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
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.
18 *
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.
23 *
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/>.
26 *
27 * @category Controllers
28 * @package Galette
29 *
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
35 */
36
37 namespace Galette\Controllers\Crud;
38
39 use Throwable;
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;
51 use Analog\Analog;
52
53 /**
54 * Galette Mailing controller
55 *
56 * @category Controllers
57 * @name MailingsController
58 * @package Galette
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
64 */
65
66 class MailingsController extends CrudController
67 {
68 // CRUD - Create
69
70 /**
71 * Add page
72 *
73 * @param Request $request PSR Request
74 * @param Response $response PSR Response
75 *
76 * @return Response
77 */
78 public function add(Request $request, Response $response): Response
79 {
80 $get = $request->getQueryParams();
81
82 //We're done :-)
83 if (
84 isset($get['mailing_new'])
85 || isset($get['reminder'])
86 ) {
87 if ($this->session->mailing !== null) {
88 // check for temporary attachments to remove
89 $m = $this->session->mailing;
90 $m->removeAttachments(true);
91 }
92 $this->session->mailing = null;
93 unset($this->session->mailing);
94 $this->session->redirect_mailing = null;
95 unset($this->session->redirect_mailing);
96 }
97
98 $params = array();
99
100 if (
101 $this->preferences->pref_mail_method == Mailing::METHOD_DISABLED
102 && !Galette::isDemo()
103 ) {
104 $this->history->add(
105 _T("Trying to load mailing while email is disabled in preferences.")
106 );
107 $this->flash->addMessage(
108 'error_detected',
109 _T("Trying to load mailing while email is disabled in preferences.")
110 );
111 return $response
112 ->withStatus(301)
113 ->withHeader('Location', $this->routeparser->urlFor('slash'));
114 } else {
115 if (isset($this->session->filter_members_sendmail)) {
116 $filters = $this->session->filter_members_sendmail;
117 } else {
118 $filters = new MembersList();
119 }
120
121 if (
122 $this->session->mailing !== null
123 && !isset($get['from'])
124 && !isset($get['reset'])
125 ) {
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!
132 $filters->reinit();
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);
138 } else {
139 if (
140 count($filters->selected) == 0
141 && !isset($get['mailing_new'])
142 && !isset($get['reminder'])
143 ) {
144 Analog::log(
145 '[Mailings] No member selected for mailing',
146 Analog::WARNING
147 );
148
149 $this->flash->addMessage(
150 'error_detected',
151 _T('No member selected for mailing!')
152 );
153
154 $redirect_url = ($this->session->redirect_mailing !== null) ?
155 $this->session->redirect_mailing : $this->routeparser->urlFor('members');
156
157 return $response
158 ->withStatus(301)
159 ->withHeader('Location', $redirect_url);
160 }
161 $m = new Members();
162 $members = $m->getArrayList($filters->selected);
163 $mailing = new Mailing($this->preferences, ($members !== false) ? $members : []);
164 }
165
166 if (isset($get['remove_attachment'])) {
167 $mailing->removeAttachment($get['remove_attachment']);
168 }
169
170 if ($mailing->current_step !== Mailing::STEP_SENT) {
171 $this->session->mailing = $mailing;
172 }
173
174 /** TODO: replace that... */
175 $this->session->labels = $mailing->unreachables;
176
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()
182 ];
183 }
184
185 $params = array_merge(
186 $params,
187 array(
188 'mailing' => $mailing,
189 'attachments' => $mailing->attachments,
190 'html_editor' => true,
191 'html_editor_active' => $this->preferences->pref_editor_enabled
192 )
193 );
194 }
195
196 // display page
197 $this->view->render(
198 $response,
199 'pages/mailing_form.html.twig',
200 array_merge(
201 array(
202 'page_title' => _T("Mailing")
203 ),
204 $params
205 )
206 );
207 return $response;
208 }
209
210 /**
211 * Add action
212 *
213 * @param Request $request PSR Request
214 * @param Response $response PSR Response
215 *
216 * @return Response
217 */
218 public function doAdd(Request $request, Response $response): Response
219 {
220 $post = $request->getParsedBody();
221 $error_detected = [];
222 $success_detected = [];
223
224 $goto = $this->routeparser->urlFor('mailings');
225 $redirect_url = $this->session->redirect_mailing ?? $this->routeparser->urlFor('members');
226
227 //We're done :-)
228 if (
229 isset($post['mailing_done'])
230 || isset($post['mailing_cancel'])
231 ) {
232 if ($this->session->mailing !== null) {
233 // check for temporary attachments to remove
234 $m = $this->session->mailing;
235 $m->removeAttachments(true);
236 }
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);
243
244 return $response
245 ->withStatus(301)
246 ->withHeader('Location', $redirect_url);
247 }
248
249 if (
250 $this->preferences->pref_mail_method == Mailing::METHOD_DISABLED
251 && !Galette::isDemo()
252 ) {
253 $this->history->add(
254 _T("Trying to load mailing while email is disabled in preferences.")
255 );
256 $error_detected[] = _T("Trying to load mailing while email is disabled in preferences.");
257 $goto = $this->routeparser->urlFor('slash');
258 } else {
259 $filters = $this->session->filter_members_sendmail ?? new MembersList();
260
261 if (
262 $this->session->mailing !== null
263 ) {
264 $mailing = $this->session->mailing;
265 } else {
266 if (count($filters->selected) == 0) {
267 Analog::log(
268 '[Mailings] No member selected for mailing',
269 Analog::WARNING
270 );
271
272 $this->flash->addMessage(
273 'error_detected',
274 _T('No member selected for mailing!')
275 );
276
277 return $response
278 ->withStatus(301)
279 ->withHeader('Location', $redirect_url);
280 }
281 $m = new Members();
282 $members = $m->getArrayList($filters->selected);
283 $mailing = new Mailing($this->preferences, ($members !== false) ? $members : []);
284 }
285
286 if (
287 isset($post['mailing_go'])
288 || isset($post['mailing_reset'])
289 || isset($post['mailing_confirm'])
290 || isset($post['mailing_save'])
291 ) {
292 if (trim($post['mailing_objet']) == '') {
293 $error_detected[] = _T("Please type an object for the message.");
294 } else {
295 $mailing->subject = $post['mailing_objet'];
296 }
297
298 if (trim($post['mailing_corps']) == '') {
299 $error_detected[] = _T("Please enter a message.");
300 } else {
301 $mailing->message = $post['mailing_corps'];
302 }
303
304 switch ($post['sender'] ?? false) {
305 case GaletteMail::SENDER_CURRENT:
306 $member = new Adherent($this->zdb, (int)$this->login->id, false);
307 $mailing->setSender(
308 $member->sname,
309 $member->getEmail()
310 );
311 break;
312 case GaletteMail::SENDER_OTHER:
313 $mailing->setSender(
314 $post['sender_name'],
315 $post['sender_address']
316 );
317 break;
318 case GaletteMail::SENDER_PREFS:
319 default:
320 //nothing to do; this is the default :)
321 break;
322 }
323
324 $mailing->html = isset($post['mailing_html']);
325
326 //handle attachments
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])) {
333 $da_file = array();
334 foreach (array_keys($_FILES['attachment']) as $key) {
335 $da_file[$key] = $_FILES['attachment'][$key][$i];
336 }
337 $res = $mailing->store($da_file);
338 if ($res < 0) {
339 //what to do if one of attachments fail? should other be removed?
340 $error_detected[] = $mailing->getAttachmentErrorMessage($res);
341 }
342 }
343 }
344 } elseif ($_FILES['attachment']['error'][$i] !== UPLOAD_ERR_NO_FILE) {
345 Analog::log(
346 $this->logo->getPhpErrorMessage($_FILES['attachment']['error'][$i]),
347 Analog::WARNING
348 );
349 $error_detected[] = $this->logo->getPhpErrorMessage(
350 $_FILES['attachment']['error'][$i]
351 );
352 }
353 }
354 }
355
356 if (
357 count($error_detected) == 0
358 && !isset($post['mailing_reset'])
359 && !isset($post['mailing_save'])
360 ) {
361 $mailing->current_step = Mailing::STEP_PREVIEW;
362 } else {
363 $mailing->current_step = Mailing::STEP_START;
364 }
365 //until mail is sent (above), we redirect to mailing page
366 $goto = $this->routeparser->urlFor('mailing');
367 }
368
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;
375 Analog::log(
376 '[Mailings] Message was not sent. Errors: ' .
377 print_r($mailing->errors, true),
378 Analog::ERROR
379 );
380 foreach ($mailing->errors as $e) {
381 $error_detected[] = $e;
382 }
383 } else {
384 $mlh = new MailingHistory($this->zdb, $this->login, $this->preferences, null, $mailing);
385 $mlh->storeMailing(true);
386 Analog::log(
387 '[Mailings] Message has been sent.',
388 Analog::INFO
389 );
390 $mailing->current_step = Mailing::STEP_SENT;
391 //cleanup
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;
397 }
398 }
399
400 if ($mailing->current_step !== Mailing::STEP_SENT) {
401 $this->session->mailing = $mailing;
402 }
403
404 /** TODO: replace that... */
405 $this->session->labels = $mailing->unreachables;
406
407 if (
408 !isset($post['html_editor_active'])
409 || trim($post['html_editor_active']) == ''
410 ) {
411 $post['html_editor_active'] = $this->preferences->pref_editor_enabled;
412 }
413
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');
422 }
423 }
424 }
425
426 //flash messages if any
427 if (count($error_detected) > 0) {
428 foreach ($error_detected as $error) {
429 $this->flash->addMessage('error_detected', $error);
430 }
431 }
432 if (count($success_detected) > 0) {
433 foreach ($success_detected as $success) {
434 $this->flash->addMessage('success_detected', $success);
435 }
436 }
437
438 return $response
439 ->withStatus(301)
440 ->withHeader('Location', $goto);
441 }
442
443 // /CRUD - Create
444 // CRUD - Read
445
446 /**
447 * Mailings history page
448 *
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
453 *
454 * @return Response
455 */
456 public function list(Request $request, Response $response, $option = null, $value = null): Response
457 {
458 if (isset($this->session->filter_mailings)) {
459 $filters = $this->session->filter_mailings;
460 } else {
461 $filters = new MailingsList();
462 }
463
464 if (isset($request->getQueryParams()['nbshow'])) {
465 $filters->show = $request->getQueryParams()['nbshow'];
466 }
467
468 $mailhist = new MailingHistory($this->zdb, $this->login, $this->preferences, $filters);
469
470 if ($option !== null) {
471 switch ($option) {
472 case 'page':
473 $filters->current_page = (int)$value;
474 break;
475 case 'order':
476 $filters->orderby = $value;
477 break;
478 case 'reset':
479 $mailhist->clean();
480 //reinitialize object after flush
481 $filters = new MailingsList();
482 $mailhist = new MailingHistory($this->zdb, $this->login, $this->preferences, $filters);
483 break;
484 }
485 }
486
487 $this->session->filter_mailings = $filters;
488
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);
494
495 // display page
496 $this->view->render(
497 $response,
498 'pages/mailings_list.html.twig',
499 array(
500 'page_title' => _T("Mailings"),
501 'logs' => $history_list,
502 'history' => $mailhist,
503 'filters' => $filters
504 )
505 );
506 return $response;
507 }
508
509 /**
510 * Mailings filtering
511 *
512 * @param Request $request PSR Request
513 * @param Response $response PSR Response
514 *
515 * @return Response
516 */
517 public function filter(Request $request, Response $response): Response
518 {
519 $post = $request->getParsedBody();
520
521 if ($this->session->filter_mailings !== null) {
522 $filters = $this->session->filter_mailings;
523 } else {
524 $filters = new MailingsList();
525 }
526
527 if (isset($post['clear_filter'])) {
528 $filters->reinit();
529 } else {
530 if (
531 (isset($post['nbshow']) && is_numeric($post['nbshow']))
532 ) {
533 $filters->show = $post['nbshow'];
534 }
535
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'];
539 }
540 if (isset($post['end_date_filter'])) {
541 $filters->end_date_filter = $post['end_date_filter'];
542 }
543 }
544
545 if (isset($post['sender_filter'])) {
546 $filters->sender_filter = $post['sender_filter'];
547 }
548
549 if (isset($post['sent_filter'])) {
550 $filters->sent_filter = $post['sent_filter'];
551 }
552
553
554 if (isset($post['subject_filter'])) {
555 $filters->subject_filter = $post['subject_filter'];
556 }
557 }
558
559 $this->session->filter_mailings = $filters;
560
561 return $response
562 ->withStatus(301)
563 ->withHeader('Location', $this->routeparser->urlFor('mailings'));
564 }
565
566 /**
567 * Edit page
568 *
569 * @param Request $request PSR Request
570 * @param Response $response PSR Response
571 * @param integer $id Record id
572 *
573 * @return Response
574 */
575 public function edit(Request $request, Response $response, int $id): Response
576 {
577 //no edit page, just to satisfy inheritance
578 return $response;
579 }
580
581 /**
582 * Edit action
583 *
584 * @param Request $request PSR Request
585 * @param Response $response PSR Response
586 * @param integer $id Record id
587 *
588 * @return Response
589 */
590 public function doEdit(Request $request, Response $response, int $id): Response
591 {
592 //no edit page, just to satisfy inheritance
593 return $response;
594 }
595
596 // /CRUD - Update
597 // CRUD - Delete
598
599 /**
600 * Get redirection URI
601 *
602 * @param array $args Route arguments
603 *
604 * @return string
605 */
606 public function redirectUri(array $args)
607 {
608 return $this->routeparser->urlFor('mailings');
609 }
610
611 /**
612 * Get form URI
613 *
614 * @param array $args Route arguments
615 *
616 * @return string
617 */
618 public function formUri(array $args)
619 {
620 return $this->routeparser->urlFor(
621 'doRemoveMailing',
622 ['id' => $args['id'] ?? null]
623 );
624 }
625
626 /**
627 * Get confirmation removal page title
628 *
629 * @param array $args Route arguments
630 *
631 * @return string
632 */
633 public function confirmRemoveTitle(array $args)
634 {
635 return sprintf(
636 _T('Remove mailing #%1$s'),
637 $args['id'] ?? ''
638 );
639 }
640
641 /**
642 * Remove object
643 *
644 * @param array $args Route arguments
645 * @param array $post POST values
646 *
647 * @return boolean
648 */
649 protected function doDelete(array $args, array $post)
650 {
651 $mailhist = new MailingHistory($this->zdb, $this->login, $this->preferences);
652 return $mailhist->removeEntries($args['id'], $this->history);
653 }
654 // /CRUD - Delete
655 // /CRUD
656
657 /**
658 * Preview action
659 *
660 * @param Request $request PSR Request
661 * @param Response $response PSR Response
662 * @param integer $id Mailing id
663 *
664 * @return Response
665 */
666 public function preview(Request $request, Response $response, int $id = null): Response
667 {
668 $post = $request->getParsedBody();
669 // check for ajax mode
670 $ajax = false;
671 if (
672 ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')
673 || isset($post['ajax'])
674 && $post['ajax'] == 'true'
675 ) {
676 $ajax = true;
677 }
678
679 $mailing = null;
680 if ($id !== null) {
681 $mailing = new Mailing($this->preferences);
682 MailingHistory::loadFrom($this->zdb, $id, $mailing, false);
683 $attachments = $mailing->attachments;
684 } else {
685 $mailing = $this->session->mailing;
686
687 switch ($post['sender']) {
688 case GaletteMail::SENDER_CURRENT:
689 $member = new Adherent($this->zdb, (int)$this->login->id, false);
690 $mailing->setSender(
691 $member->sname,
692 $member->getEmail()
693 );
694 break;
695 case GaletteMail::SENDER_OTHER:
696 $mailing->setSender(
697 $post['sender_name'],
698 $post['sender_address']
699 );
700 break;
701 case GaletteMail::SENDER_PREFS:
702 default:
703 //nothing to do; this is the default :)
704 break;
705 }
706
707 $mailing->subject = $post['subject'];
708 $mailing->message = $post['body'];
709 $mailing->html = ($post['html'] === 'true');
710 $attachments = $mailing->attachments;
711 }
712
713 // display page
714 $this->view->render(
715 $response,
716 'modals/mailing_preview.html.twig',
717 [
718 'page_title' => _T("Mailing preview"),
719 'mailing_id' => $id,
720 'mode' => ($ajax ? 'ajax' : ''),
721 'mailing' => $mailing,
722 'recipients' => $mailing->recipients,
723 'sender' => $mailing->getSenderName() . ' <' .
724 $mailing->getSenderAddress() . '>',
725 'attachments' => $attachments
726
727 ]
728 );
729 return $response;
730 }
731
732 /**
733 * Preview attachement action
734 *
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
739 *
740 * @return Response
741 */
742 public function previewAttachment(Request $request, Response $response, int $id, $pos): Response
743 {
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();
749
750 $response = $response->withHeader('Content-type', $attachment->getMimeType($filepath));
751
752 $body = $response->getBody();
753 $body->write(file_get_contents($filepath));
754 return $response;
755 }
756
757 /**
758 * Set recipients action
759 *
760 * @param Request $request PSR Request
761 * @param Response $response PSR Response
762 *
763 * @return Response
764 */
765 public function setRecipients(Request $request, Response $response): Response
766 {
767 $post = $request->getParsedBody();
768 $mailing = $this->session->mailing;
769
770 $m = new Members();
771 $members = [];
772
773 if (isset($post['recipients'])) {
774 $members = $m->getArrayList(
775 $post['recipients'],
776 null,
777 false,
778 true,
779 null,
780 false,
781 false,
782 true
783 );
784 }
785 $mailing->setRecipients($members);
786
787 $this->session->mailing = $mailing;
788
789 // display page
790 $this->view->render(
791 $response,
792 'elements/mailing_recipients.html.twig',
793 [
794 'mailing' => $mailing
795
796 ]
797 );
798 return $response;
799 }
800 }