4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Controllers
;
24 use Galette\Entity\FieldsConfig
;
25 use Galette\Entity\Social
;
26 use Galette\Repository\PaymentTypes
;
27 use Slim\Psr7\Request
;
28 use Slim\Psr7\Response
;
29 use Galette\Core\Logo
;
30 use Galette\Core\PrintLogo
;
31 use Galette\Core\Galette
;
32 use Galette\Core\GaletteMail
;
33 use Galette\Core\SysInfos
;
34 use Galette\Entity\FieldsCategories
;
35 use Galette\Entity\Status
;
36 use Galette\Entity\Texts
;
37 use Galette\Filters\MembersList
;
39 use Galette\IO\Charts
;
40 use Galette\Repository\Members
;
41 use Galette\Repository\Reminders
;
45 * Galette main controller
47 * @author Johan Cwiklinski <johan@x-tnd.be>
50 class GaletteController
extends AbstractController
55 * @param Request $request PSR Request
56 * @param Response $response PSR Response
60 public function slash(Request
$request, Response
$response): Response
62 return $this->galetteRedirect($request, $response);
68 * @param Request $request PSR Request
69 * @param Response $response PSR Response
73 public function systemInformation(Request
$request, Response
$response): Response
75 $sysinfos = new SysInfos();
76 $raw_infos = $sysinfos->getRawData(
85 'pages/sysinfos.html.twig',
87 'page_title' => _T("System information"),
88 'rawinfos' => $raw_infos
97 * @param Request $request PSR Request
98 * @param Response $response PSR Response
102 public function dashboard(Request
$request, Response
$response): Response
104 $news = new News($this->preferences
->pref_rss_url
);
107 'page_title' => _T("Dashboard"),
108 'contentcls' => 'desktop',
109 'news' => $news->getPosts(),
110 'show_dashboard' => $_COOKIE['show_galette_dashboard']
113 $hide_telemetry = true;
114 if ($this->login
->isAdmin()) {
115 $telemetry = new \Galette\Util\
Telemetry(
120 $params['reguuid'] = $telemetry->getRegistrationUuid();
121 $params['telemetry_sent'] = $telemetry->isSent();
122 $params['registered'] = $telemetry->isRegistered();
124 $hide_telemetry = $telemetry->isSent() && $telemetry->isRegistered()
125 ||
isset($_COOKIE['hide_galette_telemetry']) && $_COOKIE['hide_galette_telemetry'];
127 $params['hide_telemetry'] = $hide_telemetry;
132 'pages/desktop.html.twig',
141 * @param Request $request PSR Request
142 * @param Response $response PSR Response
146 public function preferences(Request
$request, Response
$response): Response
148 // flagging required fields
155 'pref_etiq_marges_v' => 1,
156 'pref_etiq_marges_h' => 1,
157 'pref_etiq_hspace' => 1,
158 'pref_etiq_vspace' => 1,
159 'pref_etiq_hsize' => 1,
160 'pref_etiq_vsize' => 1,
161 'pref_etiq_cols' => 1,
162 'pref_etiq_rows' => 1,
163 'pref_etiq_corps' => 1,
164 'pref_card_marges_v' => 1,
165 'pref_card_marges_h' => 1,
166 'pref_card_hspace' => 1,
167 'pref_card_vspace' => 1
170 if ($this->login
->isSuperAdmin() && !Galette
::isDemo()) {
171 $required['pref_admin_login'] = 1;
174 $prefs_fields = $this->preferences
->getFieldsNames();
177 foreach ($prefs_fields as $fieldname) {
178 $pref[$fieldname] = $this->preferences
->$fieldname;
181 //on error, user values are stored into session
182 if ($this->session
->entered_preferences
) {
183 $pref = array_merge($pref, $this->session
->entered_preferences
);
184 $this->session
->entered_preferences
= null;
187 //List available themes
189 $d = dir(GALETTE_THEMES_PATH
);
190 while (($entry = $d->read()) !== false) {
191 $full_entry = GALETTE_THEMES_PATH
. $entry;
195 && is_dir($full_entry)
196 && file_exists($full_entry . '/page.html.twig')
203 //List payment types for default to be selected
204 $ptypes = new PaymentTypes(
209 $ptlist = $ptypes->getList(false);
212 $s = new Status($this->zdb
);
215 $tab = $request->getQueryParams()['tab'] ??
'general';
220 'pages/preferences.html.twig',
222 'page_title' => _T("Settings"),
223 'staff_members' => $m->getStaffMembersList(true),
226 'pref_numrows_options' => array(
232 'print_logo' => $this->print_logo
,
233 'required' => $required,
235 'statuts' => $s->getList(),
236 'accounts_options' => array(
237 Members
::ALL_ACCOUNTS
=> _T("All accounts"),
238 Members
::ACTIVE_ACCOUNT
=> _T("Active accounts"),
239 Members
::INACTIVE_ACCOUNT
=> _T("Inactive accounts")
241 'paymenttypes' => $ptlist,
242 'osocials' => new Social($this->zdb
),
252 * @param Request $request PSR Request
253 * @param Response $response PSR Response
257 public function storePreferences(Request
$request, Response
$response): Response
259 $post = $request->getParsedBody();
260 $error_detected = [];
261 $warning_detected = [];
264 if (isset($post['valid']) && $post['valid'] == '1') {
265 if ($this->preferences
->check($post, $this->login
)) {
266 if (!$this->preferences
->store()) {
267 $error_detected[] = _T("An SQL error has occurred while storing preferences. Please try again, and contact the administrator if the problem persists.");
269 $this->flash
->addMessage(
271 _T("Preferences has been saved.")
274 $warning_detected = array_merge($warning_detected, $this->preferences
->checkCardsSizes());
277 if (!Galette
::isDemo() && isset($_FILES['logo'])) {
278 if ($_FILES['logo']['error'] === UPLOAD_ERR_OK
) {
279 if ($_FILES['logo']['tmp_name'] != '') {
280 if (is_uploaded_file($_FILES['logo']['tmp_name'])) {
281 $res = $this->logo
->store($_FILES['logo']);
283 $error_detected[] = $this->logo
->getErrorMessage($res);
285 $this->logo
= new Logo();
289 } elseif ($_FILES['logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
291 $this->logo
->getPhpErrorMessage($_FILES['logo']['error']),
294 $error_detected[] = $this->logo
->getPhpErrorMessage(
295 $_FILES['logo']['error']
300 if (!Galette
::isDemo() && isset($post['del_logo'])) {
301 if (!$this->logo
->delete()) {
302 $error_detected[] = _T("Delete failed");
304 $this->logo
= new Logo(); //get default Logo
309 if (!Galette
::isDemo() && isset($_FILES['card_logo'])) {
310 if ($_FILES['card_logo']['error'] === UPLOAD_ERR_OK
) {
311 if ($_FILES['card_logo']['tmp_name'] != '') {
312 if (is_uploaded_file($_FILES['card_logo']['tmp_name'])) {
313 $res = $this->print_logo
->store($_FILES['card_logo']);
315 $error_detected[] = $this->print_logo
->getErrorMessage($res);
317 $this->print_logo
= new PrintLogo();
321 } elseif ($_FILES['card_logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
323 $this->print_logo
->getPhpErrorMessage($_FILES['card_logo']['error']),
326 $error_detected[] = $this->print_logo
->getPhpErrorMessage(
327 $_FILES['card_logo']['error']
332 if (!Galette
::isDemo() && isset($post['del_card_logo'])) {
333 if (!$this->print_logo
->delete()) {
334 $error_detected[] = _T("Delete failed");
336 $this->print_logo
= new PrintLogo();
340 $error_detected = $this->preferences
->getErrors();
343 if (count($error_detected) > 0) {
344 $this->session
->entered_preferences
= $post;
346 foreach ($error_detected as $error) {
347 $this->flash
->addMessage(
354 if (count($warning_detected) > 0) {
356 foreach ($warning_detected as $warning) {
357 $this->flash
->addMessage(
364 if (isset($post['tab']) && $post['tab'] != 'general') {
365 $tab = '?tab=' . $post['tab'];
371 ->withHeader('Location', $this->routeparser
->urlFor('preferences') . $tab);
375 * Test mail parameters
377 * @param Request $request PSR Request
378 * @param Response $response PSR Response
382 public function testEmail(Request
$request, Response
$response): Response
385 if (!$this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
386 $this->flash
->addMessage(
388 _T("You asked Galette to send a test email, but email has been disabled in the preferences.")
391 $get = $request->getQueryParams();
392 $dest = (isset($get['adress']) ?
$get['adress'] : $this->preferences
->pref_email_newadh
);
393 if (GaletteMail
::isValidEmail($dest)) {
394 $mail = new GaletteMail($this->preferences
);
395 $mail->setSubject(_T('Test message'));
396 $mail->setRecipients(
398 $dest => _T("Galette admin")
401 $mail->setMessage(_T('Test message.'));
402 $sent = $mail->send();
405 $this->flash
->addMessage(
410 _T("An email has been sent to %email")
414 $this->flash
->addMessage(
419 _T("No email sent to %email")
424 $this->flash
->addMessage(
426 _T("Invalid email adress!")
431 if (!($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')) {
434 ->withHeader('Location', $this->routeparser
->urlFor('preferences'));
436 return $this->withJson(
448 * @param Request $request PSR Request
449 * @param Response $response PSR Response
453 public function charts(Request
$request, Response
$response): Response
455 $charts = new Charts(
457 Charts
::MEMBERS_STATUS_PIE
,
458 Charts
::MEMBERS_STATEDUE_PIE
,
459 Charts
::CONTRIBS_TYPES_PIE
,
460 Charts
::COMPANIES_OR_NOT
,
461 Charts
::CONTRIBS_ALLTIME
468 'pages/charts.html.twig',
470 'page_title' => _T("Charts"),
471 'charts' => $charts->getCharts(),
472 'require_charts' => true
479 * Core fields configuration page
481 * @param Request $request PSR Request
482 * @param Response $response PSR Response
486 public function configureCoreFields(Request
$request, Response
$response): Response
488 $fc = $this->fields_config
;
491 'page_title' => _T("Fields configuration"),
493 'categories' => FieldsCategories
::getList($this->zdb
),
494 'categorized_fields' => $fc->getCategorizedFields(),
495 'non_required' => $fc->getNonRequired(),
496 'perm_names' => FieldsConfig
::getPermissionsList()
502 'pages/configuration_core_fields.html.twig',
509 * Process core fields configuration
511 * @param Request $request PSR Request
512 * @param Response $response PSR Response
516 public function storeCoreFieldsConfig(Request
$request, Response
$response): Response
518 $post = $request->getParsedBody();
519 $fc = $this->fields_config
;
524 foreach ($post['fields'] as $abs_pos => $field) {
525 if ($current_cat != $post[$field . '_category']) {
526 //reset position when category has changed
528 //set new current category
529 $current_cat = $post[$field . '_category'];
533 if (isset($post[$field . '_required'])) {
534 $required = $post[$field . '_required'];
539 $res[$current_cat][] = array(
540 'field_id' => $field,
541 'label' => htmlspecialchars($post[$field . '_label'], ENT_QUOTES
),
542 'category' => $post[$field . '_category'],
543 'visible' => $post[$field . '_visible'],
544 'required' => $required,
545 'width_in_forms' => $post[$field . '_width_in_forms']
549 //okay, we've got the new array, we send it to the
550 //Object that will store it in the database
551 $success = $fc->setFields($res);
552 FieldsCategories
::setCategories($this->zdb
, $post['categories']);
553 if ($success === true) {
554 $this->flash
->addMessage(
556 _T("Fields configuration has been successfully stored")
559 $this->flash
->addMessage(
561 _T("An error occurred while storing fields configuration :(")
567 ->withHeader('Location', $this->routeparser
->urlFor('configureCoreFields'));
571 * Core lists configuration page
573 * @param Request $request PSR Request
574 * @param Response $response PSR Response
575 * @param string $table Tbale name
579 public function configureListFields(Request
$request, Response
$response, string $table): Response
581 //TODO: check if type table exists
583 $lc = $this->lists_config
;
586 'page_title' => _T("Lists configuration"),
589 'listed_fields' => $lc->getListedFields(),
590 'remaining_fields' => $lc->getRemainingFields(),
591 'permissions' => $lc::getPermissionsList()
597 'pages/configuration_core_lists.html.twig',
604 * Process list fields configuration
606 * @param Request $request PSR Request
607 * @param Response $response PSR Response
611 public function storeListFields(Request
$request, Response
$response): Response
613 $post = $request->getParsedBody();
615 $lc = $this->lists_config
;
617 foreach ($post['fields'] as $field) {
618 $fields[] = $lc->getField($field);
620 $success = $lc->setListFields($fields);
622 if ($success === true) {
623 $this->flash
->addMessage(
625 _T("List configuration has been successfully stored")
628 $this->flash
->addMessage(
630 _T("An error occurred while storing list configuration :(")
636 ->withHeader('Location', $this->routeparser
->urlFor('configureListFields', $this->getArgs($request)));
642 * @param Request $request PSR Request
643 * @param Response $response PSR Response
647 public function reminders(Request
$request, Response
$response): Response
649 $texts = new Texts($this->preferences
, $this->routeparser
);
652 'impending' => $texts->getTexts('impendingduedate', $this->preferences
->pref_lang
),
653 'late' => $texts->getTexts('lateduedate', $this->preferences
->pref_lang
)
656 $members = new Members();
657 $reminders = $members->getRemindersCount();
662 'pages/reminder.html.twig',
664 'page_title' => _T("Reminders"),
665 'previews' => $previews,
666 'count_impending' => $reminders['impending'],
667 'count_impending_nomail' => $reminders['nomail']['impending'],
668 'count_late' => $reminders['late'],
669 'count_late_nomail' => $reminders['nomail']['late']
678 * @param Request $request PSR Request
679 * @param Response $response PSR Response
683 public function doReminders(Request
$request, Response
$response): Response
685 $error_detected = [];
686 $warning_detected = [];
687 $success_detected = [];
689 $post = $request->getParsedBody();
690 $texts = new Texts($this->preferences
, $this->routeparser
);
692 if (isset($post['reminders'])) {
693 $selected = $post['reminders'];
695 $reminders = new Reminders($selected);
698 $labels_members = array();
699 if (isset($post['reminder_wo_mail'])) {
703 $list_reminders = $reminders->getList($this->zdb
, $labels);
704 if (count($list_reminders) == 0) {
705 $warning_detected[] = _T("No reminder to send for now.");
707 foreach ($list_reminders as $reminder) {
708 if ($labels === false) {
711 ->setLogin($this->login
)
712 ->setPreferences($this->preferences
)
713 ->setRouteParser($this->routeparser
)
715 //send reminders by email
716 $sent = $reminder->send($texts, $this->history
, $this->zdb
);
718 if ($sent === true) {
719 $success_detected[] = $reminder->getMessage();
721 $error_detected[] = $reminder->getMessage();
724 //generate labels for members without email address
725 $labels_members[] = $reminder->member_id
;
729 if ($labels === true) {
730 if (count($labels_members) > 0) {
731 $session_var = 'filters_reminders_labels';
732 $labels_filters = new MembersList();
733 $labels_filters->selected
= $labels_members;
734 $this->session
->$session_var = $labels_filters;
739 $this->routeparser
->urlFor('pdf-members-labels') . '?session_var=' . $session_var
742 $error_detected[] = _T("There are no member to proceed.");
746 if (count($error_detected) > 0) {
749 _T("Reminder has not been sent:")
753 if (count($success_detected) > 0) {
756 _T("Sent reminders:")
761 //flash messages if any
762 if (count($error_detected) > 0) {
763 foreach ($error_detected as $error) {
764 $this->flash
->addMessage('error_detected', $error);
767 if (count($warning_detected) > 0) {
768 foreach ($warning_detected as $warning) {
769 $this->flash
->addMessage('warning_detected', $warning);
772 if (count($success_detected) > 0) {
773 foreach ($success_detected as $success) {
774 $this->flash
->addMessage('success_detected', $success);
780 ->withHeader('Location', $this->routeparser
->urlFor('reminders'));
786 * @param Request $request PSR Request
787 * @param Response $response PSR Response
788 * @param string $membership Either 'late' or 'nearly'
789 * @param string $mail Either 'withmail' or 'withoutmail'
793 public function filterReminders(Request
$request, Response
$response, string $membership, string $mail): Response
795 //always reset filters
796 $filters = new MembersList();
797 $filters->filter_account
= Members
::ACTIVE_ACCOUNT
;
799 $membership = ($membership === 'nearly' ?
800 Members
::MEMBERSHIP_NEARLY
: Members
::MEMBERSHIP_LATE
);
801 $filters->membership_filter
= $membership;
803 //TODO: filter on reminder may take care of parent email as well
804 $mail = ($mail === 'withmail' ?
805 Members
::FILTER_W_EMAIL
: Members
::FILTER_WO_EMAIL
);
806 $filters->email_filter
= $mail;
808 $this->session
->filter_members
= $filters;
812 ->withHeader('Location', $this->routeparser
->urlFor('members'));
816 * Direct document page
818 * @param Request $request PSR Request
819 * @param Response $response PSR Response
820 * @param string $hash Hash
824 public function documentLink(Request
$request, Response
$response, string $hash): Response
829 'pages/directlink.html.twig',
832 'page_title' => _T('Download document')
841 * @param Request $request PSR Request
842 * @param Response $response PSR Response
846 public function favicon(Request
$request, Response
$response): Response