3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette main controller
10 * Copyright © 2019-2020 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-2020 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
34 * @link http://galette.tuxfamily.org
35 * @since Available since 0.9.4dev - 2019-12-02
38 namespace Galette\Controllers
;
40 use Slim\Http\Request
;
41 use Slim\Http\Response
;
42 use Galette\Core\Logo
;
43 use Galette\Core\PrintLogo
;
44 use Galette\Core\GaletteMail
;
45 use Galette\Core\SysInfos
;
46 use Galette\Entity\Contribution
;
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\IO\PdfMembersCards
;
54 use Galette\IO\PdfContribution
;
55 use Galette\Repository\Members
;
56 use Galette\Repository\Reminders
;
60 * Galette main controller
62 * @category Controllers
63 * @name GaletteController
65 * @author Johan Cwiklinski <johan@x-tnd.be>
66 * @copyright 2019-2020 The Galette Team
67 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
68 * @link http://galette.tuxfamily.org
69 * @since Available since 0.9.4dev - 2019-12-02
72 class GaletteController
extends AbstractController
77 * @param Request $request PSR Request
78 * @param Response $response PSR Response
79 * @param array $args Request arguments
83 public function slash(Request
$request, Response
$response, array $args = []) :Response
85 return $this->galetteRedirect($request, $response, $args);
91 * @param Request $request PSR Request
92 * @param Response $response PSR Response
96 public function systemInformation(Request
$request, Response
$response) :Response
98 $sysinfos = new SysInfos();
99 $raw_infos = $sysinfos->getRawData(
110 'page_title' => _T("System information"),
111 'rawinfos' => $raw_infos
120 * @param Request $request PSR Request
121 * @param Response $response PSR Response
125 public function dashboard(Request
$request, Response
$response) :Response
127 $news = new News($this->preferences
->pref_rss_url
);
130 'page_title' => _T("Dashboard"),
131 'contentcls' => 'desktop',
132 'news' => $news->getPosts(),
133 'show_dashboard' => $_COOKIE['show_galette_dashboard']
136 $hide_telemetry = true;
137 if ($this->login
->isAdmin()) {
138 $telemetry = new \Galette\Util\
Telemetry(
143 $params['reguuid'] = $telemetry->getRegistrationUuid();
144 $params['telemetry_sent'] = $telemetry->isSent();
145 $params['registered'] = $telemetry->isRegistered();
147 $hide_telemetry = $telemetry->isSent() && $telemetry->isRegistered()
148 ||
isset($_COOKIE['hide_galette_telemetry']) && $_COOKIE['hide_galette_telemetry'];
150 $params['hide_telemetry'] = $hide_telemetry;
164 * @param Request $request PSR Request
165 * @param Response $response PSR Response
169 public function preferences(Request
$request, Response
$response) :Response
171 // flagging required fields
178 'pref_etiq_marges_v' => 1,
179 'pref_etiq_marges_h' => 1,
180 'pref_etiq_hspace' => 1,
181 'pref_etiq_vspace' => 1,
182 'pref_etiq_hsize' => 1,
183 'pref_etiq_vsize' => 1,
184 'pref_etiq_cols' => 1,
185 'pref_etiq_rows' => 1,
186 'pref_etiq_corps' => 1,
187 'pref_card_abrev' => 1,
188 'pref_card_strip' => 1,
189 'pref_card_marges_v' => 1,
190 'pref_card_marges_h' => 1,
191 'pref_card_hspace' => 1,
192 'pref_card_vspace' => 1
195 if ($this->login
->isSuperAdmin() && GALETTE_MODE
!== 'DEMO') {
196 $required['pref_admin_login'] = 1;
199 $prefs_fields = $this->preferences
->getFieldsNames();
201 foreach ($prefs_fields as $fieldname) {
202 $pref[$fieldname] = $this->preferences
->$fieldname;
205 //on error, user values are stored into session
206 if ($this->session
->entered_preferences
) {
207 $pref = array_merge($pref, $this->session
->entered_preferences
);
208 $this->session
->entered_preferences
= null;
211 //List available themes
213 $d = dir(GALETTE_THEMES_PATH
);
214 while (($entry = $d->read()) !== false) {
215 $full_entry = GALETTE_THEMES_PATH
. $entry;
218 && is_dir($full_entry)
219 && file_exists($full_entry.'/page.tpl')
227 $s = new Status($this->zdb
);
234 'page_title' => _T("Settings"),
235 'staff_members' => $m->getStaffMembersList(true),
238 'pref_numrows_options' => array(
244 'print_logo' => $this->print_logo
,
245 'required' => $required,
247 'statuts' => $s->getList(),
248 'accounts_options' => array(
249 Members
::ALL_ACCOUNTS
=> _T("All accounts"),
250 Members
::ACTIVE_ACCOUNT
=> _T("Active accounts"),
251 Members
::INACTIVE_ACCOUNT
=> _T("Inactive accounts")
261 * @param Request $request PSR Request
262 * @param Response $response PSR Response
266 public function storePreferences(Request
$request, Response
$response) :Response
268 $post = $request->getParsedBody();
269 $error_detected = [];
270 $warning_detected = [];
273 if (isset($post['valid']) && $post['valid'] == '1') {
274 if ($this->preferences
->check($post, $this->login
)) {
275 if (!$this->preferences
->store()) {
276 $error_detected[] = _T("An SQL error has occurred while storing preferences. Please try again, and contact the administrator if the problem persists.");
278 $this->flash
->addMessage(
280 _T("Preferences has been saved.")
283 $warning_detected = array_merge($warning_detected, $this->preferences
->checkCardsSizes());
286 if (GALETTE_MODE
!== 'DEMO' && isset($_FILES['logo'])) {
287 if ($_FILES['logo']['error'] === UPLOAD_ERR_OK
) {
288 if ($_FILES['logo']['tmp_name'] !='') {
289 if (is_uploaded_file($_FILES['logo']['tmp_name'])) {
290 $res = $this->logo
->store($_FILES['logo']);
292 $error_detected[] = $this->logo
->getErrorMessage($res);
294 $this->logo
= new Logo();
298 } elseif ($_FILES['logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
300 $this->logo
->getPhpErrorMessage($_FILES['logo']['error']),
303 $error_detected[] = $this->logo
->getPhpErrorMessage(
304 $_FILES['logo']['error']
309 if (GALETTE_MODE
!== 'DEMO' && isset($post['del_logo'])) {
310 if (!$this->logo
->delete()) {
311 $error_detected[] = _T("Delete failed");
313 $this->logo
= new Logo(); //get default Logo
318 if (GALETTE_MODE
!== 'DEMO' && isset($_FILES['card_logo'])) {
319 if ($_FILES['card_logo']['error'] === UPLOAD_ERR_OK
) {
320 if ($_FILES['card_logo']['tmp_name'] !='') {
321 if (is_uploaded_file($_FILES['card_logo']['tmp_name'])) {
322 $res = $this->print_logo
->store($_FILES['card_logo']);
324 $error_detected[] = $this->print_logo
->getErrorMessage($res);
326 $this->print_logo
= new PrintLogo();
330 } elseif ($_FILES['card_logo']['error'] !== UPLOAD_ERR_NO_FILE
) {
332 $this->print_logo
->getPhpErrorMessage($_FILES['card_logo']['error']),
335 $error_detected[] = $this->print_logo
->getPhpErrorMessage(
336 $_FILES['card_logo']['error']
341 if (GALETTE_MODE
!== 'DEMO' && isset($post['del_card_logo'])) {
342 if (!$this->print_logo
->delete()) {
343 $error_detected[] = _T("Delete failed");
345 $this->print_logo
= new PrintLogo();
349 $error_detected = $this->preferences
->getErrors();
352 if (count($error_detected) > 0) {
353 $this->session
->entered_preferences
= $post;
355 foreach ($error_detected as $error) {
356 $this->flash
->addMessage(
363 if (count($warning_detected) > 0) {
365 foreach ($warning_detected as $warning) {
366 $this->flash
->addMessage(
376 ->withHeader('Location', $this->router
->pathFor('preferences'));
380 * Test mail parameters
382 * @param Request $request PSR Request
383 * @param Response $response PSR Response
387 public function testEmail(Request
$request, Response
$response) :Response
390 if (!$this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
391 $this->flash
->addMessage(
393 _T("You asked Galette to send a test email, but email has been disabled in the preferences.")
396 $get = $request->getQueryParams();
397 $dest = (isset($get['adress']) ?
$get['adress'] : $this->preferences
->pref_email_newadh
);
398 if (GaletteMail
::isValidEmail($dest)) {
399 $mail = new GaletteMail($this->preferences
);
400 $mail->setSubject(_T('Test message'));
401 $mail->setRecipients(
403 $dest => _T("Galette admin")
406 $mail->setMessage(_T('Test message.'));
407 $sent = $mail->send();
410 $this->flash
->addMessage(
415 _T("An email has been sent to %email")
419 $this->flash
->addMessage(
424 _T("No email sent to %email")
429 $this->flash
->addMessage(
431 _T("Invalid email adress!")
436 if (!$request->isXhr()) {
439 ->withHeader('Location', $this->router
->pathFor('preferences'));
441 return $response->withJson(
452 * @param Request $request PSR Request
453 * @param Response $response PSR Response
457 public function charts(Request
$request, Response
$response) :Response
459 $charts = new Charts(
461 Charts
::MEMBERS_STATUS_PIE
,
462 Charts
::MEMBERS_STATEDUE_PIE
,
463 Charts
::CONTRIBS_TYPES_PIE
,
464 Charts
::COMPANIES_OR_NOT
,
465 Charts
::CONTRIBS_ALLTIME
474 'page_title' => _T("Charts"),
475 'charts' => $charts->getCharts(),
476 'require_charts' => true
483 * Core fields configuration page
485 * @param Request $request PSR Request
486 * @param Response $response PSR Response
490 public function configureCoreFields(Request
$request, Response
$response) :Response
492 $fc = $this->fields_config
;
495 'page_title' => _T("Fields configuration"),
497 'categories' => FieldsCategories
::getList($this->zdb
),
498 'categorized_fields' => $fc->getCategorizedFields(),
499 'non_required' => $fc->getNonRequired()
512 * Process core fields configuration
514 * @param Request $request PSR Request
515 * @param Response $response PSR Response
519 public function storeCoreFieldsConfig(Request
$request, Response
$response) :Response
521 $post = $request->getParsedBody();
522 $fc = $this->fields_config
;
527 foreach ($post['fields'] as $abs_pos => $field) {
528 if ($current_cat != $post[$field . '_category']) {
529 //reset position when category has changed
531 //set new current category
532 $current_cat = $post[$field . '_category'];
536 if (isset($post[$field . '_required'])) {
537 $required = $post[$field . '_required'];
542 $res[$current_cat][] = array(
543 'field_id' => $field,
544 'label' => $post[$field . '_label'],
545 'category' => $post[$field . '_category'],
546 'visible' => $post[$field . '_visible'],
547 'required' => $required
551 //okay, we've got the new array, we send it to the
552 //Object that will store it in the database
553 $success = $fc->setFields($res);
554 FieldsCategories
::setCategories($this->zdb
, $post['categories']);
555 if ($success === true) {
556 $this->flash
->addMessage(
558 _T("Fields configuration has been successfully stored")
561 $this->flash
->addMessage(
563 _T("An error occurred while storing fields configuration :(")
569 ->withHeader('Location', $this->router
->pathFor('configureCoreFields'));
573 * Core lists configuration page
575 * @param Request $request PSR Request
576 * @param Response $response PSR Response
577 * @param array $args Request arguments
581 public function configureListFields(Request
$request, Response
$response, array $args = []) :Response
583 $table = $args['table'];
584 //TODO: check if type table exists
586 $lc = $this->lists_config
;
589 'page_title' => _T("Lists configuration"),
592 'listed_fields' => $lc->getListedFields(),
593 'remaining_fields' => $lc->getRemainingFields()
606 * Process list fields configuration
608 * @param Request $request PSR Request
609 * @param Response $response PSR Response
610 * @param array $args Request arguments
614 public function storeListFields(Request
$request, Response
$response, array $args = []) :Response
616 $post = $request->getParsedBody();
618 $lc = $this->lists_config
;
620 foreach ($post['fields'] as $field) {
621 $fields[] = $lc->getField($field);
623 $success = $lc->setListFields($fields);
625 if ($success === true) {
626 $this->flash
->addMessage(
628 _T("List configuration has been successfully stored")
631 $this->flash
->addMessage(
633 _T("An error occurred while storing list configuration :(")
639 ->withHeader('Location', $this->router
->pathFor('configureListFields', $args));
645 * @param Request $request PSR Request
646 * @param Response $response PSR Response
650 public function fakeData(Request
$request, Response
$response) :Response
653 'page_title' => _T('Generate fake data'),
654 'number_members' => \Galette\Util\FakeData
::DEFAULT_NB_MEMBERS
,
655 'number_contrib' => \Galette\Util\FakeData
::DEFAULT_NB_CONTRIB
,
656 'number_groups' => \Galette\Util\FakeData
::DEFAULT_NB_GROUPS
,
657 'number_transactions' => \Galette\Util\FakeData
::DEFAULT_NB_TRANSACTIONS
,
658 'photos' => \Galette\Util\FakeData
::DEFAULT_PHOTOS
673 * @param Request $request PSR Request
674 * @param Response $response PSR Response
678 public function doFakeData(Request
$request, Response
$response) :Response
680 $post = $request->getParsedBody();
682 $fakedata = new \Galette\Util\
FakeData($this->zdb
, $this->i18n
);
684 $fakedata->setDependencies(
686 $this->members_fields
,
692 ->setNbMembers($post['number_members'])
693 ->setNbGroups($post['number_groups'])
694 ->setNbTransactions($post['number_transactions'])
695 ->setMaxContribs($post['number_contrib'])
696 ->setWithPhotos(isset($post['photos']));
698 $fakedata->generate();
700 $report = $fakedata->getReport();
702 foreach ($report['success'] as $success) {
703 $this->flash
->addMessage(
709 foreach ($report['errors'] as $error) {
710 $this->flash
->addMessage(
716 foreach ($report['warnings'] as $warning) {
717 $this->flash
->addMessage(
725 ->withHeader('Location', $this->router
->pathFor('slash'));
731 * @param Request $request PSR Request
732 * @param Response $response PSR Response
736 public function reminders(Request
$request, Response
$response) :Response
738 $texts = new Texts($this->preferences
, $this->router
);
741 'impending' => $texts->getTexts('impendingduedate', $this->preferences
->pref_lang
),
742 'late' => $texts->getTexts('lateduedate', $this->preferences
->pref_lang
)
745 $members = new Members();
746 $reminders = $members->getRemindersCount();
753 'page_title' => _T("Reminders"),
754 'previews' => $previews,
755 'count_impending' => $reminders['impending'],
756 'count_impending_nomail' => $reminders['nomail']['impending'],
757 'count_late' => $reminders['late'],
758 'count_late_nomail' => $reminders['nomail']['late']
767 * @param Request $request PSR Request
768 * @param Response $response PSR Response
772 public function doReminders(Request
$request, Response
$response) :Response
774 $error_detected = [];
775 $warning_detected = [];
776 $success_detected = [];
778 $post = $request->getParsedBody();
779 $texts = new Texts($this->preferences
, $this->router
);
781 if (isset($post['reminders'])) {
782 $selected = $post['reminders'];
784 $reminders = new Reminders($selected);
787 $labels_members = array();
788 if (isset($post['reminder_wo_mail'])) {
792 $list_reminders = $reminders->getList($this->zdb
, $labels);
793 if (count($list_reminders) == 0) {
794 $warning_detected[] = _T("No reminder to send for now.");
796 foreach ($list_reminders as $reminder) {
797 if ($labels === false) {
798 //send reminders by email
799 $sent = $reminder->send($texts, $this->history
, $this->zdb
);
801 if ($sent === true) {
802 $success_detected[] = $reminder->getMessage();
804 $error_detected[] = $reminder->getMessage();
807 //generate labels for members without email address
808 $labels_members[] = $reminder->member_id
;
812 if ($labels === true) {
813 if (count($labels_members) > 0) {
814 $session_var = 'filters_reminders_labels';
815 $labels_filters = new MembersList();
816 $labels_filters->selected
= $labels_members;
817 $this->session
->$session_var = $labels_filters;
822 $this->router
->pathFor('pdf-member-labels') . '?session_var=' . $session_var
825 $error_detected[] = _T("There are no member to proceed.");
829 if (count($error_detected) > 0) {
832 _T("Reminder has not been sent:")
836 if (count($success_detected) > 0) {
839 _T("Sent reminders:")
844 //flash messages if any
845 if (count($error_detected) > 0) {
846 foreach ($error_detected as $error) {
847 $this->flash
->addMessage('error_detected', $error);
850 if (count($warning_detected) > 0) {
851 foreach ($warning_detected as $warning) {
852 $this->flash
->addMessage('warning_detected', $warning);
855 if (count($success_detected) > 0) {
856 foreach ($success_detected as $success) {
857 $this->flash
->addMessage('success_detected', $success);
863 ->withHeader('Location', $this->router
->pathFor('reminders'));
869 * @param Request $request PSR Request
870 * @param Response $response PSR Response
871 * @param array $args Request arguments
875 public function filterReminders(Request
$request, Response
$response, array $args = []) :Response
877 //always reset filters
878 $filters = new MembersList();
879 $filters->filter_account
= Members
::ACTIVE_ACCOUNT
;
881 $membership = ($args['membership'] === 'nearly' ?
882 Members
::MEMBERSHIP_NEARLY
:
883 Members
::MEMBERSHIP_LATE
);
884 $filters->membership_filter
= $membership;
886 //TODO: filter on reminder may take care of parent email as well
887 $mail = ($args['mail'] === 'withmail' ?
888 Members
::FILTER_W_EMAIL
:
889 Members
::FILTER_WO_EMAIL
);
890 $filters->email_filter
= $mail;
892 $this->session
->filter_members
= $filters;
896 ->withHeader('Location', $this->router
->pathFor('members'));
900 * Direct document page
902 * @param Request $request PSR Request
903 * @param Response $response PSR Response
904 * @param array $args Request arguments
908 public function documentLink(Request
$request, Response
$response, array $args = []) :Response
915 'hash' => $args['hash'],
916 'page_title' => _T('Download document')