3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette main controller
10 * Copyright © 2019-2021 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-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
37 namespace Galette\Controllers
;
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
;
52 use Galette\IO\Charts
;
53 use Galette\Repository\Members
;
54 use Galette\Repository\Reminders
;
58 * Galette main controller
60 * @category Controllers
61 * @name GaletteController
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
70 class GaletteController
extends AbstractController
75 * @param Request $request PSR Request
76 * @param Response $response PSR Response
80 public function slash(Request
$request, Response
$response): Response
82 return $this->galetteRedirect($request, $response);
88 * @param Request $request PSR Request
89 * @param Response $response PSR Response
93 public function systemInformation(Request
$request, Response
$response): Response
95 $sysinfos = new SysInfos();
96 $raw_infos = $sysinfos->getRawData(
107 'page_title' => _T("System information"),
108 'rawinfos' => $raw_infos
117 * @param Request $request PSR Request
118 * @param Response $response PSR Response
122 public function dashboard(Request
$request, Response
$response): Response
124 $news = new News($this->preferences
->pref_rss_url
);
127 'page_title' => _T("Dashboard"),
128 'contentcls' => 'desktop',
129 'news' => $news->getPosts(),
130 'show_dashboard' => $_COOKIE['show_galette_dashboard']
133 $hide_telemetry = true;
134 if ($this->login
->isAdmin()) {
135 $telemetry = new \Galette\Util\
Telemetry(
140 $params['reguuid'] = $telemetry->getRegistrationUuid();
141 $params['telemetry_sent'] = $telemetry->isSent();
142 $params['registered'] = $telemetry->isRegistered();
144 $hide_telemetry = $telemetry->isSent() && $telemetry->isRegistered()
145 ||
isset($_COOKIE['hide_galette_telemetry']) && $_COOKIE['hide_galette_telemetry'];
147 $params['hide_telemetry'] = $hide_telemetry;
161 * @param Request $request PSR Request
162 * @param Response $response PSR Response
166 public function preferences(Request
$request, Response
$response): Response
168 // flagging required fields
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
192 if ($this->login
->isSuperAdmin() && GALETTE_MODE
!== Galette
::MODE_DEMO
) {
193 $required['pref_admin_login'] = 1;
196 $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.tpl')
224 //List payment types for default to be selected
225 $ptypes = new PaymentTypes(
230 $ptlist = $ptypes->getList();
233 $s = new Status($this->zdb
);
240 'page_title' => _T("Settings"),
241 'staff_members' => $m->getStaffMembersList(true),
244 'pref_numrows_options' => array(
250 'print_logo' => $this->print_logo
,
251 'required' => $required,
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")
259 'paymenttypes' => $ptlist
268 * @param Request $request PSR Request
269 * @param Response $response PSR Response
273 public function storePreferences(Request
$request, Response
$response): Response
275 $post = $request->getParsedBody();
276 $error_detected = [];
277 $warning_detected = [];
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.");
285 $this->flash
->addMessage(
287 _T("Preferences has been saved.")
290 $warning_detected = array_merge($warning_detected, $this->preferences
->checkCardsSizes());
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']);
299 $error_detected[] = $this->logo
->getErrorMessage($res);
301 $this->logo
= new Logo();
305 } elseif ($_FILES['logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
307 $this->logo
->getPhpErrorMessage($_FILES['logo']['error']),
310 $error_detected[] = $this->logo
->getPhpErrorMessage(
311 $_FILES['logo']['error']
316 if (GALETTE_MODE
!== Galette
::MODE_DEMO
&& isset($post['del_logo'])) {
317 if (!$this->logo
->delete()) {
318 $error_detected[] = _T("Delete failed");
320 $this->logo
= new Logo(); //get default Logo
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']);
331 $error_detected[] = $this->print_logo
->getErrorMessage($res);
333 $this->print_logo
= new PrintLogo();
337 } elseif ($_FILES['card_logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
339 $this->print_logo
->getPhpErrorMessage($_FILES['card_logo']['error']),
342 $error_detected[] = $this->print_logo
->getPhpErrorMessage(
343 $_FILES['card_logo']['error']
348 if (GALETTE_MODE
!== Galette
::MODE_DEMO
&& isset($post['del_card_logo'])) {
349 if (!$this->print_logo
->delete()) {
350 $error_detected[] = _T("Delete failed");
352 $this->print_logo
= new PrintLogo();
356 $error_detected = $this->preferences
->getErrors();
359 if (count($error_detected) > 0) {
360 $this->session
->entered_preferences
= $post;
362 foreach ($error_detected as $error) {
363 $this->flash
->addMessage(
370 if (count($warning_detected) > 0) {
372 foreach ($warning_detected as $warning) {
373 $this->flash
->addMessage(
383 ->withHeader('Location', $this->router
->pathFor('preferences'));
387 * Test mail parameters
389 * @param Request $request PSR Request
390 * @param Response $response PSR Response
394 public function testEmail(Request
$request, Response
$response): Response
397 if (!$this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
398 $this->flash
->addMessage(
400 _T("You asked Galette to send a test email, but email has been disabled in the preferences.")
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(
410 $dest => _T("Galette admin")
413 $mail->setMessage(_T('Test message.'));
414 $sent = $mail->send();
417 $this->flash
->addMessage(
422 _T("An email has been sent to %email")
426 $this->flash
->addMessage(
431 _T("No email sent to %email")
436 $this->flash
->addMessage(
438 _T("Invalid email adress!")
443 if (!$request->isXhr()) {
446 ->withHeader('Location', $this->router
->pathFor('preferences'));
448 return $response->withJson(
459 * @param Request $request PSR Request
460 * @param Response $response PSR Response
464 public function charts(Request
$request, Response
$response): Response
466 $charts = new Charts(
468 Charts
::MEMBERS_STATUS_PIE
,
469 Charts
::MEMBERS_STATEDUE_PIE
,
470 Charts
::CONTRIBS_TYPES_PIE
,
471 Charts
::COMPANIES_OR_NOT
,
472 Charts
::CONTRIBS_ALLTIME
481 'page_title' => _T("Charts"),
482 'charts' => $charts->getCharts(),
483 'require_charts' => true
490 * Core fields configuration page
492 * @param Request $request PSR Request
493 * @param Response $response PSR Response
497 public function configureCoreFields(Request
$request, Response
$response): Response
499 $fc = $this->fields_config
;
502 'page_title' => _T("Fields configuration"),
504 'categories' => FieldsCategories
::getList($this->zdb
),
505 'categorized_fields' => $fc->getCategorizedFields(),
506 'non_required' => $fc->getNonRequired()
519 * Process core fields configuration
521 * @param Request $request PSR Request
522 * @param Response $response PSR Response
526 public function storeCoreFieldsConfig(Request
$request, Response
$response): Response
528 $post = $request->getParsedBody();
529 $fc = $this->fields_config
;
534 foreach ($post['fields'] as $abs_pos => $field) {
535 if ($current_cat != $post[$field . '_category']) {
536 //reset position when category has changed
538 //set new current category
539 $current_cat = $post[$field . '_category'];
543 if (isset($post[$field . '_required'])) {
544 $required = $post[$field . '_required'];
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
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(
565 _T("Fields configuration has been successfully stored")
568 $this->flash
->addMessage(
570 _T("An error occurred while storing fields configuration :(")
576 ->withHeader('Location', $this->router
->pathFor('configureCoreFields'));
580 * Core lists configuration page
582 * @param Request $request PSR Request
583 * @param Response $response PSR Response
584 * @param string $table Tbale name
588 public function configureListFields(Request
$request, Response
$response, string $table): Response
590 //TODO: check if type table exists
592 $lc = $this->lists_config
;
595 'page_title' => _T("Lists configuration"),
598 'listed_fields' => $lc->getListedFields(),
599 'remaining_fields' => $lc->getRemainingFields()
612 * Process list fields configuration
614 * @param Request $request PSR Request
615 * @param Response $response PSR Response
619 public function storeListFields(Request
$request, Response
$response): Response
621 $post = $request->getParsedBody();
623 $lc = $this->lists_config
;
625 foreach ($post['fields'] as $field) {
626 $fields[] = $lc->getField($field);
628 $success = $lc->setListFields($fields);
630 if ($success === true) {
631 $this->flash
->addMessage(
633 _T("List configuration has been successfully stored")
636 $this->flash
->addMessage(
638 _T("An error occurred while storing list configuration :(")
644 ->withHeader('Location', $this->router
->pathFor('configureListFields', $this->getArgs($request)));
650 * @param Request $request PSR Request
651 * @param Response $response PSR Response
655 public function reminders(Request
$request, Response
$response): Response
657 $texts = new Texts($this->preferences
, $this->router
);
660 'impending' => $texts->getTexts('impendingduedate', $this->preferences
->pref_lang
),
661 'late' => $texts->getTexts('lateduedate', $this->preferences
->pref_lang
)
664 $members = new Members();
665 $reminders = $members->getRemindersCount();
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']
686 * @param Request $request PSR Request
687 * @param Response $response PSR Response
691 public function doReminders(Request
$request, Response
$response): Response
693 $error_detected = [];
694 $warning_detected = [];
695 $success_detected = [];
697 $post = $request->getParsedBody();
698 $texts = new Texts($this->preferences
, $this->router
);
700 if (isset($post['reminders'])) {
701 $selected = $post['reminders'];
703 $reminders = new Reminders($selected);
706 $labels_members = array();
707 if (isset($post['reminder_wo_mail'])) {
711 $list_reminders = $reminders->getList($this->zdb
, $labels);
712 if (count($list_reminders) == 0) {
713 $warning_detected[] = _T("No reminder to send for now.");
715 foreach ($list_reminders as $reminder) {
716 if ($labels === false) {
719 ->setLogin($this->login
)
720 ->setPreferences($this->preferences
)
721 ->setRouter($this->router
)
723 //send reminders by email
724 $sent = $reminder->send($texts, $this->history
, $this->zdb
);
726 if ($sent === true) {
727 $success_detected[] = $reminder->getMessage();
729 $error_detected[] = $reminder->getMessage();
732 //generate labels for members without email address
733 $labels_members[] = $reminder->member_id
;
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;
747 $this->router
->pathFor('pdf-members-labels') . '?session_var=' . $session_var
750 $error_detected[] = _T("There are no member to proceed.");
754 if (count($error_detected) > 0) {
757 _T("Reminder has not been sent:")
761 if (count($success_detected) > 0) {
764 _T("Sent reminders:")
769 //flash messages if any
770 if (count($error_detected) > 0) {
771 foreach ($error_detected as $error) {
772 $this->flash
->addMessage('error_detected', $error);
775 if (count($warning_detected) > 0) {
776 foreach ($warning_detected as $warning) {
777 $this->flash
->addMessage('warning_detected', $warning);
780 if (count($success_detected) > 0) {
781 foreach ($success_detected as $success) {
782 $this->flash
->addMessage('success_detected', $success);
788 ->withHeader('Location', $this->router
->pathFor('reminders'));
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'
801 public function filterReminders(Request
$request, Response
$response, string $membership, string $mail): Response
803 //always reset filters
804 $filters = new MembersList();
805 $filters->filter_account
= Members
::ACTIVE_ACCOUNT
;
807 $membership = ($membership === 'nearly' ?
808 Members
::MEMBERSHIP_NEARLY
: Members
::MEMBERSHIP_LATE
);
809 $filters->membership_filter
= $membership;
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;
816 $this->session
->filter_members
= $filters;
820 ->withHeader('Location', $this->router
->pathFor('members'));
824 * Direct document page
826 * @param Request $request PSR Request
827 * @param Response $response PSR Response
828 * @param string $hash Hash
832 public function documentLink(Request
$request, Response
$response, string $hash): Response
840 'page_title' => _T('Download document')