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