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 Galette\DynamicFields\Boolean
;
41 use Slim\Http\Request
;
42 use Slim\Http\Response
;
43 use Galette\Core\GaletteMail
;
44 use Galette\Core\Gaptcha
;
45 use Galette\Entity\Adherent
;
46 use Galette\Entity\Contribution
;
47 use Galette\Entity\ContributionsTypes
;
48 use Galette\Entity\DynamicFieldsHandle
;
49 use Galette\Entity\Group
;
50 use Galette\Entity\Status
;
51 use Galette\Entity\FieldsConfig
;
52 use Galette\Filters\AdvancedMembersList
;
53 use Galette\Filters\MembersList
;
55 use Galette\Repository\Groups
;
56 use Galette\Repository\Members
;
57 use Galette\Repository\PaymentTypes
;
58 use Galette\Repository\Titles
;
62 * Galette members controller
64 * @category Controllers
65 * @name GaletteController
67 * @author Johan Cwiklinski <johan@x-tnd.be>
68 * @copyright 2019-2021 The Galette Team
69 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
70 * @link http://galette.tuxfamily.org
71 * @since Available since 0.9.4dev - 2019-12-02
74 class MembersController
extends CrudController
77 private $is_self_membership = false;
84 * @param Request $request PSR Request
85 * @param Response $response PSR Response
89 public function add(Request
$request, Response
$response): Response
91 return $this->edit($request, $response, null, 'add');
97 * @param Request $request PSR Request
98 * @param Response $response PSR Response
102 public function addChild(Request
$request, Response
$response): Response
104 return $this->edit($request, $response, null, 'addchild');
108 * Self subscription page
110 * @param Request $request PSR Request
111 * @param Response $response PSR Response
115 public function selfSubscribe(Request
$request, Response
$response): Response
117 if (!$this->preferences
->pref_bool_selfsubscribe ||
$this->login
->isLogged()) {
120 ->withHeader('Location', $this->router
->pathFor('slash'));
123 if ($this->session
->member
!== null) {
124 $member = $this->session
->member
;
125 $this->session
->member
= null;
127 $member = new Adherent($this->zdb
);
128 $member->enableDep('dynamics');
131 //mark as self membership
132 $member->setSelfMembership();
134 // flagging required fields
135 $fc = $this->fields_config
;
136 $form_elements = $fc->getFormElements($this->login
, true, true);
140 $members = $m->getSelectizedMembers(
142 $member->hasParent() ?
$member->parent
->id
: null
147 'filters' => $m->getFilters(),
148 'count' => $m->getCount()
152 if (count($members)) {
153 $params['members']['list'] = $members;
156 $gaptcha = new Gaptcha($this->i18n
);
157 $this->session
->gaptcha
= $gaptcha;
163 'page_title' => _T("Subscription"),
164 'parent_tpl' => 'public_page.tpl',
167 'autocomplete' => true,
170 'titles_list' => Titles
::getList($this->zdb
),
171 'fieldsets' => $form_elements['fieldsets'],
172 'hidden_elements' => $form_elements['hiddens'],
174 'gaptcha' => $gaptcha
183 * @param Request $request PSR Request
184 * @param Response $response PSR Response
188 public function doAdd(Request
$request, Response
$response): Response
190 return $this->store($request, $response);
194 * Self subscription add action
196 * @param Request $request PSR Request
197 * @param Response $response PSR Response
201 public function doSelfSubscribe(Request
$request, Response
$response): Response
203 $this->setSelfMembership();
204 return $this->doAdd($request, $response);
211 * @param Request $request PSR Request
212 * @param Response $response PSR Response
213 * @param integer $id_adh Member ID to duplicate
217 public function duplicate(Request
$request, Response
$response, int $id_adh): Response
219 $adh = new Adherent($this->zdb
, $id_adh, ['dynamics' => true, 'parent' => true]);
220 $adh->setDuplicate();
222 //store entity in session
223 $this->session
->member
= $adh;
227 ->withHeader('Location', $this->router
->pathFor('addMember'));
234 * Display member card
236 * @param Request $request PSR Request
237 * @param Response $response PSR Response
238 * @param integer $id Member ID
242 public function show(Request
$request, Response
$response, int $id): Response
244 $member = new Adherent($this->zdb
);
249 if (!$member->canShow($this->login
)) {
250 $this->flash
->addMessage(
252 _T("You do not have permission for requested URL.")
259 $this->router
->pathFor('me')
263 if ($member->id
== null) {
264 //member does not exists!
265 $this->flash
->addMessage(
267 str_replace('%id', $id, _T("No member #%id."))
273 $this->router
->pathFor('slash')
277 // flagging fields visibility
278 $fc = $this->fields_config
;
279 $display_elements = $fc->getDisplayElements($this->login
);
286 'page_title' => _T("Member Profile"),
288 'pref_lang' => $this->i18n
->getNameFromId($member->language
),
289 'pref_card_self' => $this->preferences
->pref_card_self
,
290 'groups' => Groups
::getSimpleList(),
292 'display_elements' => $display_elements
301 * @param Request $request PSR Request
302 * @param Response $response PSR Response
306 public function showMe(Request
$request, Response
$response): Response
308 if ($this->login
->isSuperAdmin()) {
311 ->withHeader('Location', $this->router
->pathFor('slash'));
313 return $this->show($request, $response, $this->login
->id
);
317 * Public pages (trombinoscope, public list)
319 * @param Request $request PSR Request
320 * @param Response $response PSR Response
321 * @param string $option One of 'page' or 'order'
322 * @param string|integer $value Value of the option
323 * @param string $type List type (either list or trombi)
327 public function publicList(
334 $varname = 'public_filter_' . $type;
335 if (isset($this->session
->$varname)) {
336 $filters = $this->session
->$varname;
338 $filters = new MembersList();
341 if ($option !== null) {
344 $filters->current_page
= (int)$value;
347 $filters->orderby
= $value;
352 $m = new Members($filters);
353 $members = $m->getPublicList($type === 'trombi');
355 $this->session
->$varname = $filters;
357 //assign pagination variables to the template and add pagination links
358 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
363 ($type === 'list' ?
'liste_membres' : 'trombinoscope') . '.tpl',
365 'page_title' => ($type === 'list' ?
_T("Members list") : _T('Trombinoscope')),
366 'additionnal_html_class' => ($type === 'list' ?
'' : 'trombinoscope'),
368 'members' => $members,
369 'nb_members' => $m->getCount(),
370 'filters' => $filters
377 * Public pages (trombinoscope, public list)
379 * @param Request $request PSR Request
380 * @param Response $response PSR Response
381 * @param string $type Type
385 public function filterPublicList(Request
$request, Response
$response, string $type): Response
387 $post = $request->getParsedBody();
389 $varname = 'public_filter_' . $type;
390 if (isset($this->session
->$varname)) {
391 $filters = $this->session
->$varname;
393 $filters = new MembersList();
396 //reintialize filters
397 if (isset($post['clear_filter'])) {
400 //number of rows to show
401 if (isset($post['nbshow'])) {
402 $filters->show
= (int)$post['nbshow'];
406 $this->session
->$varname = $filters;
410 ->withHeader('Location', $this->router
->pathFor('publicList', ['type' => $type]));
416 * @param Request $request PSR Request
417 * @param Response $response PSR Response
418 * @param integer $id Member ID
419 * @param integer $fid Dynamic fields ID
420 * @param integer $pos Dynamic field position
421 * @param string $name File name
425 public function getDynamicFile(
433 $member = new Adherent($this->zdb
);
436 ->enableDep('dynamics')
440 if (!$member->canShow($this->login
)) {
441 $fields = $member->getDynamicFields()->getFields();
442 if (!isset($fields[$fid])) {
443 //field does not exists or access is forbidden
450 if ($denied === true) {
451 $this->flash
->addMessage(
453 _T("You do not have permission for requested URL.")
459 $this->router
->pathFor(
466 $filename = str_replace(
477 'member_%mid_field_%fid_value_%pos'
480 if (file_exists(GALETTE_FILES_PATH
. $filename)) {
481 $type = File
::getMimeType(GALETTE_FILES_PATH
. $filename);
483 $response = $response->withHeader('Content-Description', 'File Transfer')
484 ->withHeader('Content-Type', $type)
485 ->withHeader('Content-Disposition', 'attachment;filename="' . $name . '"')
486 ->withHeader('Pragma', 'no-cache')
487 ->withHeader('Content-Transfer-Encoding', 'binary')
488 ->withHeader('Expires', '0')
489 ->withHeader('Cache-Control', 'must-revalidate')
490 ->withHeader('Pragma', 'public');
492 $stream = fopen('php://memory', 'r+');
493 fwrite($stream, file_get_contents(GALETTE_FILES_PATH
. $filename));
496 return $response->withBody(new \Slim\Http\
Stream($stream));
499 'A request has been made to get a dynamic file named `' .
500 $filename . '` that does not exists.',
504 $this->flash
->addMessage(
506 _T("The file does not exists or cannot be read :(")
512 $this->router
->pathFor('member', ['id' => $id])
520 * @param Request $request PSR Request
521 * @param Response $response PSR Response
522 * @param string $option One of 'page' or 'order'
523 * @param string|integer $value Value of the option
527 public function list(Request
$request, Response
$response, $option = null, $value = null): Response
529 if (isset($this->session
->filter_members
)) {
530 $filters = $this->session
->filter_members
;
532 $filters = new MembersList();
535 if ($option !== null) {
538 $filters->current_page
= (int)$value;
541 $filters->orderby
= $value;
546 $members = new Members($filters);
548 $members_list = array();
549 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
550 $members_list = $members->getMembersList(true);
552 $members_list = $members->getManagedMembersList(true);
555 $groups = new Groups($this->zdb
, $this->login
);
556 $groups_list = $groups->getList();
558 //assign pagination variables to the template and add pagination links
559 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
560 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
562 $this->session
->filter_members
= $filters;
567 'gestion_adherents.tpl',
569 'page_title' => _T("Members management"),
570 'require_mass' => true,
571 'members' => $members_list,
572 'filter_groups_options' => $groups_list,
573 'nb_members' => $members->getCount(),
574 'filters' => $filters,
575 'adv_filters' => $filters instanceof AdvancedMembersList
,
576 'galette_list' => $this->lists_config
->getDisplayElements($this->login
)
585 * @param Request $request PSR Request
586 * @param Response $response PSR Response
590 public function filter(Request
$request, Response
$response): Response
592 $post = $request->getParsedBody();
593 if (isset($this->session
->filter_members
)) {
594 //CAUTION: this one may be simple or advanced, display must change
595 $filters = $this->session
->filter_members
;
597 $filters = new MembersList();
600 //reintialize filters
601 if (isset($post['clear_filter'])) {
602 $filters = new MembersList();
603 } elseif (isset($post['clear_adv_filter'])) {
604 $this->session
->filter_members
= null;
605 unset($this->session
->filter_members
);
609 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
610 } elseif (isset($post['adv_criteria'])) {
613 ->withHeader('Location', $this->router
->pathFor('advanced-search'));
616 if (isset($post['filter_str'])) { //filter search string
617 $filters->filter_str
= stripslashes(
618 htmlspecialchars($post['filter_str'], ENT_QUOTES
)
622 if (isset($post['field_filter'])) {
623 if (is_numeric($post['field_filter'])) {
624 $filters->field_filter
= $post['field_filter'];
627 //membership to filter
628 if (isset($post['membership_filter'])) {
629 if (is_numeric($post['membership_filter'])) {
630 $filters->membership_filter
631 = $post['membership_filter'];
634 //account status to filter
635 if (isset($post['filter_account'])) {
636 if (is_numeric($post['filter_account'])) {
637 $filters->filter_account
= $post['filter_account'];
641 if (isset($post['email_filter'])) {
642 $filters->email_filter
= (int)$post['email_filter'];
646 isset($post['group_filter'])
647 && $post['group_filter'] > 0
649 $filters->group_filter
= (int)$post['group_filter'];
651 //number of rows to show
652 if (isset($post['nbshow'])) {
653 $filters->show
= (int)$post['nbshow'];
656 if (isset($post['advanced_filtering'])) {
657 if (!$filters instanceof AdvancedMembersList
) {
658 $filters = new AdvancedMembersList($filters);
662 unset($post['advanced_filtering']);
664 foreach ($post as $k => $v) {
665 if (strpos($k, 'free_', 0) === 0) {
668 foreach ($post['free_field'] as $f) {
671 && trim($post['free_text'][$i]) !== ''
673 $fs_search = htmlspecialchars($post['free_text'][$i], ENT_QUOTES
);
675 = (int)$post['free_logical_operator'][$i];
677 = (int)$post['free_query_operator'][$i];
678 $type = (int)$post['free_type'][$i];
683 'search' => $fs_search,
687 $filters->free_search
= $fs;
693 } elseif ($k == 'groups_search') {
695 $filters->groups_search_log_op
= (int)$post['groups_logical_operator'];
696 foreach ($post['groups_search'] as $g) {
697 if (trim($g) !== '') {
702 $filters->groups_search
= $gs;
708 case 'contrib_min_amount':
709 case 'contrib_max_amount':
710 if (trim($v) !== '') {
723 if (isset($post['savesearch'])) {
728 $this->router
->pathFor(
735 $this->session
->filter_members
= $filters;
739 ->withHeader('Location', $this->router
->pathFor('members'));
743 * Advanced search page
745 * @param Request $request PSR Request
746 * @param Response $response PSR Response
750 public function advancedSearch(Request
$request, Response
$response): Response
752 if (isset($this->session
->filter_members
)) {
753 $filters = $this->session
->filter_members
;
754 if (!$filters instanceof AdvancedMembersList
) {
755 $filters = new AdvancedMembersList($filters);
758 $filters = new AdvancedMembersList();
761 $groups = new Groups($this->zdb
, $this->login
);
762 $groups_list = $groups->getList();
764 //we want only visible fields
765 $fields = $this->members_fields
;
766 $fc = $this->fields_config
;
767 $fc->filterVisible($this->login
, $fields);
769 //add status label search
770 if ($pos = array_search(Status
::PK
, array_keys($fields))) {
771 $fields = array_slice($fields, 0, $pos, true) +
772 ['status_label' => ['label' => _T('Status label')]] +
773 array_slice($fields, $pos, count($fields) - 1, true);
777 $member = new Adherent($this->zdb
);
780 ->enableDep('dynamics')
781 ->loadFromLoginOrMail($this->login
->login
);
782 $adh_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $member);
784 $contrib = new Contribution($this->zdb
, $this->login
);
785 $contrib_dynamics = new DynamicFieldsHandle($this->zdb
, $this->login
, $contrib);
788 $statuts = new Status($this->zdb
);
790 //Contributions types
791 $ct = new ContributionsTypes($this->zdb
);
794 $ptypes = new PaymentTypes(
799 $ptlist = $ptypes->getList();
801 $filters->setViewCommonsFilters($this->preferences
, $this->view
->getSmarty());
806 'advanced_search.tpl',
808 'page_title' => _T("Advanced search"),
809 'filter_groups_options' => $groups_list,
810 'search_fields' => $fields,
811 'adh_dynamics' => $adh_dynamics->getFields(),
812 'contrib_dynamics' => $contrib_dynamics->getFields(),
813 'statuts' => $statuts->getList(),
814 'contributions_types' => $ct->getList(),
815 'filters' => $filters,
816 'payments_types' => $ptlist
823 * Members list for ajax
825 * @param Request $request PSR Request
826 * @param Response $response PSR Response
827 * @param string|null $option One of 'page' or 'order'
828 * @param string|integer $value Value of the option
832 public function ajaxList(Request
$request, Response
$response, string $option = null, $value = null): Response
834 $post = $request->getParsedBody();
836 $filters = $this->session
->ajax_members_filters ??
new MembersList();
838 if ($option == 'page') {
839 $filters->current_page
= (int)$value;
842 //numbers of rows to display
843 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
844 $filters->show
= (int)$post['nbshow'];
847 $members = new Members($filters);
848 if (!$this->login
->isAdmin() && !$this->login
->isStaff()) {
849 if ($this->login
->isGroupManager()) {
850 $members_list = $members->getManagedMembersList(true);
855 [$this->login
->id
, $this->login
->login
],
856 'Trying to list group members without access from #%id (%login)'
860 throw new \
Exception('Access denied.');
863 $members_list = $members->getMembersList(true);
866 //assign pagination variables to the template and add pagination links
867 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty(), false);
869 $this->session
->ajax_members_filters
= $filters;
871 $selected_members = null;
872 $unreachables_members = null;
873 if (!isset($post['from'])) {
874 $mailing = $this->session
->mailing
;
875 if (!isset($post['members'])) {
876 $selected_members = $mailing->recipients
;
877 $unreachables_members = $mailing->unreachables
;
880 $selected_members = $m->getArrayList($post['members']);
881 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
882 $unreachables_members = $m->getArrayList($post['unreachables']);
886 switch ($post['from']) {
888 if (!isset($post['gid'])) {
890 'Trying to list group members with no group id provided',
893 throw new \
Exception('A group id is required.');
895 if (!isset($post['members'])) {
896 $group = new Group((int)$post['gid']);
897 $selected_members = array();
898 if (!isset($post['mode']) ||
$post['mode'] == 'members') {
899 $selected_members = $group->getMembers();
900 } elseif ($post['mode'] == 'managers') {
901 $selected_members = $group->getManagers();
904 'Trying to list group members with unknown mode',
907 throw new \
Exception('Unknown mode.');
911 $selected_members = $m->getArrayList($post['members']);
912 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
913 $unreachables_members = $m->getArrayList($post['unreachables']);
918 if (!isset($post['id_adh'])) {
919 throw new \
RuntimeException(
920 'Current selected member must be excluded while attaching!'
928 'filters' => $filters,
929 'members_list' => $members_list,
930 'selected_members' => $selected_members,
931 'unreachables_members' => $unreachables_members
934 if (isset($post['multiple'])) {
935 $params['multiple'] = true;
938 if (isset($post['gid'])) {
939 $params['the_id'] = (int)$post['gid'];
942 if (isset($post['id_adh'])) {
943 $params['excluded'] = (int)$post['id_adh'];
956 * Batch actions handler
958 * @param Request $request PSR Request
959 * @param Response $response PSR Response
963 public function handleBatch(Request
$request, Response
$response): Response
965 $post = $request->getParsedBody();
967 if (isset($post['member_sel'])) {
968 if (isset($this->session
->filter_members
)) {
969 $filters = $this->session
->filter_members
;
971 $filters = new MembersList();
974 $filters->selected
= $post['member_sel'];
975 $this->session
->filter_members
= $filters;
977 if (isset($post['cards'])) {
980 ->withHeader('Location', $this->router
->pathFor('pdf-members-cards'));
983 if (isset($post['labels'])) {
986 ->withHeader('Location', $this->router
->pathFor('pdf-members-labels'));
989 if (isset($post['mailing'])) {
992 ->withHeader('Location', $this->router
->pathFor('mailing') . '?mailing_new=new');
995 if (isset($post['attendance_sheet'])) {
998 ->withHeader('Location', $this->router
->pathFor('attendance_sheet_details'));
1001 if (isset($post['csv'])) {
1004 ->withHeader('Location', $this->router
->pathFor('csv-memberslist'));
1007 if (isset($post['delete'])) {
1010 ->withHeader('Location', $this->router
->pathFor('removeMembers'));
1013 if (isset($post['masschange'])) {
1016 ->withHeader('Location', $this->router
->pathFor('masschangeMembers'));
1019 if (isset($post['masscontributions'])) {
1022 ->withHeader('Location', $this->router
->pathFor('massAddContributionsChooseType'));
1025 throw new \
RuntimeException('Does not know what to batch :(');
1027 $this->flash
->addMessage(
1029 _T("No member was selected, please check at least one name.")
1034 ->withHeader('Location', $this->router
->pathFor('members'));
1044 * @param Request $request PSR Request
1045 * @param Response $response PSR Response
1046 * @param mixed $id Member id/array of members id
1047 * @param string $action null or 'add'
1051 public function edit(
1055 string $action = 'edit'
1057 //instantiate member object
1058 $member = new Adherent($this->zdb
);
1059 $member->enableAllDeps()->load($id);
1061 if ($this->session
->member
!== null) {
1062 //retrieve from session, in add or edit
1063 $member = $this->session
->member
;
1064 $this->session
->member
= null;
1069 //load requested member
1071 $can = $member->canEdit($this->login
);
1073 $can = $member->canCreate($this->login
);
1077 $this->flash
->addMessage(
1079 _T("You do not have permission for requested URL.")
1085 $this->router
->pathFor('me')
1089 //if adding a child, force parent here
1090 if ($action === 'addchild') {
1091 $member->setParent((int)$this->login
->id
);
1094 // flagging required fields
1095 $fc = $this->fields_config
;
1097 // password required if we create a new member
1099 $fc->setNotRequired('mdp_adh');
1102 //handle requirements for parent fields
1103 $parent_fields = $member->getParentFields();
1104 $tpl_parent_fields = []; //for JS when detaching
1105 foreach ($parent_fields as $field) {
1106 if ($fc->isRequired($field)) {
1107 $tpl_parent_fields[] = $field;
1108 if ($member->hasParent()) {
1109 $fc->setNotRequired($field);
1114 // flagging required fields invisible to members
1115 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1116 $fc->setNotRequired('activite_adh');
1117 $fc->setNotRequired('id_statut');
1120 // template variable declaration
1121 $title = _T("Member Profile");
1122 if ($member->id
!= '') {
1123 $title .= ' (' . _T("modification") . ')';
1125 $title .= ' (' . _T("creation") . ')';
1129 $statuts = new Status($this->zdb
);
1132 $groups = new Groups($this->zdb
, $this->login
);
1133 $groups_list = $groups->getSimpleList(true);
1135 $form_elements = $fc->getFormElements(
1143 if ($member->hasParent()) {
1144 $pid = ($member->parent
instanceof Adherent ?
$member->parent
->id
: $member->parent
);
1146 $members = $m->getSelectizedMembers(
1151 $route_params['members'] = [
1152 'filters' => $m->getFilters(),
1153 'count' => $m->getCount()
1156 if (count($members)) {
1157 $route_params['members']['list'] = $members;
1161 $this->view
->render(
1165 'parent_tpl' => 'page.tpl',
1166 'autocomplete' => true,
1167 'page_title' => $title,
1168 'member' => $member,
1169 'self_adh' => false,
1170 // pseudo random int
1172 'titles_list' => Titles
::getList($this->zdb
),
1173 'statuts' => $statuts->getList(),
1174 'groups' => $groups_list,
1175 'fieldsets' => $form_elements['fieldsets'],
1176 'hidden_elements' => $form_elements['hiddens'],
1177 'parent_fields' => $tpl_parent_fields,
1178 'addchild' => ($action === 'addchild')
1187 * @param Request $request PSR Request
1188 * @param Response $response PSR Response
1189 * @param integer $id Member id
1193 public function doEdit(Request
$request, Response
$response, int $id): Response
1195 return $this->store($request, $response);
1199 * Massive change page
1201 * @param Request $request PSR Request
1202 * @param Response $response PSR Response
1206 public function massChange(Request
$request, Response
$response): Response
1208 $filters = $this->session
->filter_members
;
1211 'id' => $filters->selected
,
1212 'redirect_uri' => $this->router
->pathFor('members')
1215 $fc = $this->fields_config
;
1216 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1219 $member = new Adherent($this->zdb
);
1220 $member->disableAllDeps()->enableDep('dynamics');
1223 $statuts = new Status($this->zdb
);
1226 $this->view
->render(
1228 'mass_change_members.tpl',
1230 'mode' => $request->isXhr() ?
'ajax' : '',
1231 'page_title' => str_replace(
1234 _T('Mass change %count members')
1236 'form_url' => $this->router
->pathFor('masschangeMembersReview'),
1237 'cancel_uri' => $this->router
->pathFor('members'),
1239 'member' => $member,
1240 'fieldsets' => $form_elements['fieldsets'],
1241 'titles_list' => Titles
::getList($this->zdb
),
1242 'statuts' => $statuts->getList(),
1243 'require_mass' => true
1250 * Massive changes validation page
1252 * @param Request $request PSR Request
1253 * @param Response $response PSR Response
1257 public function validateMassChange(Request
$request, Response
$response): Response
1259 $post = $request->getParsedBody();
1261 if (!isset($post['confirm'])) {
1262 $this->flash
->addMessage(
1264 _T("Mass changes has not been confirmed!")
1267 //we want only visibles fields
1268 $fc = $this->fields_config
;
1269 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1272 foreach ($form_elements['fieldsets'] as $form_element) {
1273 foreach ($form_element->elements
as $field) {
1275 isset($post['mass_' . $field->field_id
])
1276 && (isset($post[$field->field_id
]) ||
$field->type
=== FieldsConfig
::TYPE_BOOL
)
1278 $changes[$field->field_id
] = [
1279 'label' => $field->label
,
1280 'value' => $post[$field->field_id
] ??
0
1286 //handle dynamic fields
1287 $member = new Adherent($this->zdb
);
1292 $this->members_fields
,
1295 $dynamic_fields = $member->getDynamicFields()->getFields();
1296 foreach ($dynamic_fields as $field) {
1297 $mass_id = 'mass_info_field_' . $field->getId();
1298 $field_id = 'info_field_' . $field->getId() . '_1';
1300 isset($post[$mass_id])
1301 && (isset($post[$field_id]) ||
$field instanceof Boolean
)
1303 $changes[$field_id] = [
1304 'label' => $field->getName(),
1305 'value' => $post[$field_id] ??
0
1311 $filters = $this->session
->filter_members
;
1313 'id' => $filters->selected
,
1314 'redirect_uri' => $this->router
->pathFor('members')
1318 $statuts = new Status($this->zdb
);
1321 $this->view
->render(
1323 'mass_change_members.tpl',
1325 'mode' => $request->isXhr() ?
'ajax' : '',
1326 'page_title' => str_replace(
1329 _T('Review mass change %count members')
1331 'form_url' => $this->router
->pathFor('massstoremembers'),
1332 'cancel_uri' => $this->router
->pathFor('members'),
1334 'titles_list' => Titles
::getList($this->zdb
),
1335 'statuts' => $statuts->getList(),
1336 'changes' => $changes
1343 * Do massive changes
1345 * @param Request $request PSR Request
1346 * @param Response $response PSR Response
1350 public function doMassChange(Request
$request, Response
$response): Response
1352 $post = $request->getParsedBody();
1353 $redirect_url = $post['redirect_uri'];
1354 $error_detected = [];
1356 $dynamic_fields = null;
1358 unset($post['redirect_uri']);
1359 if (!isset($post['confirm'])) {
1360 $error_detected[] = _T("Mass changes has not been confirmed!");
1362 unset($post['confirm']);
1366 $fc = $this->fields_config
;
1367 $form_elements = $fc->getMassiveFormElements($this->members_fields
, $this->login
);
1368 $disabled = $this->members_fields
;
1369 foreach (array_keys($post) as $key) {
1371 foreach ($form_elements['fieldsets'] as $fieldset) {
1372 if (isset($fieldset->elements
[$key])) {
1379 //try on dynamic fields
1380 if ($dynamic_fields === null) {
1381 //handle dynamic fields
1382 $member = new Adherent($this->zdb
);
1387 $this->members_fields
,
1390 $dynamic_fields = $member->getDynamicFields()->getFields();
1392 foreach ($dynamic_fields as $field) {
1393 $field_id = 'info_field_' . $field->getId() . '_1';
1394 if ($key == $field_id) {
1403 'Permission issue mass editing field ' . $key,
1408 unset($disabled[$key]);
1412 if (!count($post)) {
1413 $error_detected[] = _T("Nothing to do!");
1415 foreach ($ids as $id) {
1416 $is_manager = !$this->login
->isAdmin()
1417 && !$this->login
->isStaff()
1418 && $this->login
->isGroupManager();
1419 $member = new Adherent($this->zdb
);
1420 $member->disableAllDeps();
1422 $member->enableDep('groups');
1424 $member->load((int)$id);
1425 $member->setDependencies(
1427 $this->members_fields
,
1430 if (!$member->canEdit($this->login
)) {
1434 $valid = $member->check($post, [], $disabled);
1435 if ($valid === true) {
1436 $done = $member->store();
1438 $error_detected[] = _T("An error occurred while storing the member.");
1443 $error_detected = array_merge($error_detected, $valid);
1449 if ($mass == 0 && !count($error_detected)) {
1450 $error_detected[] = _T('Something went wront during mass edition!');
1452 $this->flash
->addMessage(
1457 _T('%count members has been changed successfully!')
1462 if (count($error_detected) > 0) {
1463 foreach ($error_detected as $error) {
1464 $this->flash
->addMessage(
1471 if (!$request->isXhr()) {
1474 ->withHeader('Location', $redirect_url);
1476 return $response->withJson(
1478 'success' => count($error_detected) === 0
1487 * @param Request $request PSR Request
1488 * @param Response $response PSR Response
1492 public function store(Request
$request, Response
$response): Response
1494 if (!$this->preferences
->pref_bool_selfsubscribe
&& !$this->login
->isLogged()) {
1497 ->withHeader('Location', $this->router
->pathFor('slash'));
1500 $post = $request->getParsedBody();
1501 $member = new Adherent($this->zdb
);
1506 $this->members_fields
,
1510 $success_detected = [];
1511 $warning_detected = [];
1512 $error_detected = [];
1514 if ($this->isSelfMembership() && !isset($post[Adherent
::PK
])) {
1515 //mark as self membership
1516 $member->setSelfMembership();
1519 $gaptcha = $this->session
->gaptcha
;
1520 if (!$gaptcha->check($post['gaptcha'])) {
1521 $error_detected[] = _T('Invalid captcha');
1526 if (isset($post['id_adh'])) {
1527 $member->load((int)$post['id_adh']);
1528 if (!$member->canEdit($this->login
)) {
1529 //redirection should have been done before. Just throw an Exception.
1530 throw new \
RuntimeException(
1534 'No right to store member #%id'
1539 if ($member->id
!= '') {
1540 $member->load($this->login
->id
);
1544 // flagging required fields
1545 $fc = $this->fields_config
;
1547 // password required if we create a new member but not from self subscription
1548 if ($member->id
!= '' ||
$this->isSelfMembership()) {
1549 $fc->setNotRequired('mdp_adh');
1553 $member->hasParent() && !isset($post['detach_parent'])
1554 ||
isset($post['parent_id']) && !empty($post['parent_id'])
1556 $parent_fields = $member->getParentFields();
1557 foreach ($parent_fields as $field) {
1558 if ($fc->isRequired($field)) {
1559 $fc->setNotRequired($field);
1564 // flagging required fields invisible to members
1565 if ($this->login
->isAdmin() ||
$this->login
->isStaff()) {
1566 $fc->setNotRequired('activite_adh');
1567 $fc->setNotRequired('id_statut');
1570 $form_elements = $fc->getFormElements(
1573 $this->isSelfMembership()
1575 $fieldsets = $form_elements['fieldsets'];
1576 $required = array();
1577 $disabled = array();
1579 foreach ($fieldsets as $category) {
1580 foreach ($category->elements
as $field) {
1581 if ($field->required
== true) {
1582 $required[$field->field_id
] = true;
1584 if ($field->disabled
== true) {
1585 $disabled[$field->field_id
] = true;
1586 } elseif (!isset($post[$field->field_id
])) {
1587 switch ($field->field_id
) {
1588 //unchecked booleans are not sent from form
1589 case 'bool_admin_adh':
1590 case 'bool_exempt_adh':
1591 case 'bool_display_info':
1592 $post[$field->field_id
] = 0;
1599 $real_requireds = array_diff(array_keys($required), array_keys($disabled));
1602 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1603 if (!count($real_requireds) ||
isset($post[array_shift($real_requireds)])) {
1605 $valid = $member->check($post, $required, $disabled);
1606 if ($valid !== true) {
1607 $error_detected = array_merge($error_detected, $valid);
1610 if (count($error_detected) == 0) {
1611 //all goes well, we can proceed
1614 if ($member->id
== '') {
1618 // send email to member
1619 if ($this->isSelfMembership() ||
isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1620 $member->setSendmail(); //flag to send creation email
1623 $store = $member->store();
1624 if ($store === true) {
1625 //member has been stored :)
1627 if ($this->isSelfMembership()) {
1628 $success_detected[] = _T("Your account has been created!");
1630 $this->preferences
->pref_mail_method
> GaletteMail
::METHOD_DISABLED
1631 && $member->getEmail() != ''
1633 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1636 $success_detected[] = _T("New member has been successfully added.");
1639 $success_detected[] = _T("Member account has been modified.");
1642 //store requested groups
1643 $groups_adh = $post['groups_adh'] ??
null;
1644 $managed_groups_adh = $post['groups_managed_adh'] ??
null;
1646 //add/remove user from groups
1647 $add_groups = Groups
::addMemberToGroups(
1652 if ($add_groups === false) {
1653 $error_detected[] = _T("An error occurred adding member to its groups.");
1656 //add/remove manager from groups
1657 $add_groups = Groups
::addMemberToGroups(
1659 $managed_groups_adh,
1662 $member->loadGroups();
1664 if ($add_groups === false) {
1665 $error_detected[] = _T("An error occurred adding member to its groups as manager.");
1668 //something went wrong :'(
1669 $error_detected[] = _T("An error occurred while storing the member.");
1673 if (count($error_detected) === 0) {
1674 $files_res = $member->handleFiles($_FILES);
1675 if (is_array($files_res)) {
1676 $error_detected = array_merge($error_detected, $files_res);
1679 if (isset($post['del_photo'])) {
1680 if (!$member->picture
->delete($member->id
)) {
1681 $error_detected[] = _T("Delete failed");
1682 $str_adh = $member->id
. ' (' . $member->sname
. ' ' . ')';
1684 'Unable to delete picture for member ' . $str_adh,
1691 if (count($error_detected) > 0) {
1692 foreach ($error_detected as $error) {
1693 if (strpos($error, '%member_url_') !== false) {
1694 preg_match('/%member_url_(\d+)/', $error, $matches);
1695 $url = $this->router
->pathFor('member', ['id' => $matches[1]]);
1696 $error = str_replace(
1697 '%member_url_' . $matches[1],
1702 $this->flash
->addMessage(
1709 if (count($warning_detected) > 0) {
1710 foreach ($warning_detected as $warning) {
1711 $this->flash
->addMessage(
1717 if (count($success_detected) > 0) {
1718 foreach ($success_detected as $success) {
1719 $this->flash
->addMessage(
1726 if (count($error_detected) === 0) {
1727 if ($this->isSelfMembership()) {
1728 $redirect_url = $this->router
->pathFor('login');
1730 isset($post['redirect_on_create'])
1731 && $post['redirect_on_create'] > Adherent
::AFTER_ADD_DEFAULT
1733 switch ($post['redirect_on_create']) {
1734 case Adherent
::AFTER_ADD_TRANS
:
1735 $redirect_url = $this->router
->pathFor('addTransaction');
1737 case Adherent
::AFTER_ADD_NEW
:
1738 $redirect_url = $this->router
->pathFor('addMember');
1740 case Adherent
::AFTER_ADD_SHOW
:
1741 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1743 case Adherent
::AFTER_ADD_LIST
:
1744 $redirect_url = $this->router
->pathFor('members');
1746 case Adherent
::AFTER_ADD_HOME
:
1747 $redirect_url = $this->router
->pathFor('slash');
1750 } elseif (!isset($post['id_adh']) && !$member->isDueFree()) {
1751 $redirect_url = $this->router
->pathFor(
1754 ) . '?id_adh=' . $member->id
;
1756 $redirect_url = $this->router
->pathFor('member', ['id' => $member->id
]);
1759 //store entity in session
1760 $this->session
->member
= $member;
1762 if ($this->isSelfMembership()) {
1763 $redirect_url = $this->router
->pathFor('subscribe');
1766 $redirect_url = $this->router
->pathFor(
1768 ['id' => $member->id
]
1771 $redirect_url = $this->router
->pathFor((isset($post['addchild']) ?
'addMemberChild' : 'addMember'));
1779 ->withHeader('Location', $redirect_url);
1787 * Get redirection URI
1789 * @param array $args Route arguments
1793 public function redirectUri(array $args)
1795 return $this->router
->pathFor('members');
1801 * @param array $args Route arguments
1805 public function formUri(array $args)
1807 return $this->router
->pathFor(
1817 * In simple cases, we get the ID in the route arguments; but for
1818 * batchs, it should be found elsewhere.
1819 * In post values, we look for id key, as well as all {sthing}_sel keys (like members_sel or contrib_sel)
1821 * @param array $args Request arguments
1822 * @param array $post POST values
1824 * @return null|integer|integer[]
1826 protected function getIdsToRemove(&$args, $post)
1828 if (isset($args['id'])) {
1831 $filters = $this->session
->filter_members
;
1832 return $filters->selected
;
1837 * Get confirmation removal page title
1839 * @param array $args Route arguments
1843 public function confirmRemoveTitle(array $args)
1845 if (isset($args['id_adh']) ||
isset($args['id'])) {
1846 //one member removal
1847 $id_adh = $args['id_adh'] ??
$args['id'];
1848 $adh = new Adherent($this->zdb
, (int)$id_adh);
1850 _T('Remove member %1$s'),
1854 //batch members removal
1855 $filters = $this->session
->filter_members
;
1858 count($filters->selected
),
1859 _T('You are about to remove %count members.')
1867 * @param array $args Route arguments
1868 * @param array $post POST values
1872 protected function doDelete(array $args, array $post)
1874 if (isset($this->session
->filter_members
)) {
1875 $filters = $this->session
->filter_members
;
1877 $filters = new MembersList();
1879 $members = new Members($filters);
1881 if (!is_array($post['id'])) {
1882 $ids = (array)$post['id'];
1887 return $members->removeMembers($ids);
1893 * Set self memebrship flag
1895 * @return MembersController
1897 private function setSelfMembership(): MembersController
1899 $this->is_self_membership
= true;
1904 * Is self membership?
1908 private function isSelfMembership(): bool
1910 return $this->is_self_membership
;