3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette main controller
10 * Copyright © 2019-2023 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
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.
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.
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/>.
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
37 namespace Galette\Controllers
;
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
;
53 use Galette\IO\Charts
;
54 use Galette\Repository\Members
;
55 use Galette\Repository\Reminders
;
59 * Galette main controller
61 * @category Controllers
62 * @name GaletteController
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
71 class GaletteController
extends AbstractController
76 * @param Request $request PSR Request
77 * @param Response $response PSR Response
81 public function slash(Request
$request, Response
$response): Response
83 return $this->galetteRedirect($request, $response);
89 * @param Request $request PSR Request
90 * @param Response $response PSR Response
94 public function systemInformation(Request
$request, Response
$response): Response
96 $sysinfos = new SysInfos();
97 $raw_infos = $sysinfos->getRawData(
106 'pages/sysinfos.html.twig',
108 'page_title' => _T("System information"),
109 'rawinfos' => $raw_infos
118 * @param Request $request PSR Request
119 * @param Response $response PSR Response
123 public function dashboard(Request
$request, Response
$response): Response
125 $news = new News($this->preferences
->pref_rss_url
);
128 'page_title' => _T("Dashboard"),
129 'contentcls' => 'desktop',
130 'news' => $news->getPosts(),
131 'show_dashboard' => $_COOKIE['show_galette_dashboard']
134 $hide_telemetry = true;
135 if ($this->login
->isAdmin()) {
136 $telemetry = new \Galette\Util\
Telemetry(
141 $params['reguuid'] = $telemetry->getRegistrationUuid();
142 $params['telemetry_sent'] = $telemetry->isSent();
143 $params['registered'] = $telemetry->isRegistered();
145 $hide_telemetry = $telemetry->isSent() && $telemetry->isRegistered()
146 ||
isset($_COOKIE['hide_galette_telemetry']) && $_COOKIE['hide_galette_telemetry'];
148 $params['hide_telemetry'] = $hide_telemetry;
153 'pages/desktop.html.twig',
162 * @param Request $request PSR Request
163 * @param Response $response PSR Response
167 public function preferences(Request
$request, Response
$response): Response
169 // flagging required fields
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
191 if ($this->login
->isSuperAdmin() && GALETTE_MODE
!== Galette
::MODE_DEMO
) {
192 $required['pref_admin_login'] = 1;
195 $prefs_fields = $this->preferences
->getFieldsNames();
198 foreach ($prefs_fields as $fieldname) {
199 $pref[$fieldname] = $this->preferences
->$fieldname;
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;
208 //List available themes
210 $d = dir(GALETTE_THEMES_PATH
);
211 while (($entry = $d->read()) !== false) {
212 $full_entry = GALETTE_THEMES_PATH
. $entry;
216 && is_dir($full_entry)
217 && file_exists($full_entry . '/page.html.twig')
224 //List payment types for default to be selected
225 $ptypes = new PaymentTypes(
230 $ptlist = $ptypes->getList();
233 $s = new Status($this->zdb
);
236 $tab = $request->getQueryParams()['tab'] ??
'general';
241 'pages/preferences.html.twig',
243 'page_title' => _T("Settings"),
244 'staff_members' => $m->getStaffMembersList(true),
247 'pref_numrows_options' => array(
253 'print_logo' => $this->print_logo
,
254 'required' => $required,
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")
262 'paymenttypes' => $ptlist,
263 'osocials' => new Social($this->zdb
),
273 * @param Request $request PSR Request
274 * @param Response $response PSR Response
278 public function storePreferences(Request
$request, Response
$response): Response
280 $post = $request->getParsedBody();
281 $error_detected = [];
282 $warning_detected = [];
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.");
290 $this->flash
->addMessage(
292 _T("Preferences has been saved.")
295 $warning_detected = array_merge($warning_detected, $this->preferences
->checkCardsSizes());
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']);
304 $error_detected[] = $this->logo
->getErrorMessage($res);
306 $this->logo
= new Logo();
310 } elseif ($_FILES['logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
312 $this->logo
->getPhpErrorMessage($_FILES['logo']['error']),
315 $error_detected[] = $this->logo
->getPhpErrorMessage(
316 $_FILES['logo']['error']
321 if (GALETTE_MODE
!== Galette
::MODE_DEMO
&& isset($post['del_logo'])) {
322 if (!$this->logo
->delete()) {
323 $error_detected[] = _T("Delete failed");
325 $this->logo
= new Logo(); //get default Logo
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']);
336 $error_detected[] = $this->print_logo
->getErrorMessage($res);
338 $this->print_logo
= new PrintLogo();
342 } elseif ($_FILES['card_logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
344 $this->print_logo
->getPhpErrorMessage($_FILES['card_logo']['error']),
347 $error_detected[] = $this->print_logo
->getPhpErrorMessage(
348 $_FILES['card_logo']['error']
353 if (GALETTE_MODE
!== Galette
::MODE_DEMO
&& isset($post['del_card_logo'])) {
354 if (!$this->print_logo
->delete()) {
355 $error_detected[] = _T("Delete failed");
357 $this->print_logo
= new PrintLogo();
361 $error_detected = $this->preferences
->getErrors();
364 if (count($error_detected) > 0) {
365 $this->session
->entered_preferences
= $post;
367 foreach ($error_detected as $error) {
368 $this->flash
->addMessage(
375 if (count($warning_detected) > 0) {
377 foreach ($warning_detected as $warning) {
378 $this->flash
->addMessage(
385 if (isset($post['tab']) && $post['tab'] != 'general') {
386 $tab = '?tab=' . $post['tab'];
392 ->withHeader('Location', $this->routeparser
->urlFor('preferences') . $tab);
396 * Test mail parameters
398 * @param Request $request PSR Request
399 * @param Response $response PSR Response
403 public function testEmail(Request
$request, Response
$response): Response
406 if (!$this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
407 $this->flash
->addMessage(
409 _T("You asked Galette to send a test email, but email has been disabled in the preferences.")
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(
419 $dest => _T("Galette admin")
422 $mail->setMessage(_T('Test message.'));
423 $sent = $mail->send();
426 $this->flash
->addMessage(
431 _T("An email has been sent to %email")
435 $this->flash
->addMessage(
440 _T("No email sent to %email")
445 $this->flash
->addMessage(
447 _T("Invalid email adress!")
452 if (!($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')) {
455 ->withHeader('Location', $this->routeparser
->urlFor('preferences'));
457 return $this->withJson(
469 * @param Request $request PSR Request
470 * @param Response $response PSR Response
474 public function charts(Request
$request, Response
$response): Response
476 $charts = new Charts(
478 Charts
::MEMBERS_STATUS_PIE
,
479 Charts
::MEMBERS_STATEDUE_PIE
,
480 Charts
::CONTRIBS_TYPES_PIE
,
481 Charts
::COMPANIES_OR_NOT
,
482 Charts
::CONTRIBS_ALLTIME
489 'pages/charts.html.twig',
491 'page_title' => _T("Charts"),
492 'charts' => $charts->getCharts(),
493 'require_charts' => true
500 * Core fields configuration page
502 * @param Request $request PSR Request
503 * @param Response $response PSR Response
507 public function configureCoreFields(Request
$request, Response
$response): Response
509 $fc = $this->fields_config
;
512 'page_title' => _T("Fields configuration"),
514 'categories' => FieldsCategories
::getList($this->zdb
),
515 'categorized_fields' => $fc->getCategorizedFields(),
516 'non_required' => $fc->getNonRequired()
522 'pages/configuration_core_fields.html.twig',
529 * Process core fields configuration
531 * @param Request $request PSR Request
532 * @param Response $response PSR Response
536 public function storeCoreFieldsConfig(Request
$request, Response
$response): Response
538 $post = $request->getParsedBody();
539 $fc = $this->fields_config
;
544 foreach ($post['fields'] as $abs_pos => $field) {
545 if ($current_cat != $post[$field . '_category']) {
546 //reset position when category has changed
548 //set new current category
549 $current_cat = $post[$field . '_category'];
553 if (isset($post[$field . '_required'])) {
554 $required = $post[$field . '_required'];
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
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(
575 _T("Fields configuration has been successfully stored")
578 $this->flash
->addMessage(
580 _T("An error occurred while storing fields configuration :(")
586 ->withHeader('Location', $this->routeparser
->urlFor('configureCoreFields'));
590 * Core lists configuration page
592 * @param Request $request PSR Request
593 * @param Response $response PSR Response
594 * @param string $table Tbale name
598 public function configureListFields(Request
$request, Response
$response, string $table): Response
600 //TODO: check if type table exists
602 $lc = $this->lists_config
;
605 'page_title' => _T("Lists configuration"),
608 'listed_fields' => $lc->getListedFields(),
609 'remaining_fields' => $lc->getRemainingFields()
615 'pages/configuration_core_lists.html.twig',
622 * Process list fields configuration
624 * @param Request $request PSR Request
625 * @param Response $response PSR Response
629 public function storeListFields(Request
$request, Response
$response): Response
631 $post = $request->getParsedBody();
633 $lc = $this->lists_config
;
635 foreach ($post['fields'] as $field) {
636 $fields[] = $lc->getField($field);
638 $success = $lc->setListFields($fields);
640 if ($success === true) {
641 $this->flash
->addMessage(
643 _T("List configuration has been successfully stored")
646 $this->flash
->addMessage(
648 _T("An error occurred while storing list configuration :(")
654 ->withHeader('Location', $this->routeparser
->urlFor('configureListFields', $this->getArgs($request)));
660 * @param Request $request PSR Request
661 * @param Response $response PSR Response
665 public function reminders(Request
$request, Response
$response): Response
667 $texts = new Texts($this->preferences
, $this->routeparser
);
670 'impending' => $texts->getTexts('impendingduedate', $this->preferences
->pref_lang
),
671 'late' => $texts->getTexts('lateduedate', $this->preferences
->pref_lang
)
674 $members = new Members();
675 $reminders = $members->getRemindersCount();
680 'pages/reminder.html.twig',
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']
696 * @param Request $request PSR Request
697 * @param Response $response PSR Response
701 public function doReminders(Request
$request, Response
$response): Response
703 $error_detected = [];
704 $warning_detected = [];
705 $success_detected = [];
707 $post = $request->getParsedBody();
708 $texts = new Texts($this->preferences
, $this->routeparser
);
710 if (isset($post['reminders'])) {
711 $selected = $post['reminders'];
713 $reminders = new Reminders($selected);
716 $labels_members = array();
717 if (isset($post['reminder_wo_mail'])) {
721 $list_reminders = $reminders->getList($this->zdb
, $labels);
722 if (count($list_reminders) == 0) {
723 $warning_detected[] = _T("No reminder to send for now.");
725 foreach ($list_reminders as $reminder) {
726 if ($labels === false) {
729 ->setLogin($this->login
)
730 ->setPreferences($this->preferences
)
731 ->setRouteParser($this->routeparser
)
733 //send reminders by email
734 $sent = $reminder->send($texts, $this->history
, $this->zdb
);
736 if ($sent === true) {
737 $success_detected[] = $reminder->getMessage();
739 $error_detected[] = $reminder->getMessage();
742 //generate labels for members without email address
743 $labels_members[] = $reminder->member_id
;
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;
757 $this->routeparser
->urlFor('pdf-members-labels') . '?session_var=' . $session_var
760 $error_detected[] = _T("There are no member to proceed.");
764 if (count($error_detected) > 0) {
767 _T("Reminder has not been sent:")
771 if (count($success_detected) > 0) {
774 _T("Sent reminders:")
779 //flash messages if any
780 if (count($error_detected) > 0) {
781 foreach ($error_detected as $error) {
782 $this->flash
->addMessage('error_detected', $error);
785 if (count($warning_detected) > 0) {
786 foreach ($warning_detected as $warning) {
787 $this->flash
->addMessage('warning_detected', $warning);
790 if (count($success_detected) > 0) {
791 foreach ($success_detected as $success) {
792 $this->flash
->addMessage('success_detected', $success);
798 ->withHeader('Location', $this->routeparser
->urlFor('reminders'));
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'
811 public function filterReminders(Request
$request, Response
$response, string $membership, string $mail): Response
813 //always reset filters
814 $filters = new MembersList();
815 $filters->filter_account
= Members
::ACTIVE_ACCOUNT
;
817 $membership = ($membership === 'nearly' ?
818 Members
::MEMBERSHIP_NEARLY
: Members
::MEMBERSHIP_LATE
);
819 $filters->membership_filter
= $membership;
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;
826 $this->session
->filter_members
= $filters;
830 ->withHeader('Location', $this->routeparser
->urlFor('members'));
834 * Direct document page
836 * @param Request $request PSR Request
837 * @param Response $response PSR Response
838 * @param string $hash Hash
842 public function documentLink(Request
$request, Response
$response, string $hash): Response
847 'pages/directlink.html.twig',
850 'page_title' => _T('Download document')
859 * @param Request $request PSR Request
860 * @param Response $response PSR Response
864 public function favicon(Request
$request, Response
$response): Response