3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette members 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/>.
27 * @category Controllers
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\Crud
;
40 use Galette\Controllers\CrudController
;
41 use Slim\Http\Request
;
42 use Slim\Http\Response
;
43 use Galette\Core\Authentication
;
44 use Galette\Core\GaletteMail
;
45 use Galette\Core\Password
;
46 use Galette\Core\PasswordImage
;
47 use Galette\Core\Picture
;
48 use Galette\Entity\Adherent
;
49 use Galette\Entity\Contribution
;
50 use Galette\Entity\ContributionsTypes
;
51 use Galette\Entity\DynamicFieldsHandle
;
52 use Galette\Entity\Group
;
53 use Galette\Entity\Status
;
54 use Galette\Entity\FieldsConfig
;
55 use Galette\Entity\Texts
;
56 use Galette\Filters\AdvancedMembersList
;
57 use Galette\Filters\MembersList
;
59 use Galette\IO\MembersCsv
;
60 use Galette\Repository\Groups
;
61 use Galette\Repository\Members
;
62 use Galette\Repository\PaymentTypes
;
63 use Galette\Repository\Titles
;
67 * Galette members controller
69 * @category Controllers
70 * @name GaletteController
72 * @author Johan Cwiklinski <johan@x-tnd.be>
73 * @copyright 2019-2020 The Galette Team
74 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
75 * @link http://galette.tuxfamily.org
76 * @since Available since 0.9.4dev - 2019-12-02
79 class MembersController
extends CrudController
86 * @param Request $request PSR Request
87 * @param Response $response PSR Response
88 * @param array $args Request arguments
92 public function add(Request
$request, Response
$response, array $args = []): Response
94 return $this->edit($request, $response, $args);
98 * Self subscription page
100 * @param Request $request PSR Request
101 * @param Response $response PSR Response
102 * @param array $args Request arguments
106 public function selfSubscribe(Request
$request, Response
$response, array $args = []): Response
108 if (!$this->preferences
->pref_bool_selfsubscribe ||
$this->login
->isLogged()) {
111 ->withHeader('Location', $this->router
->pathFor('slash'));
114 if ($this->session
->member
!== null) {
115 $member = $this->session
->member
;
116 $this->session
->member
= null;
121 $member = new Adherent($this->zdb
, null, $deps);
124 //mark as self membership
125 $member->setSelfMembership();
127 // flagging required fields
128 $fc = $this->fields_config
;
129 $form_elements = $fc->getFormElements($this->login
, true, true);
131 //image to defeat mass filling forms
132 $spam = new PasswordImage();
133 $spam_pass = $spam->newImage();
134 $spam_img = $spam->getImage();
138 $members = $m->getSelectizedMembers(
140 $member->hasParent() ?
$member->parent
->id
: null
145 'filters' => $m->getFilters(),
146 'count' => $m->getCount()
150 if (count($members)) {
151 $params['members']['list'] = $members;
159 'page_title' => _T("Subscription"),
160 'parent_tpl' => 'public_page.tpl',
163 'autocomplete' => true,
166 'titles_list' => Titles
::getList($this->zdb
),
168 'spam_pass' => $spam_pass,
169 'spam_img' => $spam_img,
170 'fieldsets' => $form_elements['fieldsets'],
171 'hidden_elements' => $form_elements['hiddens']
180 * @param Request $request PSR Request
181 * @param Response $response PSR Response
182 * @param array $args Request arguments
186 public function doAdd(Request
$request, Response
$response, array $args = []): Response
188 return $this->store($request, $response, $args);
194 * @param Request $request PSR Request
195 * @param Response $response PSR Response
196 * @param array $args Request arguments
200 public function duplicate(Request
$request, Response
$response, array $args = []): Response
202 $id_adh = (int)$args[Adherent
::PK
];
203 $adh = new Adherent($this->zdb
, $id_adh, ['dynamics' => true, 'parent' => true]);
204 $adh->setDuplicate();
206 //store entity in session
207 $this->session
->member
= $adh;
211 ->withHeader('Location', $this->router
->pathFor('editmember', ['action' => 'add']));
218 * Display member card
220 * @param Request $request PSR Request
221 * @param Response $response PSR Response
222 * @param array $args Request arguments
226 public function show(Request
$request, Response
$response, array $args): Response
228 $id = (int)$args['id'];
238 $member = new Adherent($this->zdb
, $id, $deps);
240 if (!$member->canEdit($this->login
)) {
241 $this->flash
->addMessage(
243 _T("You do not have permission for requested URL.")
250 $this->router
->pathFor('me')
254 if ($member->id
== null) {
255 //member does not exists!
256 $this->flash
->addMessage(
258 str_replace('%id', $args['id'], _T("No member #%id."))
265 $this->router
->pathFor('slash')
269 // flagging fields visibility
270 $fc = $this->fields_config
;
271 $display_elements = $fc->getDisplayElements($this->login
);
278 'page_title' => _T("Member Profile"),
280 'pref_lang' => $this->i18n
->getNameFromId($member->language
),
281 'pref_card_self' => $this->preferences
->pref_card_self
,
282 'groups' => Groups
::getSimpleList(),
284 'display_elements' => $display_elements
293 * @param Request $request PSR Request
294 * @param Response $response PSR Response
295 * @param array $args Request arguments
299 public function showMe(Request
$request, Response
$response, array $args = []): Response
301 if ($this->login
->isSuperAdmin()) {
304 ->withHeader('Location', $this->router
->pathFor('slash'));
306 $args['show_me'] = true;
307 $args['id'] = $this->login
->id
;
308 return $this->show($request, $response, $args);
312 * Public pages (trombinoscope, public list)
314 * @param Request $request PSR Request
315 * @param Response $response PSR Response
316 * @param array $args Request arguments
320 public function publicList(Request
$request, Response
$response, array $args = []): Response
323 $type = $args['type'];
324 if (isset($args['option'])) {
325 $option = $args['option'];
328 if (isset($args['value'])) {
329 $value = $args['value'];
332 $varname = 'public_filter_' . $type;
333 if (isset($this->session
->$varname)) {
334 $filters = $this->session
->$varname;
336 $filters = new MembersList();
339 if ($option !== null) {
342 $filters->current_page
= (int)$value;
345 $filters->orderby
= $value;
350 $m = new Members($filters);
351 $members = $m->getPublicList($type === 'trombi');
353 $this->session
->$varname = $filters;
355 //assign pagination variables to the template and add pagination links
356 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
361 ($type === 'list' ?
'liste_membres' : 'trombinoscope') . '.tpl',
363 'page_title' => ($type === 'list' ?
_T("Members list") : _T('Trombinoscope')),
364 'additionnal_html_class' => ($type === 'list' ?
'' : 'trombinoscope'),
366 'members' => $members,
367 'nb_members' => $m->getCount(),
368 'filters' => $filters
375 * Public pages (trombinoscope, public list)
377 * @param Request $request PSR Request
378 * @param Response $response PSR Response
379 * @param array $args Request arguments
383 public function filterPublicList(Request
$request, Response
$response, array $args = []): Response
385 $type = $args['type'];
386 $post = $request->getParsedBody();
388 $varname = 'public_filter_' . $type;
389 if (isset($this->session
->$varname)) {
390 $filters = $this->session
->$varname;
392 $filters = new MembersList();
395 //reintialize filters
396 if (isset($post['clear_filter'])) {
399 //number of rows to show
400 if (isset($post['nbshow'])) {
401 $filters->show
= $post['nbshow'];
405 $this->session
->$varname = $filters;
409 ->withHeader('Location', $this->router
->pathFor('publicList', ['type' => $type]));
415 * @param Request $request PSR Request
416 * @param Response $response PSR Response
417 * @param array $args Request arguments
421 public function getDynamicFile(Request
$request, Response
$response, array $args): Response
423 $id = (int)$args['id'];
432 $member = new Adherent($this->zdb
, $id, $deps);
435 if (!$member->canEdit($this->login
)) {
436 $fields = $member->getDynamicFields()->getFields();
437 if (!isset($fields[$args['fid']])) {
438 //field does not exists or access is forbidden
445 if ($denied === true) {
446 $this->flash
->addMessage(
448 _T("You do not have permission for requested URL.")
455 $this->router
->pathFor(
462 $filename = str_replace(
473 'member_%mid_field_%fid_value_%pos'
476 if (file_exists(GALETTE_FILES_PATH
. $filename)) {
477 $type = File
::getMimeType(GALETTE_FILES_PATH
. $filename);
478 $response = $response
479 ->withHeader('Content-Type', $type)
480 ->withHeader('Content-Disposition', 'attachment;filename="' . $args['name'] . '"')
481 ->withHeader('Pragma', 'no-cache');
482 $response->write(readfile(GALETTE_FILES_PATH
. $filename));
486 'A request has been made to get an exported file named `' .
487 $filename . '` that does not exists.',
491 $this->flash
->addMessage(
493 _T("The file does not exists or cannot be read :(")
500 $this->router
->pathFor('member', ['id' => $args['id']])
508 * @param Request $request PSR Request
509 * @param Response $response PSR Response
510 * @param array $args Request arguments
514 public function list(Request
$request, Response
$response, array $args = []): Response
516 $option = $args['option'] ??
null;
517 $value = $args['value'] ??
null;
519 if (isset($this->session
->filter_members
)) {
520 $filters = $this->session
->filter_members
;
522 $filters = new MembersList();
525 if ($option !== null) {
528 $filters->current_page
= (int)$value;
531 $filters->orderby
= $value;
536 $members = new Members($filters);
538 $members_list = array();
539 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
540 $members_list = $members->getMembersList(true);
542 $members_list = $members->getManagedMembersList(true);
545 $groups = new Groups($this->zdb
, $this->login
);
546 $groups_list = $groups->getList();
548 //assign pagination variables to the template and add pagination links
549 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
550 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
552 $this->session
->filter_members
= $filters;
557 'gestion_adherents.tpl',
559 'page_title' => _T("Members management"),
560 'require_mass' => true,
561 'members' => $members_list,
562 'filter_groups_options' => $groups_list,
563 'nb_members' => $members->getCount(),
564 'filters' => $filters,
565 'adv_filters' => $filters instanceof AdvancedMembersList
,
566 'galette_list' => $this->lists_config
->getDisplayElements($this->login
)
575 * @param Request $request PSR Request
576 * @param Response $response PSR Response
580 public function filter(Request
$request, Response
$response): Response
582 $post = $request->getParsedBody();
583 if (isset($this->session
->filter_members
)) {
584 //CAUTION: this one may be simple or advanced, display must change
585 $filters = $this->session
->filter_members
;
587 $filters = new MembersList();
590 //reintialize filters
591 if (isset($post['clear_filter'])) {
592 $filters = new MembersList();
593 } elseif (isset($post['clear_adv_filter'])) {
594 $this->session
->filter_members
= null;
595 unset($this->session
->filter_members
);
599 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
600 } elseif (isset($post['adv_criteria'])) {
603 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
606 if (isset($post['filter_str'])) { //filter search string
607 $filters->filter_str
= stripslashes(
608 htmlspecialchars($post['filter_str'], ENT_QUOTES
)
612 if (isset($post['field_filter'])) {
613 if (is_numeric($post['field_filter'])) {
614 $filters->field_filter
= $post['field_filter'];
617 //membership to filter
618 if (isset($post['membership_filter'])) {
619 if (is_numeric($post['membership_filter'])) {
620 $filters->membership_filter
621 = $post['membership_filter'];
624 //account status to filter
625 if (isset($post['filter_account'])) {
626 if (is_numeric($post['filter_account'])) {
627 $filters->filter_account
= $post['filter_account'];
631 if (isset($post['email_filter'])) {
632 $filters->email_filter
= (int)$post['email_filter'];
636 isset($post['group_filter'])
637 && $post['group_filter'] > 0
639 $filters->group_filter
= (int)$post['group_filter'];
641 //number of rows to show
642 if (isset($post['nbshow'])) {
643 $filters->show
= $post['nbshow'];
646 if (isset($post['advanced_filtering'])) {
647 if (!$filters instanceof AdvancedMembersList
) {
648 $filters = new AdvancedMembersList($filters);
652 unset($post['advanced_filtering']);
654 foreach ($post as $k => $v) {
655 if (strpos($k, 'free_', 0) === 0) {
658 foreach ($post['free_field'] as $f) {
661 && trim($post['free_text'][$i]) !== ''
663 $fs_search = $post['free_text'][$i];
665 = (int)$post['free_logical_operator'][$i];
667 = (int)$post['free_query_operator'][$i];
668 $type = (int)$post['free_type'][$i];
673 'search' => $fs_search,
677 $filters->free_search
= $fs;
683 } elseif ($k == 'groups_search') {
685 $filters->groups_search_log_op
= (int)$post['groups_logical_operator'];
686 foreach ($post['groups_search'] as $g) {
687 if (trim($g) !== '') {
692 $filters->groups_search
= $gs;
698 case 'contrib_min_amount':
699 case 'contrib_max_amount':
700 if (trim($v) !== '') {
713 if (isset($post['savesearch'])) {
718 $this->router
->pathFor(
725 $this->session
->filter_members
= $filters;
729 ->withHeader('Location', $this->router
->pathFor('members'));
733 * Advanced search page
735 * @param Request $request PSR Request
736 * @param Response $response PSR Response
740 public function advancedSearch(Request
$request, Response
$response): Response
742 if (isset($this->session
->filter_members
)) {
743 $filters = $this->session
->filter_members
;
744 if (!$filters instanceof AdvancedMembersList
) {
745 $filters = new AdvancedMembersList($filters);
748 $filters = new AdvancedMembersList();
751 $groups = new Groups($this->zdb
, $this->login
);
752 $groups_list = $groups->getList();
754 //we want only visibles fields
755 $fields = $this->members_fields
;
756 $fc = $this->fields_config
;
757 $visibles = $fc->getVisibilities();
758 $access_level = $this->login
->getAccessLevel();
760 //remove not searchable fields
761 unset($fields['mdp_adh']);
763 foreach ($fields as $k => $f) {
765 $visibles[$k] == FieldsConfig
::NOBODY ||
766 ($visibles[$k] == FieldsConfig
::ADMIN
&&
767 $access_level < Authentication
::ACCESS_ADMIN
) ||
768 ($visibles[$k] == FieldsConfig
::STAFF
&&
769 $access_level < Authentication
::ACCESS_STAFF
) ||
770 ($visibles[$k] == FieldsConfig
::MANAGER
&&
771 $access_level < Authentication
::ACCESS_MANAGER
)
777 //add status label search
778 if ($pos = array_search(Status
::PK
, array_keys($fields))) {
779 $fields = array_slice($fields, 0, $pos, true) +
780 ['status_label' => ['label' => _T('Status label')]] +
781 array_slice($fields, $pos, count($fields) - 1, true);
793 $member = new Adherent($this->zdb
, $this->login
->login
, $deps);
794 $adh_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $member);
796 $contrib = new Contribution($this->zdb
, $this->login
);
797 $contrib_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $contrib);
800 $statuts = new Status($this->zdb
);
802 //Contributions types
803 $ct = new ContributionsTypes($this->zdb
);
806 $ptypes = new PaymentTypes(
811 $ptlist = $ptypes->getList();
813 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
818 'advanced_search.tpl',
820 'page_title' => _T("Advanced search"),
821 'filter_groups_options' => $groups_list,
822 'search_fields' => $fields,
823 'adh_dynamics' => $adh_dynamics->getFields(),
824 'contrib_dynamics' => $contrib_dynamics->getFields(),
825 'statuts' => $statuts->getList(),
826 'contributions_types' => $ct->getList(),
827 'filters' => $filters,
828 'payments_types' => $ptlist
835 * Members list for ajax
837 * @param Request $request PSR Request
838 * @param Response $response PSR Response
839 * @param array $args Request arguments
843 public function ajaxList(Request
$request, Response
$response, array $args = []): Response
845 $post = $request->getParsedBody();
847 if (isset($this->session
->ajax_members_filters
)) {
848 $filters = $this->session
->ajax_members_filters
;
850 $filters = new MembersList();
853 if (isset($args['option']) && $args['option'] == 'page') {
854 $filters->current_page
= (int)$args['value'];
857 //numbers of rows to display
858 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
859 $filters->show
= $post['nbshow'];
862 $members = new Members($filters);
863 if (!$this->login
->isAdmin() && !$this->login
->isStaff()) {
864 if ($this->login
->isGroupManager()) {
865 $members_list = $members->getManagedMembersList(true);
870 [$this->login
->id
, $this->login
->login
],
871 'Trying to list group members without access from #%id (%login)'
875 throw new \
Exception('Access denied.');
878 $members_list = $members->getMembersList(true);
881 //assign pagination variables to the template and add pagination links
882 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
884 $this->session
->ajax_members_filters
= $filters;
886 $selected_members = null;
887 $unreachables_members = null;
888 if (!isset($post['from'])) {
889 $mailing = $this->session
->mailing
;
890 if (!isset($post['members'])) {
891 $selected_members = $mailing->recipients
;
892 $unreachables_members = $mailing->unreachables
;
895 $selected_members = $m->getArrayList($post['members']);
896 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
897 $unreachables_members = $m->getArrayList($post['unreachables']);
901 switch ($post['from']) {
903 if (!isset($post['gid'])) {
905 'Trying to list group members with no group id provided',
908 throw new \
Exception('A group id is required.');
911 if (!isset($post['members'])) {
912 $group = new Group((int)$post['gid']);
913 $selected_members = array();
914 if (!isset($post['mode']) ||
$post['mode'] == 'members') {
915 $selected_members = $group->getMembers();
916 } elseif ($post['mode'] == 'managers') {
917 $selected_members = $group->getManagers();
920 'Trying to list group members with unknown mode',
923 throw new \
Exception('Unknown mode.');
928 $selected_members = $m->getArrayList($post['members']);
929 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
930 $unreachables_members = $m->getArrayList($post['unreachables']);
935 if (!isset($post['id_adh'])) {
936 throw new \
RuntimeException(
937 'Current selected member must be excluded while attaching!'
946 'filters' => $filters,
947 'members_list' => $members_list,
948 'selected_members' => $selected_members,
949 'unreachables_members' => $unreachables_members
952 if (isset($post['multiple'])) {
953 $params['multiple'] = true;
956 if (isset($post['gid'])) {
957 $params['the_id'] = $post['gid'];
960 if (isset($post['id_adh'])) {
961 $params['excluded'] = $post['id_adh'];
974 * Batch actions handler
976 * @param Request $request PSR Request
977 * @param Response $response PSR Response
981 public function handleBatch(Request
$request, Response
$response): Response
983 $post = $request->getParsedBody();
985 if (isset($post['member_sel'])) {
986 if (isset($this->session
->filter_members
)) {
987 $filters = $this->session
->filter_members
;
989 $filters = new MembersList();
992 $filters->selected
= $post['member_sel'];
993 $this->session
->filter_members
= $filters;
995 if (isset($post['cards'])) {
998 ->withHeader('Location', $this->router
->pathFor('pdf-members-cards'));
1001 if (isset($post['labels'])) {
1004 ->withHeader('Location', $this->router
->pathFor('pdf-members-labels'));
1007 if (isset($post['mailing'])) {
1010 ->withHeader('Location', $this->router
->pathFor('mailing') . '?mailing_new=new');
1013 if (isset($post['attendance_sheet'])) {
1016 ->withHeader('Location', $this->router
->pathFor('attendance_sheet_details'));
1019 if (isset($post['csv'])) {
1022 ->withHeader('Location', $this->router
->pathFor('csv-memberslist'));
1025 if (isset($post['delete'])) {
1028 ->withHeader('Location', $this->router
->pathFor('removeMembers'));
1031 if (isset($post['masschange'])) {
1034 ->withHeader('Location', $this->router
->pathFor('masschangeMembers'));
1037 throw new \
RuntimeException('Does not know what to batch :(');
1039 $this->flash
->addMessage(
1041 _T("No member was selected, please check at least one name.")
1046 ->withHeader('Location', $this->router
->pathFor('members'));
1056 * @param Request $request PSR Request
1057 * @param Response $response PSR Response
1058 * @param array $args Request arguments
1062 public function edit(Request
$request, Response
$response, array $args = []): Response
1064 $action = $args['action'];
1066 if (isset($args['id'])) {
1067 $id = (int)$args['id'];
1070 if ($action === 'edit' && $id === null) {
1071 throw new \
RuntimeException(
1072 _T("Member ID cannot ben null calling edit route!")
1074 } elseif ($action === 'add' && $id !== null) {
1077 ->withHeader('Location', $this->router
->pathFor('editmember', ['action' => 'add']));
1088 if ($this->session
->member
!== null) {
1089 $member = $this->session
->member
;
1090 $this->session
->member
= null;
1092 $member = new Adherent($this->zdb
, $id, $deps);
1097 if (!$member->canEdit($this->login
)) {
1098 $this->flash
->addMessage(
1100 _T("You do not have permission for requested URL.")
1107 $this->router
->pathFor('me')
1111 if ($member->id
!= $id) {
1112 $member->load($this->login
->id
);
1116 // flagging required fields
1117 $fc = $this->fields_config
;
1119 // password required if we create a new member
1120 if ($member->id
!= '') {
1121 $fc->setNotRequired('mdp_adh');
1124 //handle requirements for parent fields
1125 $parent_fields = $member->getParentFields();
1126 $tpl_parent_fields = []; //for JS when detaching
1127 foreach ($parent_fields as $field) {
1128 if ($fc->isRequired($field)) {
1129 $tpl_parent_fields[] = $field;
1130 if ($member->hasParent()) {
1131 $fc->setNotRequired($field);
1136 // flagging required fields invisible to members
1137 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1138 $fc->setNotRequired('activite_adh');
1139 $fc->setNotRequired('id_statut');
1142 // template variable declaration
1143 $title = _T("Member Profile");
1144 if ($member->id
!= '') {
1145 $title .= ' (' . _T("modification") . ')';
1147 $title .= ' (' . _T("creation") . ')';
1151 $statuts = new Status($this->zdb
);
1154 $groups = new Groups($this->zdb
, $this->login
);
1155 $groups_list = $groups->getSimpleList(true);
1157 $form_elements = $fc->getFormElements(
1165 if ($member->hasParent()) {
1166 $id = ($member->parent
instanceof Adherent ?
$member->parent
->id
: $member->parent
);
1168 $members = $m->getSelectizedMembers(
1173 $route_params['members'] = [
1174 'filters' => $m->getFilters(),
1175 'count' => $m->getCount()
1178 if (count($members)) {
1179 $route_params['members']['list'] = $members;
1183 $this->view
->render(
1187 'parent_tpl' => 'page.tpl',
1188 'autocomplete' => true,
1189 'page_title' => $title,
1190 'member' => $member,
1191 'self_adh' => false,
1192 // pseudo random int
1194 'titles_list' => Titles
::getList($this->zdb
),
1195 'statuts' => $statuts->getList(),
1196 'groups' => $groups_list,
1197 'fieldsets' => $form_elements['fieldsets'],
1198 'hidden_elements' => $form_elements['hiddens'],
1199 'parent_fields' => $tpl_parent_fields
1208 * @param Request $request PSR Request
1209 * @param Response $response PSR Response
1210 * @param array $args Request arguments
1214 public function doEdit(Request
$request, Response
$response, array $args = []): Response
1216 return $this->store($request, $response, $args);
1220 * Massive change page
1222 * @param Request $request PSR Request
1223 * @param Response $response PSR Response
1224 * @param array $args Request arguments
1228 public function massChange(Request
$request, Response
$response, array $args = []): Response
1230 $filters = $this->session
->filter_members
;
1233 'id' => $filters->selected
,
1234 'redirect_uri' => $this->router
->pathFor('members')
1237 $fc = $this->fields_config
;
1238 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1246 'children' => false,
1249 $member = new Adherent($this->zdb
, null, $deps);
1252 $statuts = new Status($this->zdb
);
1255 $this->view
->render(
1257 'mass_change_members.tpl',
1259 'mode' => $request->isXhr() ?
'ajax' : '',
1260 'page_title' => str_replace(
1263 _T('Mass change %count members')
1265 'form_url' => $this->router
->pathFor('masschangeMembersReview'),
1266 'cancel_uri' => $this->router
->pathFor('members'),
1268 'member' => $member,
1269 'fieldsets' => $form_elements['fieldsets'],
1270 'titles_list' => Titles
::getList($this->zdb
),
1271 'statuts' => $statuts->getList(),
1272 'require_mass' => true
1279 * Massive changes validation page
1281 * @param Request $request PSR Request
1282 * @param Response $response PSR Response
1283 * @param array $args Request arguments
1287 public function validateMassChange(Request
$request, Response
$response, array $args = []): Response
1289 $post = $request->getParsedBody();
1291 if (!isset($post['confirm'])) {
1292 $this->flash
->addMessage(
1294 _T("Mass changes has not been confirmed!")
1297 //we want only visibles fields
1298 $fc = $this->fields_config
;
1299 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1302 foreach ($form_elements['fieldsets'] as $form_element) {
1303 foreach ($form_element->elements
as $field) {
1304 if (isset($post[$field->field_id
]) && isset($post['mass_' . $field->field_id
])) {
1305 $changes[$field->field_id
] = [
1306 'label' => $field->label
,
1307 'value' => $post[$field->field_id
]
1314 $filters = $this->session
->filter_members
;
1316 'id' => $filters->selected
,
1317 'redirect_uri' => $this->router
->pathFor('members')
1321 $statuts = new Status($this->zdb
);
1324 $this->view
->render(
1326 'mass_change_members.tpl',
1328 'mode' => $request->isXhr() ?
'ajax' : '',
1329 'page_title' => str_replace(
1332 _T('Review mass change %count members')
1334 'form_url' => $this->router
->pathFor('massstoremembers'),
1335 'cancel_uri' => $this->router
->pathFor('members'),
1337 'titles_list' => Titles
::getList($this->zdb
),
1338 'statuts' => $statuts->getList(),
1339 'changes' => $changes
1346 * Do massive changes
1348 * @param Request $request PSR Request
1349 * @param Response $response PSR Response
1350 * @param array $args Request arguments
1354 public function doMassChange(Request
$request, Response
$response, array $args = []): Response
1356 $post = $request->getParsedBody();
1357 $redirect_url = $post['redirect_uri'];
1358 $error_detected = [];
1361 unset($post['redirect_uri']);
1362 if (!isset($post['confirm'])) {
1363 $error_detected[] = _T("Mass changes has not been confirmed!");
1365 unset($post['confirm']);
1369 $fc = $this->fields_config
;
1370 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1371 $disabled = $this->members_fields
;
1372 foreach (array_keys($post) as $key) {
1374 foreach ($form_elements['fieldsets'] as $fieldset) {
1375 if (isset($fieldset->elements
[$key])) {
1382 'Permission issue mass editing field ' . $key,
1387 unset($disabled[$key]);
1391 if (!count($post)) {
1392 $error_detected[] = _T("Nothing to do!");
1394 foreach ($ids as $id) {
1395 $is_manager = !$this->login
->isAdmin()
1396 && !$this->login
->isStaff()
1397 && $this->login
->isGroupManager();
1400 'groups' => $is_manager,
1403 'children' => false,
1406 $member = new Adherent($this->zdb
, (int)$id, $deps);
1407 $member->setDependencies(
1409 $this->members_fields
,
1412 if (!$member->canEdit($this->login
)) {
1416 $valid = $member->check($post, [], $disabled);
1417 if ($valid === true) {
1418 $done = $member->store();
1420 $error_detected[] = _T("An error occurred while storing the member.");
1425 $error_detected = array_merge($error_detected, $valid);
1431 if ($mass == 0 && !count($error_detected)) {
1432 $error_detected[] = _T('Something went wront during mass edition!');
1434 $this->flash
->addMessage(
1439 _T('%count members has been changed successfully!')
1444 if (count($error_detected) > 0) {
1445 foreach ($error_detected as $error) {
1446 $this->flash
->addMessage(
1453 if (!$request->isXhr()) {
1456 ->withHeader('Location', $redirect_url);
1458 return $response->withJson(
1460 'success' => count($error_detected) === 0
1469 * @param Request $request PSR Request
1470 * @param Response $response PSR Response
1471 * @param array $args Request arguments
1475 public function store(Request
$request, Response
$response, array $args = []): Response
1477 if (!$this->preferences
->pref_bool_selfsubscribe
&& !$this->login
->isLogged()) {
1480 ->withHeader('Location', $this->router
->pathFor('slash'));
1483 $post = $request->getParsedBody();
1492 $member = new Adherent($this->zdb
, null, $deps);
1493 $member->setDependencies(
1495 $this->members_fields
,
1498 if (isset($args['self'])) {
1499 //mark as self membership
1500 $member->setSelfMembership();
1503 $success_detected = [];
1504 $warning_detected = [];
1505 $error_detected = [];
1508 if (isset($args['self'])) {
1511 ||
!$post['mdp_adh']
1512 ||
!crypt($post['mdp_adh'], $post['mdp_crypt']) == $post['mdp_crypt']
1514 $error_detected[] = __('Please repeat in the field the password shown in the image.');
1519 $adherent['id_adh'] = get_numeric_form_value('id_adh', '');
1520 if ($this->login
->isAdmin() ||
$this->login
->isStaff() ||
$this->login
->isGroupManager()) {
1521 if ($adherent['id_adh']) {
1522 $member->load((int)$adherent['id_adh']);
1523 if (!$member->canEdit($this->login
)) {
1524 //redirection should have been done before. Just throw an Exception.
1525 throw new \
RuntimeException(
1529 'No right to store member #%id'
1535 $member->load($this->login
->id
);
1536 $adherent['id_adh'] = $this->login
->id
;
1539 // flagging required fields
1540 $fc = $this->fields_config
;
1542 // password required if we create a new member
1543 if ($member->id
!= '') {
1544 $fc->setNotRequired('mdp_adh');
1548 $member->hasParent() && !isset($post['detach_parent'])
1549 ||
isset($post['parent_id']) && !empty($post['parent_id'])
1551 $parent_fields = $member->getParentFields();
1552 foreach ($parent_fields as $field) {
1553 if ($fc->isRequired($field)) {
1554 $fc->setNotRequired($field);
1559 // flagging required fields invisible to members
1560 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1561 $fc->setNotRequired('activite_adh');
1562 $fc->setNotRequired('id_statut');
1565 $form_elements = $fc->getFormElements(
1568 isset($args['self'])
1570 $fieldsets = $form_elements['fieldsets'];
1571 $required = array();
1572 $disabled = array();
1574 foreach ($fieldsets as $category) {
1575 foreach ($category->elements
as $field) {
1576 if ($field->required
== true) {
1577 $required[$field->field_id
] = true;
1579 if ($field->disabled
== true) {
1580 $disabled[$field->field_id
] = true;
1581 } elseif (!isset($post[$field->field_id
])) {
1582 switch ($field->field_id
) {
1583 //unchecked booleans are not sent from form
1584 case 'bool_admin_adh':
1585 case 'bool_exempt_adh':
1586 case 'bool_display_info':
1587 $post[$field->field_id
] = 0;
1594 $real_requireds = array_diff(array_keys($required), array_keys($disabled));
1597 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1598 if (isset($post[array_shift($real_requireds)])) {
1600 $valid = $member->check($post, $required, $disabled);
1601 if ($valid !== true) {
1602 $error_detected = array_merge($error_detected, $valid);
1605 if (count($error_detected) == 0) {
1606 //all goes well, we can proceed
1609 if ($member->id
== '') {
1612 $store = $member->store();
1613 if ($store === true) {
1614 //member has been stored :)
1616 if (isset($args['self'])) {
1617 $success_detected[] = _T("Your account has been created!");
1619 $this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
1620 && $member->getEmail() != ''
1622 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1625 $success_detected[] = _T("New member has been successfully added.");
1627 //Send email to admin if preference checked
1629 $this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
1630 && $this->preferences
->pref_bool_mailadh
1636 'name_adh' => custom_html_entity_decode(
1639 'firstname_adh' => custom_html_entity_decode(
1642 'lastname_adh' => custom_html_entity_decode(
1645 'mail_adh' => custom_html_entity_decode(
1648 'login_adh' => custom_html_entity_decode(
1653 $mtxt = $texts->getTexts(
1654 (isset($args['self']) ?
'newselfadh' : 'newadh'),
1655 $this->preferences
->pref_lang
1658 $mail = new GaletteMail($this->preferences
);
1659 $mail->setSubject($texts->getSubject());
1661 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
1662 $recipients[$pref_email] = $pref_email;
1664 $mail->setRecipients($recipients);
1665 $mail->setMessage($texts->getBody());
1666 $sent = $mail->send();
1668 if ($sent == GaletteMail
::MAIL_SENT
) {
1669 $this->history
->add(
1672 $member->sname
. ' (' . $member->email
. ')',
1673 _T("New account email sent to admin for '%s'.")
1679 $member->sname
. ' (' . $member->email
. ')',
1680 _T("A problem happened while sending email to admin for account '%s'.")
1682 $this->history
->add($str);
1683 $warning_detected[] = $str;
1688 $success_detected[] = _T("Member account has been modified.");
1691 // send email to member
1692 if (isset($args['self']) ||
isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1693 if ($this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
) {
1694 if ($member->getEmail() == '' && !isset($args['self'])) {
1695 $error_detected[] = _T("- You can't send a confirmation by email if the member hasn't got an address!");
1698 'name_adh' => custom_html_entity_decode(
1701 'firstname_adh' => custom_html_entity_decode(
1704 'lastname_adh' => custom_html_entity_decode(
1707 'mail_adh' => custom_html_entity_decode(
1710 'login_adh' => custom_html_entity_decode(
1715 $password = new Password($this->zdb
);
1716 $res = $password->generateNewPassword($member->id
);
1718 $link_validity = new \
DateTime();
1719 $link_validity->add(new \
DateInterval('PT24H'));
1720 $mreplaces['change_pass_uri'] = $this->preferences
->getURL() .
1721 $this->router
->pathFor(
1722 'password-recovery',
1723 ['hash' => base64_encode($password->getHash())]
1725 $mreplaces['link_validity'] = $link_validity->format(_T("Y-m-d H:i:s"));
1730 _T("An error occurred storing temporary password for %s. Please inform an admin.")
1732 $this->history
->add($str);
1733 $this->flash
->addMessage(
1740 //send email to member
1741 // Get email text in database
1747 $mlang = $this->preferences
->pref_lang
;
1748 if (isset($post['pref_lang'])) {
1749 $mlang = $post['pref_lang'];
1751 $mtxt = $texts->getTexts(
1752 (($new) ?
'sub' : 'accountedited'),
1756 $mail = new GaletteMail($this->preferences
);
1757 $mail->setSubject($texts->getSubject());
1758 $mail->setRecipients(
1760 $member->getEmail() => $member->sname
1763 $mail->setMessage($texts->getBody());
1764 $sent = $mail->send();
1766 if ($sent == GaletteMail
::MAIL_SENT
) {
1769 $member->sname
. ' (' . $member->getEmail() . ')',
1771 _T("New account email sent to '%s'.") : _T("Account modification email sent to '%s'.")
1773 $this->history
->add($msg);
1774 $success_detected[] = $msg;
1778 $member->sname
. ' (' . $member->getEmail() . ')',
1779 _T("A problem happened while sending account email to '%s'")
1781 $this->history
->add($str);
1782 $error_detected[] = $str;
1785 } elseif ($this->preferences
->pref_mail_method
== GaletteMail
::METHOD_DISABLED
) {
1786 //if email has been disabled in the preferences, we should not be here ;
1787 //we do not throw an error, just a simple warning that will be show later
1788 $msg = _T("You asked Galette to send a confirmation email to the member, but email has been disabled in the preferences.");
1789 $warning_detected[] = $msg;
1793 // send email to admin
1795 $this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
1796 && $this->preferences
->pref_bool_mailadh
1798 && $member->id
== $this->login
->id
1801 'name_adh' => custom_html_entity_decode(
1804 'firstname_adh' => custom_html_entity_decode(
1807 'lastname_adh' => custom_html_entity_decode(
1810 'mail_adh' => custom_html_entity_decode(
1813 'login_adh' => custom_html_entity_decode(
1818 //send email to member
1819 // Get email text in database
1825 $mlang = $this->preferences
->pref_lang
;
1827 $mtxt = $texts->getTexts(
1832 $mail = new GaletteMail($this->preferences
);
1833 $mail->setSubject($texts->getSubject());
1835 foreach ($this->preferences
->vpref_email_newadh
as $pref_email) {
1836 $recipients[$pref_email] = $pref_email;
1838 $mail->setRecipients($recipients);
1840 $mail->setMessage($texts->getBody());
1841 $sent = $mail->send();
1843 if ($sent == GaletteMail
::MAIL_SENT
) {
1844 $msg = _T("Account modification email sent to admin.");
1845 $this->history
->add($msg);
1846 $success_detected[] = $msg;
1848 $str = _T("A problem happened while sending account email to admin");
1849 $this->history
->add($str);
1850 $warning_detected[] = $str;
1854 //store requested groups
1857 $managed_groups_adh = null;
1859 //add/remove user from groups
1860 if (isset($post['groups_adh'])) {
1861 $groups_adh = $post['groups_adh'];
1863 $add_groups = Groups
::addMemberToGroups(
1868 if ($add_groups === false) {
1869 $error_detected[] = _T("An error occurred adding member to its groups.");
1872 //add/remove manager from groups
1873 if (isset($post['groups_managed_adh'])) {
1874 $managed_groups_adh = $post['groups_managed_adh'];
1876 $add_groups = Groups
::addMemberToGroups(
1878 $managed_groups_adh,
1881 $member->loadGroups();
1883 if ($add_groups === false) {
1884 $error_detected[] = _T("An error occurred adding member to its groups as manager.");
1887 //something went wrong :'(
1888 $error_detected[] = _T("An error occurred while storing the member.");
1892 if (count($error_detected) == 0) {
1893 $files_res = $member->handleFiles($_FILES);
1894 if (is_array($files_res)) {
1895 $error_detected = array_merge($error_detected, $files_res);
1898 if (isset($post['del_photo'])) {
1899 if (!$member->picture
->delete($member->id
)) {
1900 $error_detected[] = _T("Delete failed");
1901 $str_adh = $member->id
. ' (' . $member->sname
. ' ' . ')';
1903 'Unable to delete picture for member ' . $str_adh,
1910 if (count($error_detected) > 0) {
1911 foreach ($error_detected as $error) {
1912 if (strpos($error, '%member_url_') !== false) {
1913 preg_match('/%member_url_(\d+)/', $error, $matches);
1914 $url = $this->router
->pathFor('member', ['id' => $matches[1]]);
1915 $error = str_replace(
1916 '%member_url_' . $matches[1],
1921 $this->flash
->addMessage(
1928 if (count($warning_detected) > 0) {
1929 foreach ($warning_detected as $warning) {
1930 $this->flash
->addMessage(
1936 if (count($success_detected) > 0) {
1937 foreach ($success_detected as $success) {
1938 $this->flash
->addMessage(
1945 if (count($error_detected) == 0) {
1946 if (isset($args['self'])) {
1947 $redirect_url = $this->router
->pathFor('login');
1949 isset($post['redirect_on_create'])
1950 && $post['redirect_on_create'] > Adherent
::AFTER_ADD_DEFAULT
1952 switch ($post['redirect_on_create']) {
1953 case Adherent
::AFTER_ADD_TRANS
:
1954 $redirect_url = $this->router
->pathFor('transaction', ['action' => 'add']);
1956 case Adherent
::AFTER_ADD_NEW
:
1957 $redirect_url = $this->router
->pathFor('editmember', ['action' => 'add']);
1959 case Adherent
::AFTER_ADD_SHOW
:
1960 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1962 case Adherent
::AFTER_ADD_LIST
:
1963 $redirect_url = $this->router
->pathFor('members');
1965 case Adherent
::AFTER_ADD_HOME
:
1966 $redirect_url = $this->router
->pathFor('slash');
1969 } elseif (!isset($post['id_adh']) && !$member->isDueFree()) {
1970 $redirect_url = $this->router
->pathFor(
1973 ) . '?id_adh=' . $member->id
;
1975 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1978 //store entity in session
1979 $this->session
->member
= $member;
1981 if (isset($args['self'])) {
1982 $redirect_url = $this->router
->pathFor('subscribe');
1986 'id' => $member->id
,
1990 $rparams = ['action' => 'add'];
1992 $redirect_url = $this->router
->pathFor(
2002 ->withHeader('Location', $redirect_url);
2010 * Get redirection URI
2012 * @param array $args Route arguments
2016 public function redirectUri(array $args = [])
2018 return $this->router
->pathFor('members');
2024 * @param array $args Route arguments
2028 public function formUri(array $args = [])
2030 return $this->router
->pathFor(
2040 * In simple cases, we get the ID in the route arguments; but for
2041 * batchs, it should be found elsewhere.
2042 * In post values, we look for id key, as well as all {sthing}_sel keys (like members_sel or contrib_sel)
2044 * @param array $args Request arguments
2045 * @param array $post POST values
2047 * @return null|integer|integer[]
2049 protected function getIdsToRemove(&$args, $post)
2051 if (isset($args['id'])) {
2054 $filters = $this->session
->filter_members
;
2055 return $filters->selected
;
2060 * Get confirmation removal page title
2062 * @param array $args Route arguments
2066 public function confirmRemoveTitle(array $args = [])
2068 if (isset($args['id_adh'])) {
2069 //one member removal
2070 $adh = new Adherent($this->zdb
, (int)$args['id_adh']);
2072 _T('Remove member %1$s'),
2076 //batch members removal
2077 $filters = $this->session
->filter_members
;
2080 count($filters->selected
),
2081 _T('You are about to remove %count members.')
2089 * @param array $args Route arguments
2090 * @param array $post POST values
2094 protected function doDelete(array $args, array $post)
2096 if (isset($this->session
->filter_members
)) {
2097 $filters = $this->session
->filter_members
;
2099 $filters = new MembersList();
2101 $members = new Members($filters);
2103 if (!is_array($post['id'])) {
2104 $ids = (array)$post['id'];
2109 return $members->removeMembers($ids);