]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/PdfController.php
Fix property access on direct download links
[galette.git] / galette / lib / Galette / Controllers / PdfController.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Galette PDF controller
7 *
8 * PHP version 5
9 *
10 * Copyright © 2019 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 Entity
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2019 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-05
35 */
36
37 namespace Galette\Controllers;
38
39 use Throwable;
40 use Slim\Http\Request;
41 use Slim\Http\Response;
42 use Analog\Analog;
43 use Galette\Core\Links;
44 use Galette\Core\Login;
45 use Galette\Entity\Adherent;
46 use Galette\Entity\Contribution;
47 use Galette\Entity\PdfModel;
48 use Galette\Filters\MembersList;
49 use Galette\IO\Pdf;
50 use Galette\IO\PdfAttendanceSheet;
51 use Galette\IO\PdfContribution;
52 use Galette\IO\PdfGroups;
53 use Galette\IO\PdfMembersCards;
54 use Galette\IO\PdfMembersLabels;
55 use Galette\Repository\Members;
56 use Galette\Repository\Groups;
57 use Galette\Repository\PdfModels;
58
59 /**
60 * Galette PDF controller
61 *
62 * @category Controllers
63 * @name GaletteController
64 * @package Galette
65 * @author Johan Cwiklinski <johan@x-tnd.be>
66 * @copyright 2019 The Galette Team
67 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
68 * @link http://galette.tuxfamily.org
69 * @since Available since 0.9.4dev - 2019-12-05
70 */
71
72 class PdfController extends AbstractController
73 {
74 /**
75 * Send response
76 *
77 * @param Response $response PSR Response
78 * @param Pdf $pdf PDF to output
79 *
80 * @return Response
81 */
82 protected function sendResponse(Response $response, Pdf $pdf): Response
83 {
84 return $response
85 ->withHeader('Content-type', 'application/pdf')
86 ->withHeader('Content-Disposition', 'attachment;filename="' . $pdf->getFileName() . '"')
87 ->write($pdf->download());
88 }
89
90 /**
91 * Members PDF card
92 *
93 * @param Request $request PSR Request
94 * @param Response $response PSR Response
95 * @param integer $id_adh Member id
96 *
97 * @return Response
98 */
99 public function membersCards(Request $request, Response $response, int $id_adh = null): Response
100 {
101 if ($this->session->filter_members) {
102 $filters = $this->session->filter_members;
103 } else {
104 $filters = new MembersList();
105 }
106
107 if ($id_adh !== null && $id_adh > 0) {
108 $deps = ['dynamics' => true];
109 if ($this->login->id === $id_adh) {
110 $deps['dues'] = true;
111 }
112 $adh = new Adherent(
113 $this->zdb,
114 $id_adh,
115 $deps
116 );
117 if (!$adh->canEdit($this->login)) {
118 $this->flash->addMessage(
119 'error_detected',
120 _T("You do not have permission for requested URL.")
121 );
122
123 return $response
124 ->withStatus(403)
125 ->withHeader(
126 'Location',
127 $this->router->pathFor('me')
128 );
129 }
130
131 //check if member is up to date
132 if ($this->login->id == $id_adh) {
133 if (!$adh->isUp2Date()) {
134 Analog::log(
135 'Member ' . $id_adh . ' is not up to date; cannot get his PDF member card',
136 Analog::WARNING
137 );
138 return $response
139 ->withStatus(301)
140 ->withHeader('Location', $this->router->pathFor('slash'));
141 }
142 }
143
144 // If we are called from a member's card, get unique id value
145 $unique = $id_adh;
146 } else {
147 if (count($filters->selected) == 0) {
148 Analog::log(
149 'No member selected to generate members cards',
150 Analog::INFO
151 );
152 $this->flash->addMessage(
153 'error_detected',
154 _T("No member was selected, please check at least one name.")
155 );
156
157 return $response
158 ->withStatus(301)
159 ->withHeader('Location', $this->router->pathFor('members'));
160 }
161 }
162
163 // Fill array $selected with selected ids
164 $selected = array();
165 if (isset($unique) && $unique) {
166 $selected[] = $unique;
167 } else {
168 $selected = $filters->selected;
169 }
170
171 $m = new Members();
172 $members = $m->getArrayList(
173 $selected,
174 array('nom_adh', 'prenom_adh'),
175 true
176 );
177
178 if (!is_array($members) || count($members) < 1) {
179 Analog::log(
180 'An error has occurred, unable to get members list.',
181 Analog::ERROR
182 );
183
184 $this->flash->addMessage(
185 'error_detected',
186 _T("Unable to get members list.")
187 );
188
189 return $response
190 ->withStatus(301)
191 ->withHeader('Location', $this->router->pathFor('members'));
192 }
193
194 $pdf = new PdfMembersCards($this->preferences);
195 $pdf->drawCards($members);
196
197 return $this->sendResponse($response, $pdf);
198 }
199
200 /**
201 * Members PDF label
202 *
203 * @param Request $request PSR Request
204 * @param Response $response PSR Response
205 *
206 * @return Response
207 */
208 public function membersLabels(Request $request, Response $response): Response
209 {
210 $post = $request->getParsedBody();
211 $get = $request->getQueryParams();
212
213 $session_var = $post['session_var'] ?? $get['session_var'] ?? 'filter_members';
214
215 if (isset($this->session->$session_var)) {
216 $filters = $this->session->$session_var;
217 } else {
218 $filters = new MembersList();
219 }
220
221 $members = null;
222 if (
223 isset($get['from'])
224 && $get['from'] === 'mailing'
225 ) {
226 //if we're from mailing, we have to retrieve
227 //its unreachables members for labels
228 $mailing = $this->session->mailing;
229 $members = $mailing->unreachables;
230 } else {
231 if (count($filters->selected) == 0) {
232 Analog::log('No member selected to generate labels', Analog::INFO);
233 $this->flash->addMessage(
234 'error_detected',
235 _T("No member was selected, please check at least one name.")
236 );
237
238 return $response
239 ->withStatus(301)
240 ->withHeader('Location', $this->router->pathFor('members'));
241 }
242
243 $m = new Members();
244 $members = $m->getArrayList(
245 $filters->selected,
246 array('nom_adh', 'prenom_adh')
247 );
248 }
249
250 if (!is_array($members) || count($members) < 1) {
251 Analog::log(
252 'An error has occurred, unable to get members list.',
253 Analog::ERROR
254 );
255
256 $this->flash->addMessage(
257 'error_detected',
258 _T("Unable to get members list.")
259 );
260
261 return $response
262 ->withStatus(301)
263 ->withHeader('Location', $this->router->pathFor('members'));
264 }
265
266 $pdf = new PdfMembersLabels($this->preferences);
267 $pdf->drawLabels($members);
268
269 return $this->sendResponse($response, $pdf);
270 }
271
272 /**
273 * PDF adhesion form
274 *
275 * @param Request $request PSR Request
276 * @param Response $response PSR Response
277 * @param integer $id_adh Member id
278 *
279 * @return Response
280 */
281 public function adhesionForm(Request $request, Response $response, int $id_adh = null): Response
282 {
283 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true]);
284
285 if ($id_adh !== null && !$adh->canEdit($this->login)) {
286 $this->flash->addMessage(
287 'error_detected',
288 _T("You do not have permission for requested URL.")
289 );
290
291 return $response
292 ->withStatus(403)
293 ->withHeader(
294 'Location',
295 $this->router->pathFor('me')
296 );
297 }
298
299 $form = $this->preferences->pref_adhesion_form;
300 $pdf = new $form($adh, $this->zdb, $this->preferences);
301
302 return $this->sendResponse($response, $pdf);
303 }
304
305 /**
306 * PDF attendance sheet configuration page
307 *
308 * @param Request $request PSR Request
309 * @param Response $response PSR Response
310 *
311 * @return Response
312 */
313 public function attendanceSheetConfig(Request $request, Response $response): Response
314 {
315 $post = $request->getParsedBody();
316
317 if ($this->session->filter_members !== null) {
318 $filters = $this->session->filter_members;
319 } else {
320 $filters = new MembersList();
321 }
322
323 // check for ajax mode
324 $ajax = false;
325 if (
326 $request->isXhr()
327 || (isset($post['ajax'])
328 && $post['ajax'] == 'true')
329 ) {
330 $ajax = true;
331
332 //retrieve selected members
333 $selection = $post['selection'] ?? array();
334
335 $filters->selected = $selection;
336 $this->session->filter_members = $filters;
337 } else {
338 $selection = $filters->selected;
339 }
340
341 // display page
342 $this->view->render(
343 $response,
344 'attendance_sheet_details.tpl',
345 [
346 'page_title' => _T("Attendance sheet configuration"),
347 'ajax' => $ajax,
348 'selection' => $selection
349 ]
350 );
351 return $response;
352 }
353
354 /**
355 * PDF attendance sheet
356 *
357 * @param Request $request PSR Request
358 * @param Response $response PSR Response
359 *
360 * @return Response
361 */
362 public function attendanceSheet(Request $request, Response $response): Response
363 {
364 $post = $request->getParsedBody();
365
366 if ($this->session->filter_members !== null) {
367 $filters = $this->session->filter_members;
368 } else {
369 $filters = new MembersList();
370 }
371
372 //retrieve selected members
373 $selection = (isset($post['selection'])) ? $post['selection'] : array();
374
375 $filters->selected = $selection;
376 $this->session->filter_members = $filters;
377
378 if (count($filters->selected) == 0) {
379 Analog::log('No member selected to generate attendance sheet', Analog::INFO);
380 $this->flash->addMessage(
381 'error_detected',
382 _T("No member selected to generate attendance sheet")
383 );
384
385 return $response
386 ->withStatus(301)
387 ->withHeader('Location', $this->router->pathFor('members'));
388 }
389
390 $m = new Members();
391 $members = $m->getArrayList(
392 $filters->selected,
393 array('nom_adh', 'prenom_adh'),
394 true
395 );
396
397 if (!is_array($members) || count($members) < 1) {
398 Analog::log('No member selected to generate attendance sheet', Analog::INFO);
399 $this->flash->addMessage(
400 'error_detected',
401 _T("No member selected to generate attendance sheet")
402 );
403
404 return $response
405 ->withStatus(301)
406 ->withHeader('Location', $this->router->pathFor('members'));
407 }
408
409 $doc_title = _T("Attendance sheet");
410 if (isset($post['sheet_type']) && trim($post['sheet_type']) != '') {
411 $doc_title = $post['sheet_type'];
412 }
413
414 $data = [
415 'doc_title' => $doc_title,
416 'title' => $post['sheet_title'] ?? null,
417 'subtitle' => $post['sheet_sub_title'] ?? null,
418 'sheet_date' => $post['sheet_date'] ?? null
419 ];
420 $pdf = new PdfAttendanceSheet($this->zdb, $this->preferences, $data);
421 //with or without images?
422 if (isset($post['sheet_photos']) && $post['sheet_photos'] === '1') {
423 $pdf->withImages();
424 }
425 $pdf->drawSheet($members);
426
427 return $this->sendResponse($response, $pdf);
428 }
429
430 /**
431 * Contribution PDF
432 *
433 * @param Request $request PSR Request
434 * @param Response $response PSR Response
435 * @param integer $id Contribution id
436 *
437 * @return Response
438 */
439 public function contribution(Request $request, Response $response, int $id): Response
440 {
441 $contribution = new Contribution($this->zdb, $this->login, $id);
442 if ($contribution->id == '') {
443 //not possible to load contribution, exit
444 $this->flash->addMessage(
445 'error_detected',
446 str_replace(
447 '%id',
448 $id,
449 _T("Unable to load contribution #%id!")
450 )
451 );
452 return $response
453 ->withStatus(301)
454 ->withHeader('Location', $this->router->pathFor(
455 'contributions',
456 ['type' => 'contributions']
457 ));
458 }
459
460 $pdf = new PdfContribution($contribution, $this->zdb, $this->preferences);
461 return $this->sendResponse($response, $pdf);
462 }
463
464 /**
465 * Groups PDF
466 *
467 * @param Request $request PSR Request
468 * @param Response $response PSR Response
469 * @param integer $id Group id
470 *
471 * @return Response
472 */
473 public function group(Request $request, Response $response, int $id = null): Response
474 {
475 $groups = new Groups($this->zdb, $this->login);
476
477 $groups_list = null;
478 if ($id !== null) {
479 $groups_list = $groups->getList(true, $id);
480 } else {
481 $groups_list = $groups->getList();
482 }
483
484 if (!is_array($groups_list) || count($groups_list) < 1) {
485 Analog::log(
486 'An error has occurred, unable to get groups list.',
487 Analog::ERROR
488 );
489
490 $this->flash->addMessage(
491 'error_detected',
492 _T("Unable to get groups list.")
493 );
494
495 return $response
496 ->withStatus(301)
497 ->withHeader('Location', $this->router->pathFor('groups'));
498 }
499
500 $pdf = new PdfGroups($this->preferences);
501 $pdf->draw($groups_list, $this->login);
502
503 return $this->sendResponse($response, $pdf);
504 }
505
506 /**
507 * PDF models list
508 *
509 * @param Request $request PSR Request
510 * @param Response $response PSR Response
511 * @param integer $id Model id
512 *
513 * @return Response
514 */
515 public function models(Request $request, Response $response, int $id = null): Response
516 {
517 $mid = 1;
518 if (isset($_POST[PdfModel::PK])) {
519 $mid = (int)$_POST[PdfModel::PK];
520 } elseif ($id !== null) {
521 $mid = $id;
522 }
523
524
525 $ms = new PdfModels($this->zdb, $this->preferences, $this->login);
526 $models = $ms->getList();
527
528 $model = null;
529 foreach ($models as $m) {
530 if ($m->id === $mid) {
531 $model = $m;
532 break;
533 }
534 }
535
536 $tpl = null;
537 $params = ['model' => $model];
538
539 //Render directly template if we called from ajax,
540 //render in a full page otherwise
541 if (
542 $request->isXhr()
543 || (isset($request->getQueryParams()['ajax'])
544 && $request->getQueryParams()['ajax'] == 'true')
545 ) {
546 $tpl = 'gestion_pdf_content.tpl';
547 } else {
548 $tpl = 'gestion_pdf.tpl';
549 $params += [
550 'page_title' => _T("PDF models"),
551 'models' => $models
552 ];
553 }
554
555 // display page
556 $this->view->render(
557 $response,
558 $tpl,
559 $params
560 );
561 return $response;
562 }
563
564 /**
565 * Store PDF models
566 *
567 * @param Request $request PSR Request
568 * @param Response $response PSR Response
569 *
570 * @return Response
571 */
572 public function storeModels(Request $request, Response $response): Response
573 {
574 $post = $request->getParsedBody();
575 $error_detected = [];
576
577 if (!isset($post['model_type'])) {
578 $error_detected[] = _T("Missing PDF model type!");
579 } else {
580 $type = (int)$post['model_type'];
581 $class = PdfModel::getTypeClass($type);
582 if (isset($post[PdfModel::PK])) {
583 $model = new $class($this->zdb, $this->preferences, (int)$post[PdfModel::PK]);
584 } else {
585 $model = new $class($this->zdb, $this->preferences);
586 }
587
588 try {
589 $fields = [
590 'model_header' => 'header',
591 'model_footer' => 'footer',
592 'model_body' => 'body',
593 'model_title' => 'title',
594 'model_subtitle' => 'subtitle',
595 'model_styles' => 'styles'
596 ];
597
598 $model->type = $type;
599 foreach ($fields as $pvar => $prop) {
600 if (isset($post[$pvar])) {
601 $model->$prop = $post[$pvar];
602 }
603 }
604
605 $res = $model->store();
606 if ($res === true) {
607 $this->flash->addMessage(
608 'success_detected',
609 _T("Model has been successfully stored!")
610 );
611 } else {
612 $error_detected[] = _T("Model has not been stored :(");
613 }
614 } catch (Throwable $e) {
615 $error_detected[] = $e->getMessage();
616 }
617 }
618
619 if (count($error_detected) > 0) {
620 foreach ($error_detected as $error) {
621 $this->flash->addMessage(
622 'error_detected',
623 $error
624 );
625 }
626 }
627
628 return $response
629 ->withStatus(301)
630 ->withHeader('Location', $this->router->pathFor('pdfModels', ['id' => $model->id ?? null]));
631 }
632
633
634 /**
635 * Get direct document
636 *
637 * @param Request $request PSR Request
638 * @param Response $response PSR Response
639 * @param string $hash Hash
640 *
641 * @return Response
642 */
643 public function directlinkDocument(Request $request, Response $response, string $hash): Response
644 {
645 $post = $request->getParsedBody();
646 $email = $post['email'];
647
648 $links = new Links($this->zdb);
649 $valid = $links->isHashValid($hash, $email);
650
651 if ($valid === false) {
652 $this->flash->addMessage(
653 'error_detected',
654 _T("Invalid link!")
655 );
656
657 return $response->withStatus(301)
658 ->withHeader('Location', $this->router->pathFor('directlink', ['hash' => $hash]));
659 }
660
661 $target = $valid[0];
662 $id = (int)$valid[1];
663
664 //get user information (like id...) from DB since its missing
665 $select = $this->zdb->select(Adherent::TABLE, 'a');
666 $select->where(['email_adh' => $post['email']]);
667 $results = $this->zdb->execute($select);
668 $row = $results->current();
669
670 //create a new login instance, to not break current session if any
671 //this will be passed directly to Contribution constructor
672 $login = new Login(
673 $this->zdb,
674 $this->i18n
675 );
676 $login->setId((int)$row['id_adh']);
677
678 if ($target === Links::TARGET_MEMBERCARD) {
679 $m = new Members();
680 $members = $m->getArrayList(
681 [$id],
682 array('nom_adh', 'prenom_adh'),
683 true
684 );
685
686 if (!is_array($members) || count($members) < 1) {
687 Analog::log(
688 'An error has occurred, unable to get members list.',
689 Analog::ERROR
690 );
691
692 $this->flash->addMessage(
693 'error_detected',
694 _T("Unable to get members list.")
695 );
696
697 return $response
698 ->withStatus(301)
699 ->withHeader('Location', $this->router->pathFor('directlink', ['hash' => $hash]));
700 }
701
702 $pdf = new PdfMembersCards($this->preferences);
703 $pdf->drawCards($members);
704 } else {
705 $contribution = new Contribution($this->zdb, $login, $id);
706 if ($contribution->id == '') {
707 //not possible to load contribution, exit
708 $this->flash->addMessage(
709 'error_detected',
710 str_replace(
711 '%id',
712 $id,
713 _T("Unable to load contribution #%id!")
714 )
715 );
716 return $response
717 ->withStatus(301)
718 ->withHeader('Location', $this->router->pathFor(
719 'directlink',
720 ['hash' => $hash]
721 ));
722 }
723 $pdf = new PdfContribution($contribution, $this->zdb, $this->preferences);
724 }
725
726 return $this->sendResponse($response, $pdf);
727 }
728 }