]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/GaletteController.php
43c6e73271a78365a48fc3e12fa14a10bcceae1f
[galette.git] / galette / lib / Galette / Controllers / GaletteController.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Galette main 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 Entity
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-02
35 */
36
37 namespace Galette\Controllers;
38
39 use Galette\Entity\Social;
40 use Galette\Repository\PaymentTypes;
41 use Slim\Psr7\Request;
42 use Slim\Psr7\Response;
43 use Galette\Core\Logo;
44 use Galette\Core\PrintLogo;
45 use Galette\Core\Galette;
46 use Galette\Core\GaletteMail;
47 use Galette\Core\SysInfos;
48 use Galette\Entity\FieldsCategories;
49 use Galette\Entity\Status;
50 use Galette\Entity\Texts;
51 use Galette\Filters\MembersList;
52 use Galette\IO\News;
53 use Galette\IO\Charts;
54 use Galette\Repository\Members;
55 use Galette\Repository\Reminders;
56 use Analog\Analog;
57
58 /**
59 * Galette main controller
60 *
61 * @category Controllers
62 * @name GaletteController
63 * @package Galette
64 * @author Johan Cwiklinski <johan@x-tnd.be>
65 * @copyright 2019-2023 The Galette Team
66 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
67 * @link http://galette.tuxfamily.org
68 * @since Available since 0.9.4dev - 2019-12-02
69 */
70
71 class GaletteController extends AbstractController
72 {
73 /**
74 * Main route
75 *
76 * @param Request $request PSR Request
77 * @param Response $response PSR Response
78 *
79 * @return Response
80 */
81 public function slash(Request $request, Response $response): Response
82 {
83 return $this->galetteRedirect($request, $response);
84 }
85
86 /**
87 * System information
88 *
89 * @param Request $request PSR Request
90 * @param Response $response PSR Response
91 *
92 * @return Response
93 */
94 public function systemInformation(Request $request, Response $response): Response
95 {
96 $sysinfos = new SysInfos();
97 $raw_infos = $sysinfos->getRawData(
98 $this->zdb,
99 $this->preferences,
100 $this->plugins
101 );
102
103 // display page
104 $this->view->render(
105 $response,
106 'pages/sysinfos.html.twig',
107 array(
108 'page_title' => _T("System information"),
109 'rawinfos' => $raw_infos
110 )
111 );
112 return $response;
113 }
114
115 /**
116 * Dashboard page
117 *
118 * @param Request $request PSR Request
119 * @param Response $response PSR Response
120 *
121 * @return Response
122 */
123 public function dashboard(Request $request, Response $response): Response
124 {
125 $news = new News($this->preferences->pref_rss_url);
126
127 $params = [
128 'page_title' => _T("Dashboard"),
129 'contentcls' => 'desktop',
130 'news' => $news->getPosts(),
131 'show_dashboard' => $_COOKIE['show_galette_dashboard']
132 ];
133
134 $hide_telemetry = true;
135 if ($this->login->isAdmin()) {
136 $telemetry = new \Galette\Util\Telemetry(
137 $this->zdb,
138 $this->preferences,
139 $this->plugins
140 );
141 $params['reguuid'] = $telemetry->getRegistrationUuid();
142 $params['telemetry_sent'] = $telemetry->isSent();
143 $params['registered'] = $telemetry->isRegistered();
144
145 $hide_telemetry = $telemetry->isSent() && $telemetry->isRegistered()
146 || isset($_COOKIE['hide_galette_telemetry']) && $_COOKIE['hide_galette_telemetry'];
147 }
148 $params['hide_telemetry'] = $hide_telemetry;
149
150 // display page
151 $this->view->render(
152 $response,
153 'pages/desktop.html.twig',
154 $params
155 );
156 return $response;
157 }
158
159 /**
160 * Preferences page
161 *
162 * @param Request $request PSR Request
163 * @param Response $response PSR Response
164 *
165 * @return Response
166 */
167 public function preferences(Request $request, Response $response): Response
168 {
169 // flagging required fields
170 $required = array(
171 'pref_nom' => 1,
172 'pref_lang' => 1,
173 'pref_numrows' => 1,
174 'pref_log' => 1,
175 'pref_statut' => 1,
176 'pref_etiq_marges_v' => 1,
177 'pref_etiq_marges_h' => 1,
178 'pref_etiq_hspace' => 1,
179 'pref_etiq_vspace' => 1,
180 'pref_etiq_hsize' => 1,
181 'pref_etiq_vsize' => 1,
182 'pref_etiq_cols' => 1,
183 'pref_etiq_rows' => 1,
184 'pref_etiq_corps' => 1,
185 'pref_card_marges_v' => 1,
186 'pref_card_marges_h' => 1,
187 'pref_card_hspace' => 1,
188 'pref_card_vspace' => 1
189 );
190
191 if ($this->login->isSuperAdmin() && GALETTE_MODE !== Galette::MODE_DEMO) {
192 $required['pref_admin_login'] = 1;
193 }
194
195 $prefs_fields = $this->preferences->getFieldsNames();
196 // collect data
197 $pref = [];
198 foreach ($prefs_fields as $fieldname) {
199 $pref[$fieldname] = $this->preferences->$fieldname;
200 }
201
202 //on error, user values are stored into session
203 if ($this->session->entered_preferences) {
204 $pref = array_merge($pref, $this->session->entered_preferences);
205 $this->session->entered_preferences = null;
206 }
207
208 //List available themes
209 $themes = array();
210 $d = dir(GALETTE_THEMES_PATH);
211 while (($entry = $d->read()) !== false) {
212 $full_entry = GALETTE_THEMES_PATH . $entry;
213 if (
214 $entry != '.'
215 && $entry != '..'
216 && is_dir($full_entry)
217 && file_exists($full_entry . '/page.html.twig')
218 ) {
219 $themes[] = $entry;
220 }
221 }
222 $d->close();
223
224 //List payment types for default to be selected
225 $ptypes = new PaymentTypes(
226 $this->zdb,
227 $this->preferences,
228 $this->login
229 );
230 $ptlist = $ptypes->getList();
231
232 $m = new Members();
233 $s = new Status($this->zdb);
234
235 //Active tab on page
236 $tab = $request->getQueryParams()['tab'] ?? 'general';
237
238 // display page
239 $this->view->render(
240 $response,
241 'pages/preferences.html.twig',
242 array(
243 'page_title' => _T("Settings"),
244 'staff_members' => $m->getStaffMembersList(true),
245 'time' => time(),
246 'pref' => $pref,
247 'pref_numrows_options' => array(
248 10 => '10',
249 20 => '20',
250 50 => '50',
251 100 => '100'
252 ),
253 'print_logo' => $this->print_logo,
254 'required' => $required,
255 'themes' => $themes,
256 'statuts' => $s->getList(),
257 'accounts_options' => array(
258 Members::ALL_ACCOUNTS => _T("All accounts"),
259 Members::ACTIVE_ACCOUNT => _T("Active accounts"),
260 Members::INACTIVE_ACCOUNT => _T("Inactive accounts")
261 ),
262 'paymenttypes' => $ptlist,
263 'osocials' => new Social($this->zdb),
264 'tab' => $tab
265 )
266 );
267 return $response;
268 }
269
270 /**
271 * Store preferences
272 *
273 * @param Request $request PSR Request
274 * @param Response $response PSR Response
275 *
276 * @return Response
277 */
278 public function storePreferences(Request $request, Response $response): Response
279 {
280 $post = $request->getParsedBody();
281 $error_detected = [];
282 $warning_detected = [];
283
284 // Validation
285 if (isset($post['valid']) && $post['valid'] == '1') {
286 if ($this->preferences->check($post, $this->login)) {
287 if (!$this->preferences->store()) {
288 $error_detected[] = _T("An SQL error has occurred while storing preferences. Please try again, and contact the administrator if the problem persists.");
289 } else {
290 $this->flash->addMessage(
291 'success_detected',
292 _T("Preferences has been saved.")
293 );
294 }
295 $warning_detected = array_merge($warning_detected, $this->preferences->checkCardsSizes());
296
297 // picture upload
298 if (GALETTE_MODE !== Galette::MODE_DEMO && isset($_FILES['logo'])) {
299 if ($_FILES['logo']['error'] === UPLOAD_ERR_OK) {
300 if ($_FILES['logo']['tmp_name'] != '') {
301 if (is_uploaded_file($_FILES['logo']['tmp_name'])) {
302 $res = $this->logo->store($_FILES['logo']);
303 if ($res < 0) {
304 $error_detected[] = $this->logo->getErrorMessage($res);
305 } else {
306 $this->logo = new Logo();
307 }
308 }
309 }
310 } elseif ($_FILES['logo']['error'] !== UPLOAD_ERR_NO_FILE) {
311 Analog::log(
312 $this->logo->getPhpErrorMessage($_FILES['logo']['error']),
313 Analog::WARNING
314 );
315 $error_detected[] = $this->logo->getPhpErrorMessage(
316 $_FILES['logo']['error']
317 );
318 }
319 }
320
321 if (GALETTE_MODE !== Galette::MODE_DEMO && isset($post['del_logo'])) {
322 if (!$this->logo->delete()) {
323 $error_detected[] = _T("Delete failed");
324 } else {
325 $this->logo = new Logo(); //get default Logo
326 }
327 }
328
329 // Card logo upload
330 if (GALETTE_MODE !== Galette::MODE_DEMO && isset($_FILES['card_logo'])) {
331 if ($_FILES['card_logo']['error'] === UPLOAD_ERR_OK) {
332 if ($_FILES['card_logo']['tmp_name'] != '') {
333 if (is_uploaded_file($_FILES['card_logo']['tmp_name'])) {
334 $res = $this->print_logo->store($_FILES['card_logo']);
335 if ($res < 0) {
336 $error_detected[] = $this->print_logo->getErrorMessage($res);
337 } else {
338 $this->print_logo = new PrintLogo();
339 }
340 }
341 }
342 } elseif ($_FILES['card_logo']['error'] !== UPLOAD_ERR_NO_FILE) {
343 Analog::log(
344 $this->print_logo->getPhpErrorMessage($_FILES['card_logo']['error']),
345 Analog::WARNING
346 );
347 $error_detected[] = $this->print_logo->getPhpErrorMessage(
348 $_FILES['card_logo']['error']
349 );
350 }
351 }
352
353 if (GALETTE_MODE !== Galette::MODE_DEMO && isset($post['del_card_logo'])) {
354 if (!$this->print_logo->delete()) {
355 $error_detected[] = _T("Delete failed");
356 } else {
357 $this->print_logo = new PrintLogo();
358 }
359 }
360 } else {
361 $error_detected = $this->preferences->getErrors();
362 }
363
364 if (count($error_detected) > 0) {
365 $this->session->entered_preferences = $post;
366 //report errors
367 foreach ($error_detected as $error) {
368 $this->flash->addMessage(
369 'error_detected',
370 $error
371 );
372 }
373 }
374
375 if (count($warning_detected) > 0) {
376 //report warnings
377 foreach ($warning_detected as $warning) {
378 $this->flash->addMessage(
379 'warning_detected',
380 $warning
381 );
382 }
383 }
384 }
385 if (isset($post['tab']) && $post['tab'] != 'general') {
386 $tab = '?tab=' . $post['tab'];
387 } else {
388 $tab = '';
389 }
390 return $response
391 ->withStatus(301)
392 ->withHeader('Location', $this->routeparser->urlFor('preferences') . $tab);
393 }
394
395 /**
396 * Test mail parameters
397 *
398 * @param Request $request PSR Request
399 * @param Response $response PSR Response
400 *
401 * @return Response
402 */
403 public function testEmail(Request $request, Response $response): Response
404 {
405 $sent = false;
406 if (!$this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) {
407 $this->flash->addMessage(
408 'error_detected',
409 _T("You asked Galette to send a test email, but email has been disabled in the preferences.")
410 );
411 } else {
412 $get = $request->getQueryParams();
413 $dest = (isset($get['adress']) ? $get['adress'] : $this->preferences->pref_email_newadh);
414 if (GaletteMail::isValidEmail($dest)) {
415 $mail = new GaletteMail($this->preferences);
416 $mail->setSubject(_T('Test message'));
417 $mail->setRecipients(
418 array(
419 $dest => _T("Galette admin")
420 )
421 );
422 $mail->setMessage(_T('Test message.'));
423 $sent = $mail->send();
424
425 if ($sent) {
426 $this->flash->addMessage(
427 'success_detected',
428 str_replace(
429 '%email',
430 $dest,
431 _T("An email has been sent to %email")
432 )
433 );
434 } else {
435 $this->flash->addMessage(
436 'error_detected',
437 str_replace(
438 '%email',
439 $dest,
440 _T("No email sent to %email")
441 )
442 );
443 }
444 } else {
445 $this->flash->addMessage(
446 'error_detected',
447 _T("Invalid email adress!")
448 );
449 }
450 }
451
452 if (!($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')) {
453 return $response
454 ->withStatus(301)
455 ->withHeader('Location', $this->routeparser->urlFor('preferences'));
456 } else {
457 return $this->withJson(
458 $response,
459 [
460 'sent' => $sent
461 ]
462 );
463 }
464 }
465
466 /**
467 * Charts page
468 *
469 * @param Request $request PSR Request
470 * @param Response $response PSR Response
471 *
472 * @return Response
473 */
474 public function charts(Request $request, Response $response): Response
475 {
476 $charts = new Charts(
477 array(
478 Charts::MEMBERS_STATUS_PIE,
479 Charts::MEMBERS_STATEDUE_PIE,
480 Charts::CONTRIBS_TYPES_PIE,
481 Charts::COMPANIES_OR_NOT,
482 Charts::CONTRIBS_ALLTIME
483 )
484 );
485
486 // display page
487 $this->view->render(
488 $response,
489 'pages/charts.html.twig',
490 array(
491 'page_title' => _T("Charts"),
492 'charts' => $charts->getCharts(),
493 'require_charts' => true
494 )
495 );
496 return $response;
497 }
498
499 /**
500 * Core fields configuration page
501 *
502 * @param Request $request PSR Request
503 * @param Response $response PSR Response
504 *
505 * @return Response
506 */
507 public function configureCoreFields(Request $request, Response $response): Response
508 {
509 $fc = $this->fields_config;
510
511 $params = [
512 'page_title' => _T("Fields configuration"),
513 'time' => time(),
514 'categories' => FieldsCategories::getList($this->zdb),
515 'categorized_fields' => $fc->getCategorizedFields(),
516 'non_required' => $fc->getNonRequired()
517 ];
518
519 // display page
520 $this->view->render(
521 $response,
522 'pages/configuration_core_fields.html.twig',
523 $params
524 );
525 return $response;
526 }
527
528 /**
529 * Process core fields configuration
530 *
531 * @param Request $request PSR Request
532 * @param Response $response PSR Response
533 *
534 * @return Response
535 */
536 public function storeCoreFieldsConfig(Request $request, Response $response): Response
537 {
538 $post = $request->getParsedBody();
539 $fc = $this->fields_config;
540
541 $pos = 0;
542 $current_cat = 0;
543 $res = array();
544 foreach ($post['fields'] as $abs_pos => $field) {
545 if ($current_cat != $post[$field . '_category']) {
546 //reset position when category has changed
547 $pos = 0;
548 //set new current category
549 $current_cat = $post[$field . '_category'];
550 }
551
552 $required = null;
553 if (isset($post[$field . '_required'])) {
554 $required = $post[$field . '_required'];
555 } else {
556 $required = false;
557 }
558
559 $res[$current_cat][] = array(
560 'field_id' => $field,
561 'label' => htmlspecialchars($post[$field . '_label'], ENT_QUOTES),
562 'category' => $post[$field . '_category'],
563 'visible' => $post[$field . '_visible'],
564 'required' => $required
565 );
566 $pos++;
567 }
568 //okay, we've got the new array, we send it to the
569 //Object that will store it in the database
570 $success = $fc->setFields($res);
571 FieldsCategories::setCategories($this->zdb, $post['categories']);
572 if ($success === true) {
573 $this->flash->addMessage(
574 'success_detected',
575 _T("Fields configuration has been successfully stored")
576 );
577 } else {
578 $this->flash->addMessage(
579 'error_detected',
580 _T("An error occurred while storing fields configuration :(")
581 );
582 }
583
584 return $response
585 ->withStatus(301)
586 ->withHeader('Location', $this->routeparser->urlFor('configureCoreFields'));
587 }
588
589 /**
590 * Core lists configuration page
591 *
592 * @param Request $request PSR Request
593 * @param Response $response PSR Response
594 * @param string $table Tbale name
595 *
596 * @return Response
597 */
598 public function configureListFields(Request $request, Response $response, string $table): Response
599 {
600 //TODO: check if type table exists
601
602 $lc = $this->lists_config;
603
604 $params = [
605 'page_title' => _T("Lists configuration"),
606 'table' => $table,
607 'time' => time(),
608 'listed_fields' => $lc->getListedFields(),
609 'remaining_fields' => $lc->getRemainingFields()
610 ];
611
612 // display page
613 $this->view->render(
614 $response,
615 'pages/configuration_core_lists.html.twig',
616 $params
617 );
618 return $response;
619 }
620
621 /**
622 * Process list fields configuration
623 *
624 * @param Request $request PSR Request
625 * @param Response $response PSR Response
626 *
627 * @return Response
628 */
629 public function storeListFields(Request $request, Response $response): Response
630 {
631 $post = $request->getParsedBody();
632
633 $lc = $this->lists_config;
634 $fields = [];
635 foreach ($post['fields'] as $field) {
636 $fields[] = $lc->getField($field);
637 }
638 $success = $lc->setListFields($fields);
639
640 if ($success === true) {
641 $this->flash->addMessage(
642 'success_detected',
643 _T("List configuration has been successfully stored")
644 );
645 } else {
646 $this->flash->addMessage(
647 'error_detected',
648 _T("An error occurred while storing list configuration :(")
649 );
650 }
651
652 return $response
653 ->withStatus(301)
654 ->withHeader('Location', $this->routeparser->urlFor('configureListFields', $this->getArgs($request)));
655 }
656
657 /**
658 * Reminders page
659 *
660 * @param Request $request PSR Request
661 * @param Response $response PSR Response
662 *
663 * @return Response
664 */
665 public function reminders(Request $request, Response $response): Response
666 {
667 $texts = new Texts($this->preferences, $this->routeparser);
668
669 $previews = array(
670 'impending' => $texts->getTexts('impendingduedate', $this->preferences->pref_lang),
671 'late' => $texts->getTexts('lateduedate', $this->preferences->pref_lang)
672 );
673
674 $members = new Members();
675 $reminders = $members->getRemindersCount();
676
677 // display page
678 $this->view->render(
679 $response,
680 'pages/reminder.html.twig',
681 [
682 'page_title' => _T("Reminders"),
683 'previews' => $previews,
684 'count_impending' => $reminders['impending'],
685 'count_impending_nomail' => $reminders['nomail']['impending'],
686 'count_late' => $reminders['late'],
687 'count_late_nomail' => $reminders['nomail']['late']
688 ]
689 );
690 return $response;
691 }
692
693 /**
694 * Send reminders
695 *
696 * @param Request $request PSR Request
697 * @param Response $response PSR Response
698 *
699 * @return Response
700 */
701 public function doReminders(Request $request, Response $response): Response
702 {
703 $error_detected = [];
704 $warning_detected = [];
705 $success_detected = [];
706
707 $post = $request->getParsedBody();
708 $texts = new Texts($this->preferences, $this->routeparser);
709 $selected = null;
710 if (isset($post['reminders'])) {
711 $selected = $post['reminders'];
712 }
713 $reminders = new Reminders($selected);
714
715 $labels = false;
716 $labels_members = array();
717 if (isset($post['reminder_wo_mail'])) {
718 $labels = true;
719 }
720
721 $list_reminders = $reminders->getList($this->zdb, $labels);
722 if (count($list_reminders) == 0) {
723 $warning_detected[] = _T("No reminder to send for now.");
724 } else {
725 foreach ($list_reminders as $reminder) {
726 if ($labels === false) {
727 $reminder
728 ->setDb($this->zdb)
729 ->setLogin($this->login)
730 ->setPreferences($this->preferences)
731 ->setRouteParser($this->routeparser)
732 ;
733 //send reminders by email
734 $sent = $reminder->send($texts, $this->history, $this->zdb);
735
736 if ($sent === true) {
737 $success_detected[] = $reminder->getMessage();
738 } else {
739 $error_detected[] = $reminder->getMessage();
740 }
741 } else {
742 //generate labels for members without email address
743 $labels_members[] = $reminder->member_id;
744 }
745 }
746
747 if ($labels === true) {
748 if (count($labels_members) > 0) {
749 $session_var = 'filters_reminders_labels';
750 $labels_filters = new MembersList();
751 $labels_filters->selected = $labels_members;
752 $this->session->$session_var = $labels_filters;
753 return $response
754 ->withStatus(307)
755 ->withHeader(
756 'Location',
757 $this->routeparser->urlFor('pdf-members-labels') . '?session_var=' . $session_var
758 );
759 } else {
760 $error_detected[] = _T("There are no member to proceed.");
761 }
762 }
763
764 if (count($error_detected) > 0) {
765 array_unshift(
766 $error_detected,
767 _T("Reminder has not been sent:")
768 );
769 }
770
771 if (count($success_detected) > 0) {
772 array_unshift(
773 $success_detected,
774 _T("Sent reminders:")
775 );
776 }
777 }
778
779 //flash messages if any
780 if (count($error_detected) > 0) {
781 foreach ($error_detected as $error) {
782 $this->flash->addMessage('error_detected', $error);
783 }
784 }
785 if (count($warning_detected) > 0) {
786 foreach ($warning_detected as $warning) {
787 $this->flash->addMessage('warning_detected', $warning);
788 }
789 }
790 if (count($success_detected) > 0) {
791 foreach ($success_detected as $success) {
792 $this->flash->addMessage('success_detected', $success);
793 }
794 }
795
796 return $response
797 ->withStatus(301)
798 ->withHeader('Location', $this->routeparser->urlFor('reminders'));
799 }
800
801 /**
802 * Main route
803 *
804 * @param Request $request PSR Request
805 * @param Response $response PSR Response
806 * @param string $membership Either 'late' or 'nearly'
807 * @param string $mail Either 'withmail' or 'withoutmail'
808 *
809 * @return Response
810 */
811 public function filterReminders(Request $request, Response $response, string $membership, string $mail): Response
812 {
813 //always reset filters
814 $filters = new MembersList();
815 $filters->filter_account = Members::ACTIVE_ACCOUNT;
816
817 $membership = ($membership === 'nearly' ?
818 Members::MEMBERSHIP_NEARLY : Members::MEMBERSHIP_LATE);
819 $filters->membership_filter = $membership;
820
821 //TODO: filter on reminder may take care of parent email as well
822 $mail = ($mail === 'withmail' ?
823 Members::FILTER_W_EMAIL : Members::FILTER_WO_EMAIL);
824 $filters->email_filter = $mail;
825
826 $this->session->filter_members = $filters;
827
828 return $response
829 ->withStatus(301)
830 ->withHeader('Location', $this->routeparser->urlFor('members'));
831 }
832
833 /**
834 * Direct document page
835 *
836 * @param Request $request PSR Request
837 * @param Response $response PSR Response
838 * @param string $hash Hash
839 *
840 * @return Response
841 */
842 public function documentLink(Request $request, Response $response, string $hash): Response
843 {
844 // display page
845 $this->view->render(
846 $response,
847 'pages/directlink.html.twig',
848 array(
849 'hash' => $hash,
850 'page_title' => _T('Download document')
851 )
852 );
853 return $response;
854 }
855
856 /**
857 * Main route
858 *
859 * @param Request $request PSR Request
860 * @param Response $response PSR Response
861 *
862 * @return Response
863 */
864 public function favicon(Request $request, Response $response): Response
865 {
866 return $response;
867 }
868 }