3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette members 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/>.
27 * @category Controllers
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\Crud
;
39 use Galette\Controllers\CrudController
;
40 use Slim\Http\Request
;
41 use Slim\Http\Response
;
42 use Galette\Core\Authentication
;
43 use Galette\Core\GaletteMail
;
44 use Galette\Core\Gaptcha
;
45 use Galette\Core\Password
;
46 use Galette\Core\Picture
;
47 use Galette\Entity\Adherent
;
48 use Galette\Entity\Contribution
;
49 use Galette\Entity\ContributionsTypes
;
50 use Galette\Entity\DynamicFieldsHandle
;
51 use Galette\Entity\Group
;
52 use Galette\Entity\Status
;
53 use Galette\Entity\FieldsConfig
;
54 use Galette\Filters\AdvancedMembersList
;
55 use Galette\Filters\MembersList
;
57 use Galette\IO\MembersCsv
;
58 use Galette\Repository\Groups
;
59 use Galette\Repository\Members
;
60 use Galette\Repository\PaymentTypes
;
61 use Galette\Repository\Titles
;
65 * Galette members controller
67 * @category Controllers
68 * @name GaletteController
70 * @author Johan Cwiklinski <johan@x-tnd.be>
71 * @copyright 2019-2020 The Galette Team
72 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
73 * @link http://galette.tuxfamily.org
74 * @since Available since 0.9.4dev - 2019-12-02
77 class MembersController
extends CrudController
80 private $is_self_membership = false;
87 * @param Request $request PSR Request
88 * @param Response $response PSR Response
92 public function add(Request
$request, Response
$response): Response
94 return $this->edit($request, $response, null, 'add');
98 * Self subscription page
100 * @param Request $request PSR Request
101 * @param Response $response PSR Response
105 public function selfSubscribe(Request
$request, Response
$response): Response
107 if (!$this->preferences
->pref_bool_selfsubscribe ||
$this->login
->isLogged()) {
110 ->withHeader('Location', $this->router
->pathFor('slash'));
113 if ($this->session
->member
!== null) {
114 $member = $this->session
->member
;
115 $this->session
->member
= null;
120 $member = new Adherent($this->zdb
, null, $deps);
123 //mark as self membership
124 $member->setSelfMembership();
126 // flagging required fields
127 $fc = $this->fields_config
;
128 $form_elements = $fc->getFormElements($this->login
, true, true);
132 $members = $m->getSelectizedMembers(
134 $member->hasParent() ?
$member->parent
->id
: null
139 'filters' => $m->getFilters(),
140 'count' => $m->getCount()
144 if (count($members)) {
145 $params['members']['list'] = $members;
148 $gaptcha = new Gaptcha($this->i18n
);
149 $this->session
->gaptcha
= $gaptcha;
155 'page_title' => _T("Subscription"),
156 'parent_tpl' => 'public_page.tpl',
159 'autocomplete' => true,
162 'titles_list' => Titles
::getList($this->zdb
),
163 'fieldsets' => $form_elements['fieldsets'],
164 'hidden_elements' => $form_elements['hiddens'],
166 'gaptcha' => $gaptcha
175 * @param Request $request PSR Request
176 * @param Response $response PSR Response
180 public function doAdd(Request
$request, Response
$response): Response
182 return $this->store($request, $response);
186 * Self subscription add action
188 * @param Request $request PSR Request
189 * @param Response $response PSR Response
193 public function doSelfSubscribe(Request
$request, Response
$response): Response
195 $this->setSelfMembership();
196 return $this->doAdd($request, $response);
203 * @param Request $request PSR Request
204 * @param Response $response PSR Response
205 * @param integer $id_adh Member ID to duplicate
209 public function duplicate(Request
$request, Response
$response, int $id_adh): Response
211 $adh = new Adherent($this->zdb
, $id_adh, ['dynamics' => true, 'parent' => true]);
212 $adh->setDuplicate();
214 //store entity in session
215 $this->session
->member
= $adh;
219 ->withHeader('Location', $this->router
->pathFor('addMember'));
226 * Display member card
228 * @param Request $request PSR Request
229 * @param Response $response PSR Response
230 * @param integer $id Member ID
234 public function show(Request
$request, Response
$response, int $id): Response
244 $member = new Adherent($this->zdb
, $id, $deps);
246 if (!$member->canEdit($this->login
)) {
247 $this->flash
->addMessage(
249 _T("You do not have permission for requested URL.")
256 $this->router
->pathFor('me')
260 if ($member->id
== null) {
261 //member does not exists!
262 $this->flash
->addMessage(
264 str_replace('%id', $id, _T("No member #%id."))
271 $this->router
->pathFor('slash')
275 // flagging fields visibility
276 $fc = $this->fields_config
;
277 $display_elements = $fc->getDisplayElements($this->login
);
284 'page_title' => _T("Member Profile"),
286 'pref_lang' => $this->i18n
->getNameFromId($member->language
),
287 'pref_card_self' => $this->preferences
->pref_card_self
,
288 'groups' => Groups
::getSimpleList(),
290 'display_elements' => $display_elements
299 * @param Request $request PSR Request
300 * @param Response $response PSR Response
304 public function showMe(Request
$request, Response
$response): Response
306 if ($this->login
->isSuperAdmin()) {
309 ->withHeader('Location', $this->router
->pathFor('slash'));
311 return $this->show($request, $response, $this->login
->id
);
315 * Public pages (trombinoscope, public list)
317 * @param Request $request PSR Request
318 * @param Response $response PSR Response
319 * @param string $option One of 'page' or 'order'
320 * @param string|integer $value Value of the option
321 * @param string $type List type (either list or trombi)
325 public function publicList(
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 string $type Type
383 public function filterPublicList(Request
$request, Response
$response, string $type): Response
385 $post = $request->getParsedBody();
387 $varname = 'public_filter_' . $type;
388 if (isset($this->session
->$varname)) {
389 $filters = $this->session
->$varname;
391 $filters = new MembersList();
394 //reintialize filters
395 if (isset($post['clear_filter'])) {
398 //number of rows to show
399 if (isset($post['nbshow'])) {
400 $filters->show
= (int)$post['nbshow'];
404 $this->session
->$varname = $filters;
408 ->withHeader('Location', $this->router
->pathFor('publicList', ['type' => $type]));
414 * @param Request $request PSR Request
415 * @param Response $response PSR Response
416 * @param integer $id Member ID
417 * @param integer $fid Dynamic fields ID
418 * @param integer $pos Dynamic field position
419 * @param string $name File name
423 public function getDynamicFile(
439 $member = new Adherent($this->zdb
, $id, $deps);
442 if (!$member->canEdit($this->login
)) {
443 $fields = $member->getDynamicFields()->getFields();
444 if (!isset($fields[$fid])) {
445 //field does not exists or access is forbidden
452 if ($denied === true) {
453 $this->flash
->addMessage(
455 _T("You do not have permission for requested URL.")
462 $this->router
->pathFor(
469 $filename = str_replace(
480 'member_%mid_field_%fid_value_%pos'
483 if (file_exists(GALETTE_FILES_PATH
. $filename)) {
484 $type = File
::getMimeType(GALETTE_FILES_PATH
. $filename);
486 $response = $response->withHeader('Content-Description', 'File Transfer')
487 ->withHeader('Content-Type', $type)
488 ->withHeader('Content-Disposition', 'attachment;filename="' . $name . '"')
489 ->withHeader('Pragma', 'no-cache')
490 ->withHeader('Content-Transfer-Encoding', 'binary')
491 ->withHeader('Expires', '0')
492 ->withHeader('Cache-Control', 'must-revalidate')
493 ->withHeader('Pragma', 'public');
495 $stream = fopen('php://memory', 'r+');
496 fwrite($stream, file_get_contents(GALETTE_FILES_PATH
. $filename));
499 return $response->withBody(new \Slim\Http\
Stream($stream));
502 'A request has been made to get a dynamic file named `' .
503 $filename . '` that does not exists.',
507 $this->flash
->addMessage(
509 _T("The file does not exists or cannot be read :(")
516 $this->router
->pathFor('member', ['id' => $id])
524 * @param Request $request PSR Request
525 * @param Response $response PSR Response
526 * @param string $option One of 'page' or 'order'
527 * @param string|integer $value Value of the option
531 public function list(Request
$request, Response
$response, $option = null, $value = null): Response
533 if (isset($this->session
->filter_members
)) {
534 $filters = $this->session
->filter_members
;
536 $filters = new MembersList();
539 if ($option !== null) {
542 $filters->current_page
= (int)$value;
545 $filters->orderby
= $value;
550 $members = new Members($filters);
552 $members_list = array();
553 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
554 $members_list = $members->getMembersList(true);
556 $members_list = $members->getManagedMembersList(true);
559 $groups = new Groups($this->zdb
, $this->login
);
560 $groups_list = $groups->getList();
562 //assign pagination variables to the template and add pagination links
563 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
564 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
566 $this->session
->filter_members
= $filters;
571 'gestion_adherents.tpl',
573 'page_title' => _T("Members management"),
574 'require_mass' => true,
575 'members' => $members_list,
576 'filter_groups_options' => $groups_list,
577 'nb_members' => $members->getCount(),
578 'filters' => $filters,
579 'adv_filters' => $filters instanceof AdvancedMembersList
,
580 'galette_list' => $this->lists_config
->getDisplayElements($this->login
)
589 * @param Request $request PSR Request
590 * @param Response $response PSR Response
594 public function filter(Request
$request, Response
$response): Response
596 $post = $request->getParsedBody();
597 if (isset($this->session
->filter_members
)) {
598 //CAUTION: this one may be simple or advanced, display must change
599 $filters = $this->session
->filter_members
;
601 $filters = new MembersList();
604 //reintialize filters
605 if (isset($post['clear_filter'])) {
606 $filters = new MembersList();
607 } elseif (isset($post['clear_adv_filter'])) {
608 $this->session
->filter_members
= null;
609 unset($this->session
->filter_members
);
613 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
614 } elseif (isset($post['adv_criteria'])) {
617 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
620 if (isset($post['filter_str'])) { //filter search string
621 $filters->filter_str
= stripslashes(
622 htmlspecialchars($post['filter_str'], ENT_QUOTES
)
626 if (isset($post['field_filter'])) {
627 if (is_numeric($post['field_filter'])) {
628 $filters->field_filter
= $post['field_filter'];
631 //membership to filter
632 if (isset($post['membership_filter'])) {
633 if (is_numeric($post['membership_filter'])) {
634 $filters->membership_filter
635 = $post['membership_filter'];
638 //account status to filter
639 if (isset($post['filter_account'])) {
640 if (is_numeric($post['filter_account'])) {
641 $filters->filter_account
= $post['filter_account'];
645 if (isset($post['email_filter'])) {
646 $filters->email_filter
= (int)$post['email_filter'];
650 isset($post['group_filter'])
651 && $post['group_filter'] > 0
653 $filters->group_filter
= (int)$post['group_filter'];
655 //number of rows to show
656 if (isset($post['nbshow'])) {
657 $filters->show
= (int)$post['nbshow'];
660 if (isset($post['advanced_filtering'])) {
661 if (!$filters instanceof AdvancedMembersList
) {
662 $filters = new AdvancedMembersList($filters);
666 unset($post['advanced_filtering']);
668 foreach ($post as $k => $v) {
669 if (strpos($k, 'free_', 0) === 0) {
672 foreach ($post['free_field'] as $f) {
675 && trim($post['free_text'][$i]) !== ''
677 $fs_search = htmlspecialchars($post['free_text'][$i], ENT_QUOTES
);
679 = (int)$post['free_logical_operator'][$i];
681 = (int)$post['free_query_operator'][$i];
682 $type = (int)$post['free_type'][$i];
687 'search' => $fs_search,
691 $filters->free_search
= $fs;
697 } elseif ($k == 'groups_search') {
699 $filters->groups_search_log_op
= (int)$post['groups_logical_operator'];
700 foreach ($post['groups_search'] as $g) {
701 if (trim($g) !== '') {
706 $filters->groups_search
= $gs;
712 case 'contrib_min_amount':
713 case 'contrib_max_amount':
714 if (trim($v) !== '') {
727 if (isset($post['savesearch'])) {
732 $this->router
->pathFor(
739 $this->session
->filter_members
= $filters;
743 ->withHeader('Location', $this->router
->pathFor('members'));
747 * Advanced search page
749 * @param Request $request PSR Request
750 * @param Response $response PSR Response
754 public function advancedSearch(Request
$request, Response
$response): Response
756 if (isset($this->session
->filter_members
)) {
757 $filters = $this->session
->filter_members
;
758 if (!$filters instanceof AdvancedMembersList
) {
759 $filters = new AdvancedMembersList($filters);
762 $filters = new AdvancedMembersList();
765 $groups = new Groups($this->zdb
, $this->login
);
766 $groups_list = $groups->getList();
768 //we want only visibles fields
769 $fields = $this->members_fields
;
770 $fc = $this->fields_config
;
771 $visibles = $fc->getVisibilities();
772 $access_level = $this->login
->getAccessLevel();
774 //remove not searchable fields
775 unset($fields['mdp_adh']);
777 foreach ($fields as $k => $f) {
779 $visibles[$k] == FieldsConfig
::NOBODY ||
780 ($visibles[$k] == FieldsConfig
::ADMIN
&&
781 $access_level < Authentication
::ACCESS_ADMIN
) ||
782 ($visibles[$k] == FieldsConfig
::STAFF
&&
783 $access_level < Authentication
::ACCESS_STAFF
) ||
784 ($visibles[$k] == FieldsConfig
::MANAGER
&&
785 $access_level < Authentication
::ACCESS_MANAGER
)
791 //add status label search
792 if ($pos = array_search(Status
::PK
, array_keys($fields))) {
793 $fields = array_slice($fields, 0, $pos, true) +
794 ['status_label' => ['label' => _T('Status label')]] +
795 array_slice($fields, $pos, count($fields) - 1, true);
807 $member = new Adherent($this->zdb
, $this->login
->login
, $deps);
808 $adh_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $member);
810 $contrib = new Contribution($this->zdb
, $this->login
);
811 $contrib_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $contrib);
814 $statuts = new Status($this->zdb
);
816 //Contributions types
817 $ct = new ContributionsTypes($this->zdb
);
820 $ptypes = new PaymentTypes(
825 $ptlist = $ptypes->getList();
827 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
832 'advanced_search.tpl',
834 'page_title' => _T("Advanced search"),
835 'filter_groups_options' => $groups_list,
836 'search_fields' => $fields,
837 'adh_dynamics' => $adh_dynamics->getFields(),
838 'contrib_dynamics' => $contrib_dynamics->getFields(),
839 'statuts' => $statuts->getList(),
840 'contributions_types' => $ct->getList(),
841 'filters' => $filters,
842 'payments_types' => $ptlist
849 * Members list for ajax
851 * @param Request $request PSR Request
852 * @param Response $response PSR Response
853 * @param string $option One of 'page' or 'order'
854 * @param string|integer $value Value of the option
858 public function ajaxList(Request
$request, Response
$response, string $option = null, $value = null): Response
860 $post = $request->getParsedBody();
862 if (isset($this->session
->ajax_members_filters
)) {
863 $filters = $this->session
->ajax_members_filters
;
865 $filters = new MembersList();
868 if ($option == 'page') {
869 $filters->current_page
= (int)$value;
872 //numbers of rows to display
873 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
874 $filters->show
= (int)$post['nbshow'];
877 $members = new Members($filters);
878 if (!$this->login
->isAdmin() && !$this->login
->isStaff()) {
879 if ($this->login
->isGroupManager()) {
880 $members_list = $members->getManagedMembersList(true);
885 [$this->login
->id
, $this->login
->login
],
886 'Trying to list group members without access from #%id (%login)'
890 throw new \
Exception('Access denied.');
893 $members_list = $members->getMembersList(true);
896 //assign pagination variables to the template and add pagination links
897 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
899 $this->session
->ajax_members_filters
= $filters;
901 $selected_members = null;
902 $unreachables_members = null;
903 if (!isset($post['from'])) {
904 $mailing = $this->session
->mailing
;
905 if (!isset($post['members'])) {
906 $selected_members = $mailing->recipients
;
907 $unreachables_members = $mailing->unreachables
;
910 $selected_members = $m->getArrayList($post['members']);
911 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
912 $unreachables_members = $m->getArrayList($post['unreachables']);
916 switch ($post['from']) {
918 if (!isset($post['gid'])) {
920 'Trying to list group members with no group id provided',
923 throw new \
Exception('A group id is required.');
925 if (!isset($post['members'])) {
926 $group = new Group((int)$post['gid']);
927 $selected_members = array();
928 if (!isset($post['mode']) ||
$post['mode'] == 'members') {
929 $selected_members = $group->getMembers();
930 } elseif ($post['mode'] == 'managers') {
931 $selected_members = $group->getManagers();
934 'Trying to list group members with unknown mode',
937 throw new \
Exception('Unknown mode.');
941 $selected_members = $m->getArrayList($post['members']);
942 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
943 $unreachables_members = $m->getArrayList($post['unreachables']);
948 if (!isset($post['id_adh'])) {
949 throw new \
RuntimeException(
950 'Current selected member must be excluded while attaching!'
958 'filters' => $filters,
959 'members_list' => $members_list,
960 'selected_members' => $selected_members,
961 'unreachables_members' => $unreachables_members
964 if (isset($post['multiple'])) {
965 $params['multiple'] = true;
968 if (isset($post['gid'])) {
969 $params['the_id'] = (int)$post['gid'];
972 if (isset($post['id_adh'])) {
973 $params['excluded'] = (int)$post['id_adh'];
986 * Batch actions handler
988 * @param Request $request PSR Request
989 * @param Response $response PSR Response
993 public function handleBatch(Request
$request, Response
$response): Response
995 $post = $request->getParsedBody();
997 if (isset($post['member_sel'])) {
998 if (isset($this->session
->filter_members
)) {
999 $filters = $this->session
->filter_members
;
1001 $filters = new MembersList();
1004 $filters->selected
= $post['member_sel'];
1005 $this->session
->filter_members
= $filters;
1007 if (isset($post['cards'])) {
1010 ->withHeader('Location', $this->router
->pathFor('pdf-members-cards'));
1013 if (isset($post['labels'])) {
1016 ->withHeader('Location', $this->router
->pathFor('pdf-members-labels'));
1019 if (isset($post['mailing'])) {
1022 ->withHeader('Location', $this->router
->pathFor('mailing') . '?mailing_new=new');
1025 if (isset($post['attendance_sheet'])) {
1028 ->withHeader('Location', $this->router
->pathFor('attendance_sheet_details'));
1031 if (isset($post['csv'])) {
1034 ->withHeader('Location', $this->router
->pathFor('csv-memberslist'));
1037 if (isset($post['delete'])) {
1040 ->withHeader('Location', $this->router
->pathFor('removeMembers'));
1043 if (isset($post['masschange'])) {
1046 ->withHeader('Location', $this->router
->pathFor('masschangeMembers'));
1049 throw new \
RuntimeException('Does not know what to batch :(');
1051 $this->flash
->addMessage(
1053 _T("No member was selected, please check at least one name.")
1058 ->withHeader('Location', $this->router
->pathFor('members'));
1068 * @param Request $request PSR Request
1069 * @param Response $response PSR Response
1070 * @param mixed $id Member id/array of members id
1071 * @param string $action null or 'add'
1075 public function edit(
1090 //instanciate member object
1091 $member = new Adherent($this->zdb
, $id, $deps);
1093 if ($this->session
->member
!== null) {
1094 //retrieve from session, in add or edit
1095 $member = $this->session
->member
;
1096 $this->session
->member
= null;
1097 } elseif ($id !== null) {
1098 //load requested member
1100 if (!$member->canEdit($this->login
) ||
$member->id
!= $id) {
1101 $this->flash
->addMessage(
1103 _T("You do not have permission for requested URL.")
1110 $this->router
->pathFor('me')
1115 // flagging required fields
1116 $fc = $this->fields_config
;
1118 // password required if we create a new member
1119 if ($member->id
!= '') {
1120 $fc->setNotRequired('mdp_adh');
1123 //handle requirements for parent fields
1124 $parent_fields = $member->getParentFields();
1125 $tpl_parent_fields = []; //for JS when detaching
1126 foreach ($parent_fields as $field) {
1127 if ($fc->isRequired($field)) {
1128 $tpl_parent_fields[] = $field;
1129 if ($member->hasParent()) {
1130 $fc->setNotRequired($field);
1135 // flagging required fields invisible to members
1136 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1137 $fc->setNotRequired('activite_adh');
1138 $fc->setNotRequired('id_statut');
1141 // template variable declaration
1142 $title = _T("Member Profile");
1143 if ($member->id
!= '') {
1144 $title .= ' (' . _T("modification") . ')';
1146 $title .= ' (' . _T("creation") . ')';
1150 $statuts = new Status($this->zdb
);
1153 $groups = new Groups($this->zdb
, $this->login
);
1154 $groups_list = $groups->getSimpleList(true);
1156 $form_elements = $fc->getFormElements(
1164 if ($member->hasParent()) {
1165 $id = ($member->parent
instanceof Adherent ?
$member->parent
->id
: $member->parent
);
1167 $members = $m->getSelectizedMembers(
1172 $route_params['members'] = [
1173 'filters' => $m->getFilters(),
1174 'count' => $m->getCount()
1177 if (count($members)) {
1178 $route_params['members']['list'] = $members;
1182 $this->view
->render(
1186 'parent_tpl' => 'page.tpl',
1187 'autocomplete' => true,
1188 'page_title' => $title,
1189 'member' => $member,
1190 'self_adh' => false,
1191 // pseudo random int
1193 'titles_list' => Titles
::getList($this->zdb
),
1194 'statuts' => $statuts->getList(),
1195 'groups' => $groups_list,
1196 'fieldsets' => $form_elements['fieldsets'],
1197 'hidden_elements' => $form_elements['hiddens'],
1198 'parent_fields' => $tpl_parent_fields
1207 * @param Request $request PSR Request
1208 * @param Response $response PSR Response
1209 * @param integer $id Member id
1213 public function doEdit(Request
$request, Response
$response, int $id): Response
1215 return $this->store($request, $response);
1219 * Massive change page
1221 * @param Request $request PSR Request
1222 * @param Response $response PSR Response
1226 public function massChange(Request
$request, Response
$response): Response
1228 $filters = $this->session
->filter_members
;
1231 'id' => $filters->selected
,
1232 'redirect_uri' => $this->router
->pathFor('members')
1235 $fc = $this->fields_config
;
1236 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1244 'children' => false,
1247 $member = new Adherent($this->zdb
, null, $deps);
1250 $statuts = new Status($this->zdb
);
1253 $this->view
->render(
1255 'mass_change_members.tpl',
1257 'mode' => $request->isXhr() ?
'ajax' : '',
1258 'page_title' => str_replace(
1261 _T('Mass change %count members')
1263 'form_url' => $this->router
->pathFor('masschangeMembersReview'),
1264 'cancel_uri' => $this->router
->pathFor('members'),
1266 'member' => $member,
1267 'fieldsets' => $form_elements['fieldsets'],
1268 'titles_list' => Titles
::getList($this->zdb
),
1269 'statuts' => $statuts->getList(),
1270 'require_mass' => true
1277 * Massive changes validation page
1279 * @param Request $request PSR Request
1280 * @param Response $response PSR Response
1284 public function validateMassChange(Request
$request, Response
$response): Response
1286 $post = $request->getParsedBody();
1288 if (!isset($post['confirm'])) {
1289 $this->flash
->addMessage(
1291 _T("Mass changes has not been confirmed!")
1294 //we want only visibles fields
1295 $fc = $this->fields_config
;
1296 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1299 foreach ($form_elements['fieldsets'] as $form_element) {
1300 foreach ($form_element->elements
as $field) {
1302 isset($post['mass_' . $field->field_id
])
1303 && (isset($post[$field->field_id
]) ||
$field->type
=== FieldsConfig
::TYPE_BOOL
)
1305 $changes[$field->field_id
] = [
1306 'label' => $field->label
,
1307 'value' => $post[$field->field_id
] ??
0
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
1353 public function doMassChange(Request
$request, Response
$response): Response
1355 $post = $request->getParsedBody();
1356 $redirect_url = $post['redirect_uri'];
1357 $error_detected = [];
1360 unset($post['redirect_uri']);
1361 if (!isset($post['confirm'])) {
1362 $error_detected[] = _T("Mass changes has not been confirmed!");
1364 unset($post['confirm']);
1368 $fc = $this->fields_config
;
1369 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1370 $disabled = $this->members_fields
;
1371 foreach (array_keys($post) as $key) {
1373 foreach ($form_elements['fieldsets'] as $fieldset) {
1374 if (isset($fieldset->elements
[$key])) {
1381 'Permission issue mass editing field ' . $key,
1386 unset($disabled[$key]);
1390 if (!count($post)) {
1391 $error_detected[] = _T("Nothing to do!");
1393 foreach ($ids as $id) {
1394 $is_manager = !$this->login
->isAdmin()
1395 && !$this->login
->isStaff()
1396 && $this->login
->isGroupManager();
1399 'groups' => $is_manager,
1402 'children' => false,
1405 $member = new Adherent($this->zdb
, (int)$id, $deps);
1406 $member->setDependencies(
1408 $this->members_fields
,
1411 if (!$member->canEdit($this->login
)) {
1415 $valid = $member->check($post, [], $disabled);
1416 if ($valid === true) {
1417 $done = $member->store();
1419 $error_detected[] = _T("An error occurred while storing the member.");
1424 $error_detected = array_merge($error_detected, $valid);
1430 if ($mass == 0 && !count($error_detected)) {
1431 $error_detected[] = _T('Something went wront during mass edition!');
1433 $this->flash
->addMessage(
1438 _T('%count members has been changed successfully!')
1443 if (count($error_detected) > 0) {
1444 foreach ($error_detected as $error) {
1445 $this->flash
->addMessage(
1452 if (!$request->isXhr()) {
1455 ->withHeader('Location', $redirect_url);
1457 return $response->withJson(
1459 'success' => count($error_detected) === 0
1468 * @param Request $request PSR Request
1469 * @param Response $response PSR Response
1473 public function store(Request
$request, Response
$response): Response
1475 if (!$this->preferences
->pref_bool_selfsubscribe
&& !$this->login
->isLogged()) {
1478 ->withHeader('Location', $this->router
->pathFor('slash'));
1481 $post = $request->getParsedBody();
1490 $member = new Adherent($this->zdb
, null, $deps);
1491 $member->setDependencies(
1493 $this->members_fields
,
1497 $success_detected = [];
1498 $warning_detected = [];
1499 $error_detected = [];
1501 if ($this->isSelfMembership() && !isset($post[Adherent
::PK
])) {
1502 //mark as self membership
1503 $member->setSelfMembership();
1506 $gaptcha = $this->session
->gaptcha
;
1507 if (!$gaptcha->check($post['gaptcha'])) {
1508 $error_detected[] = _T('Invalid captcha');
1513 if ($this->login
->isAdmin() ||
$this->login
->isStaff() ||
$this->login
->isGroupManager()) {
1514 if (isset($post['id_adh'])) {
1515 $member->load((int)$post['id_adh']);
1516 if (!$member->canEdit($this->login
)) {
1517 //redirection should have been done before. Just throw an Exception.
1518 throw new \
RuntimeException(
1522 'No right to store member #%id'
1528 $member->load($this->login
->id
);
1531 // flagging required fields
1532 $fc = $this->fields_config
;
1534 // password required if we create a new member but not from self subscription
1535 if ($member->id
!= '' ||
$this->isSelfMembership()) {
1536 $fc->setNotRequired('mdp_adh');
1540 $member->hasParent() && !isset($post['detach_parent'])
1541 ||
isset($post['parent_id']) && !empty($post['parent_id'])
1543 $parent_fields = $member->getParentFields();
1544 foreach ($parent_fields as $field) {
1545 if ($fc->isRequired($field)) {
1546 $fc->setNotRequired($field);
1551 // flagging required fields invisible to members
1552 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1553 $fc->setNotRequired('activite_adh');
1554 $fc->setNotRequired('id_statut');
1557 $form_elements = $fc->getFormElements(
1560 $this->isSelfMembership()
1562 $fieldsets = $form_elements['fieldsets'];
1563 $required = array();
1564 $disabled = array();
1566 foreach ($fieldsets as $category) {
1567 foreach ($category->elements
as $field) {
1568 if ($field->required
== true) {
1569 $required[$field->field_id
] = true;
1571 if ($field->disabled
== true) {
1572 $disabled[$field->field_id
] = true;
1573 } elseif (!isset($post[$field->field_id
])) {
1574 switch ($field->field_id
) {
1575 //unchecked booleans are not sent from form
1576 case 'bool_admin_adh':
1577 case 'bool_exempt_adh':
1578 case 'bool_display_info':
1579 $post[$field->field_id
] = 0;
1586 $real_requireds = array_diff(array_keys($required), array_keys($disabled));
1589 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1590 if (!count($real_requireds) ||
isset($post[array_shift($real_requireds)])) {
1592 $valid = $member->check($post, $required, $disabled);
1593 if ($valid !== true) {
1594 $error_detected = array_merge($error_detected, $valid);
1597 if (count($error_detected) == 0) {
1598 //all goes well, we can proceed
1601 if ($member->id
== '') {
1605 // send email to member
1606 if ($this->isSelfMembership() ||
isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1607 $member->setSendmail(); //flag to send creation email
1610 $store = $member->store();
1611 if ($store === true) {
1612 //member has been stored :)
1614 if ($this->isSelfMembership()) {
1615 $success_detected[] = _T("Your account has been created!");
1617 $this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
1618 && $member->getEmail() != ''
1620 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1623 $success_detected[] = _T("New member has been successfully added.");
1626 $success_detected[] = _T("Member account has been modified.");
1629 //store requested groups
1630 $groups_adh = $post['groups_adh'] ??
null;
1631 $managed_groups_adh = $post['groups_managed_adh'] ??
null;
1633 //add/remove user from groups
1634 $add_groups = Groups
::addMemberToGroups(
1639 if ($add_groups === false) {
1640 $error_detected[] = _T("An error occurred adding member to its groups.");
1643 //add/remove manager from groups
1644 $add_groups = Groups
::addMemberToGroups(
1646 $managed_groups_adh,
1649 $member->loadGroups();
1651 if ($add_groups === false) {
1652 $error_detected[] = _T("An error occurred adding member to its groups as manager.");
1655 //something went wrong :'(
1656 $error_detected[] = _T("An error occurred while storing the member.");
1660 if (count($error_detected) === 0) {
1661 $files_res = $member->handleFiles($_FILES);
1662 if (is_array($files_res)) {
1663 $error_detected = array_merge($error_detected, $files_res);
1666 if (isset($post['del_photo'])) {
1667 if (!$member->picture
->delete($member->id
)) {
1668 $error_detected[] = _T("Delete failed");
1669 $str_adh = $member->id
. ' (' . $member->sname
. ' ' . ')';
1671 'Unable to delete picture for member ' . $str_adh,
1678 if (count($error_detected) > 0) {
1679 foreach ($error_detected as $error) {
1680 if (strpos($error, '%member_url_') !== false) {
1681 preg_match('/%member_url_(\d+)/', $error, $matches);
1682 $url = $this->router
->pathFor('member', ['id' => $matches[1]]);
1683 $error = str_replace(
1684 '%member_url_' . $matches[1],
1689 $this->flash
->addMessage(
1696 if (count($warning_detected) > 0) {
1697 foreach ($warning_detected as $warning) {
1698 $this->flash
->addMessage(
1704 if (count($success_detected) > 0) {
1705 foreach ($success_detected as $success) {
1706 $this->flash
->addMessage(
1713 if (count($error_detected) === 0) {
1714 if ($this->isSelfMembership()) {
1715 $redirect_url = $this->router
->pathFor('login');
1717 isset($post['redirect_on_create'])
1718 && $post['redirect_on_create'] > Adherent
::AFTER_ADD_DEFAULT
1720 switch ($post['redirect_on_create']) {
1721 case Adherent
::AFTER_ADD_TRANS
:
1722 $redirect_url = $this->router
->pathFor('addTransaction');
1724 case Adherent
::AFTER_ADD_NEW
:
1725 $redirect_url = $this->router
->pathFor('addMember');
1727 case Adherent
::AFTER_ADD_SHOW
:
1728 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1730 case Adherent
::AFTER_ADD_LIST
:
1731 $redirect_url = $this->router
->pathFor('members');
1733 case Adherent
::AFTER_ADD_HOME
:
1734 $redirect_url = $this->router
->pathFor('slash');
1737 } elseif (!isset($post['id_adh']) && !$member->isDueFree()) {
1738 $redirect_url = $this->router
->pathFor(
1741 ) . '?id_adh=' . $member->id
;
1743 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1746 //store entity in session
1747 $this->session
->member
= $member;
1749 if ($this->isSelfMembership()) {
1750 $redirect_url = $this->router
->pathFor('subscribe');
1753 $redirect_url = $this->router
->pathFor(
1755 ['id' => $member->id
]
1758 $redirect_url = $this->router
->pathFor('addMember');
1766 ->withHeader('Location', $redirect_url);
1774 * Get redirection URI
1776 * @param array $args Route arguments
1780 public function redirectUri(array $args)
1782 return $this->router
->pathFor('members');
1788 * @param array $args Route arguments
1792 public function formUri(array $args)
1794 return $this->router
->pathFor(
1804 * In simple cases, we get the ID in the route arguments; but for
1805 * batchs, it should be found elsewhere.
1806 * In post values, we look for id key, as well as all {sthing}_sel keys (like members_sel or contrib_sel)
1808 * @param array $args Request arguments
1809 * @param array $post POST values
1811 * @return null|integer|integer[]
1813 protected function getIdsToRemove(&$args, $post)
1815 if (isset($args['id'])) {
1818 $filters = $this->session
->filter_members
;
1819 return $filters->selected
;
1824 * Get confirmation removal page title
1826 * @param array $args Route arguments
1830 public function confirmRemoveTitle(array $args)
1832 if (isset($args['id_adh']) ||
isset($args['id'])) {
1833 //one member removal
1834 $id_adh = $args['id_adh'] ??
$args['id'];
1835 $adh = new Adherent($this->zdb
, (int)$id_adh);
1837 _T('Remove member %1$s'),
1841 //batch members removal
1842 $filters = $this->session
->filter_members
;
1845 count($filters->selected
),
1846 _T('You are about to remove %count members.')
1854 * @param array $args Route arguments
1855 * @param array $post POST values
1859 protected function doDelete(array $args, array $post)
1861 if (isset($this->session
->filter_members
)) {
1862 $filters = $this->session
->filter_members
;
1864 $filters = new MembersList();
1866 $members = new Members($filters);
1868 if (!is_array($post['id'])) {
1869 $ids = (array)$post['id'];
1874 return $members->removeMembers($ids);
1880 * Set self memebrship flag
1882 * @return MembersController
1884 private function setSelfMembership(): MembersController
1886 $this->is_self_membership
= true;
1891 * Is self membership?
1895 private function isSelfMembership(): bool
1897 return $this->is_self_membership
;