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