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