]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/Crud/MembersController.php
edf2a64abb5e70d2a01de1e5811f79132f4e8f27
[galette.git] / galette / lib / Galette / Controllers / Crud / MembersController.php
1 <?php
2
3 /**
4 * Copyright © 2003-2024 The Galette Team
5 *
6 * This file is part of Galette (https://galette.eu).
7 *
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 namespace Galette\Controllers\Crud;
23
24 use Galette\Controllers\CrudController;
25 use Galette\DynamicFields\Boolean;
26 use Galette\Features\BatchList;
27 use Slim\Psr7\Request;
28 use Slim\Psr7\Response;
29 use Galette\Core\GaletteMail;
30 use Galette\Core\Gaptcha;
31 use Galette\Entity\Adherent;
32 use Galette\Entity\Contribution;
33 use Galette\Entity\ContributionsTypes;
34 use Galette\Entity\DynamicFieldsHandle;
35 use Galette\Entity\Group;
36 use Galette\Entity\Status;
37 use Galette\Entity\FieldsConfig;
38 use Galette\Entity\Social;
39 use Galette\Filters\AdvancedMembersList;
40 use Galette\Filters\MembersList;
41 use Galette\Repository\Groups;
42 use Galette\Repository\Members;
43 use Galette\Repository\PaymentTypes;
44 use Galette\Repository\Titles;
45 use Analog\Analog;
46
47 /**
48 * Galette members controller
49 *
50 * @author Johan Cwiklinski <johan@x-tnd.be>
51 */
52
53 class MembersController extends CrudController
54 {
55 use BatchList;
56
57 /** @var bool */
58 private bool $is_self_membership = false;
59
60 // CRUD - Create
61
62 /**
63 * Add page
64 *
65 * @param Request $request PSR Request
66 * @param Response $response PSR Response
67 *
68 * @return Response
69 */
70 public function add(Request $request, Response $response): Response
71 {
72 return $this->edit($request, $response, null, 'add');
73 }
74
75 /**
76 * Add child page
77 *
78 * @param Request $request PSR Request
79 * @param Response $response PSR Response
80 *
81 * @return Response
82 */
83 public function addChild(Request $request, Response $response): Response
84 {
85 return $this->edit($request, $response, null, 'addchild');
86 }
87
88 /**
89 * Self subscription page
90 *
91 * @param Request $request PSR Request
92 * @param Response $response PSR Response
93 *
94 * @return Response
95 */
96 public function selfSubscribe(Request $request, Response $response): Response
97 {
98 if (!$this->preferences->pref_bool_selfsubscribe || $this->login->isLogged()) {
99 return $response
100 ->withStatus(301)
101 ->withHeader('Location', $this->routeparser->urlFor('slash'));
102 }
103
104 if ($this->session->member !== null) {
105 $member = $this->session->member;
106 $this->session->member = null;
107 } else {
108 $member = new Adherent($this->zdb);
109 $member->enableDep('dynamics');
110 }
111
112 //mark as self membership
113 $member->setSelfMembership();
114
115 // flagging required fields
116 $fc = $this->fields_config;
117 $form_elements = $fc->getFormElements($this->login, true, true);
118
119 // members
120 $m = new Members();
121 $members = $m->getDropdownMembers(
122 $this->zdb,
123 $this->login,
124 $member->hasParent() ? $member->parent->id : null
125 );
126
127 $params = [
128 'members' => [
129 'filters' => $m->getFilters(),
130 'count' => $m->getCount()
131 ]
132 ];
133
134 if (count($members)) {
135 $params['members']['list'] = $members;
136 }
137
138 $gaptcha = new Gaptcha($this->i18n);
139 $this->session->gaptcha = $gaptcha;
140
141 $titles = new Titles($this->zdb);
142
143 // display page
144 $this->view->render(
145 $response,
146 'pages/member_form.html.twig',
147 array(
148 'page_title' => _T("Subscription"),
149 'parent_tpl' => 'public_page.html.twig',
150 'member' => $member,
151 'self_adh' => true,
152 'autocomplete' => true,
153 'osocials' => new Social($this->zdb),
154 // pseudo random int
155 'time' => time(),
156 'titles_list' => $titles->getList(),
157 'fieldsets' => $form_elements['fieldsets'],
158 'hidden_elements' => $form_elements['hiddens'],
159 //self_adh specific
160 'gaptcha' => $gaptcha
161 ) + $params
162 );
163 return $response;
164 }
165
166 /**
167 * Add action
168 *
169 * @param Request $request PSR Request
170 * @param Response $response PSR Response
171 *
172 * @return Response
173 */
174 public function doAdd(Request $request, Response $response): Response
175 {
176 return $this->store($request, $response);
177 }
178
179 /**
180 * Self subscription add action
181 *
182 * @param Request $request PSR Request
183 * @param Response $response PSR Response
184 *
185 * @return Response
186 */
187 public function doSelfSubscribe(Request $request, Response $response): Response
188 {
189 $this->setSelfMembership();
190 return $this->doAdd($request, $response);
191 }
192
193
194 /**
195 * Duplicate action
196 *
197 * @param Request $request PSR Request
198 * @param Response $response PSR Response
199 * @param integer $id_adh Member ID to duplicate
200 *
201 * @return Response
202 */
203 public function duplicate(Request $request, Response $response, int $id_adh): Response
204 {
205 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true, 'parent' => true]);
206 $adh->setDuplicate();
207
208 //store entity in session
209 $this->session->member = $adh;
210
211 return $response
212 ->withStatus(301)
213 ->withHeader('Location', $this->routeparser->urlFor('addMember'));
214 }
215
216 // /CRUD - Create
217 // CRUD - Read
218
219 /**
220 * Display member card
221 *
222 * @param Request $request PSR Request
223 * @param Response $response PSR Response
224 * @param integer $id Member ID
225 *
226 * @return Response
227 */
228 public function show(Request $request, Response $response, int $id): Response
229 {
230 $member = new Adherent($this->zdb);
231 $member
232 ->enableAllDeps()
233 ->load($id);
234
235 if (!$member->canShow($this->login)) {
236 $this->flash->addMessage(
237 'error_detected',
238 _T("You do not have permission for requested URL.")
239 );
240
241 return $response
242 ->withStatus(301)
243 ->withHeader(
244 'Location',
245 $this->routeparser->urlFor('me')
246 );
247 }
248
249 if ($member->id == null) {
250 //member does not exist!
251 $this->flash->addMessage(
252 'error_detected',
253 str_replace('%id', (string)$id, _T("No member #%id."))
254 );
255
256 return $response
257 ->withHeader(
258 'Location',
259 $this->routeparser->urlFor('slash')
260 );
261 }
262
263 // flagging fields visibility
264 $fc = $this->fields_config;
265 $display_elements = $fc->getDisplayElements($this->login);
266
267 // display page
268 $this->view->render(
269 $response,
270 'pages/member_show.html.twig',
271 array(
272 'page_title' => _T("Member Profile"),
273 'member' => $member,
274 'pref_lang' => $this->i18n->getNameFromId($member->language),
275 'pref_card_self' => $this->preferences->pref_card_self,
276 'groups' => Groups::getSimpleList(),
277 'time' => time(),
278 'display_elements' => $display_elements,
279 'osocials' => new Social($this->zdb),
280 'navigate' => $this->handleNavigationLinks($member->id)
281 )
282 );
283 return $response;
284 }
285
286 /**
287 * Own card show
288 *
289 * @param Request $request PSR Request
290 * @param Response $response PSR Response
291 *
292 * @return Response
293 */
294 public function showMe(Request $request, Response $response): Response
295 {
296 if ($this->login->isSuperAdmin()) {
297 return $response
298 ->withStatus(301)
299 ->withHeader('Location', $this->routeparser->urlFor('slash'));
300 }
301 return $this->show($request, $response, $this->login->id);
302 }
303
304 /**
305 * Public pages (trombinoscope, public list)
306 *
307 * @param Request $request PSR Request
308 * @param Response $response PSR Response
309 * @param string|null $option One of 'page' or 'order'
310 * @param string|integer|null $value Value of the option
311 * @param string|null $type List type (either list or trombi)
312 *
313 * @return Response
314 */
315 public function publicList(
316 Request $request,
317 Response $response,
318 string $option = null,
319 string|int $value = null,
320 string $type = null
321 ): Response {
322 $varname = $this->getFilterName(['prefix' => 'public', 'suffix' => $type]);
323 if (isset($this->session->$varname)) {
324 $filters = $this->session->$varname;
325 } else {
326 $filters = new MembersList();
327 }
328
329 if ($option !== null) {
330 switch ($option) {
331 case 'page':
332 $filters->current_page = (int)$value;
333 break;
334 case 'order':
335 $filters->orderby = $value;
336 break;
337 }
338 }
339
340 $m = new Members($filters);
341 $members = $m->getPublicList($type === 'trombi');
342
343 $this->session->$varname = $filters;
344
345 //assign pagination variables to the template and add pagination links
346 $filters->setViewPagination($this->routeparser, $this->view, false);
347
348 // display page
349 $this->view->render(
350 $response,
351 ($type === 'list' ? 'pages/members_public_list' : 'pages/members_public_gallery') . '.html.twig',
352 array(
353 'page_title' => ($type === 'list' ? _T("Members list") : _T('Trombinoscope')),
354 'additionnal_html_class' => ($type === 'list' ? '' : 'trombinoscope'),
355 'type' => $type,
356 'members' => $members,
357 'nb_members' => $m->getCount(),
358 'filters' => $filters,
359 // pseudo random int
360 'time' => time(),
361 )
362 );
363 return $response;
364 }
365
366 /**
367 * Public pages (trombinoscope, public list)
368 *
369 * @param Request $request PSR Request
370 * @param Response $response PSR Response
371 * @param string $type Type
372 *
373 * @return Response
374 */
375 public function filterPublicList(Request $request, Response $response, string $type): Response
376 {
377 $post = $request->getParsedBody();
378
379 $varname = $this->getFilterName(['prefix' => 'public', 'suffix' => $type]);
380 if (isset($this->session->$varname)) {
381 $filters = $this->session->$varname;
382 } else {
383 $filters = new MembersList();
384 }
385
386 //reintialize filters
387 if (isset($post['clear_filter'])) {
388 $filters->reinit();
389 } else {
390 //number of rows to show
391 if (isset($post['nbshow'])) {
392 $filters->show = (int)$post['nbshow'];
393 }
394 }
395
396 $this->session->$varname = $filters;
397
398 return $response
399 ->withStatus(301)
400 ->withHeader('Location', $this->routeparser->urlFor('publicList', ['type' => $type]));
401 }
402
403 /**
404 * Members list
405 *
406 * @param Request $request PSR Request
407 * @param Response $response PSR Response
408 * @param string|null $option One of 'page' or 'order'
409 * @param integer|string|null $value Value of the option
410 *
411 * @return Response
412 */
413 public function list(Request $request, Response $response, string $option = null, int|string $value = null): Response
414 {
415 if (isset($this->session->{$this->getFilterName()})) {
416 $filters = $this->session->{$this->getFilterName()};
417 } else {
418 $filters = new MembersList();
419 }
420
421 if ($option !== null) {
422 switch ($option) {
423 case 'page':
424 $filters->current_page = (int)$value;
425 break;
426 case 'order':
427 $filters->orderby = $value;
428 break;
429 }
430 }
431
432 $members = new Members($filters);
433
434 $members_list = array();
435 if ($this->login->isAdmin() || $this->login->isStaff()) {
436 $members_list = $members->getMembersList(true);
437 } else {
438 $members_list = $members->getManagedMembersList(true);
439 }
440
441 $groups = new Groups($this->zdb, $this->login);
442 $groups_list = $groups->getList();
443
444 //assign pagination variables to the template and add pagination links
445 $filters->setViewPagination($this->routeparser, $this->view, false);
446 $filters->setViewCommonsFilters($this->preferences, $this->view);
447
448 $this->session->{$this->getFilterName()} = $filters;
449
450 // display page
451 $this->view->render(
452 $response,
453 'pages/members_list.html.twig',
454 array(
455 'page_title' => _T("Members management"),
456 'require_mass' => true,
457 'members' => $members_list,
458 'filter_groups_options' => $groups_list,
459 'nb_members' => $members->getCount(),
460 'filters' => $filters,
461 'adv_filters' => $filters instanceof AdvancedMembersList,
462 'galette_list' => $this->lists_config->getDisplayElements($this->login)
463 )
464 );
465 return $response;
466 }
467
468 /**
469 * Members filtering
470 *
471 * @param Request $request PSR Request
472 * @param Response $response PSR Response
473 *
474 * @return Response
475 */
476 public function filter(Request $request, Response $response): Response
477 {
478 $post = $request->getParsedBody();
479 $filters = $this->session->{$this->getFilterName()} ?? new MembersList();
480
481 //reinitialize filters
482 if (isset($post['clear_filter'])) {
483 $filters = new MembersList();
484 } elseif (isset($post['clear_adv_filter'])) {
485 $this->session->{$this->getFilterName()} = null;
486 unset($this->session->{$this->getFilterName()});
487
488 return $response
489 ->withStatus(301)
490 ->withHeader('Location', $this->routeparser->urlFor('advanced-search'));
491 } elseif (isset($post['adv_criteria'])) {
492 return $response
493 ->withStatus(301)
494 ->withHeader('Location', $this->routeparser->urlFor('advanced-search'));
495 } else {
496 //string to filter
497 if (isset($post['filter_str'])) { //filter search string
498 $filters->filter_str = stripslashes(
499 htmlspecialchars($post['filter_str'], ENT_QUOTES)
500 );
501 }
502 //field to filter
503 if (isset($post['field_filter'])) {
504 if (is_numeric($post['field_filter'])) {
505 $filters->field_filter = $post['field_filter'];
506 }
507 }
508 //membership to filter
509 if (isset($post['membership_filter'])) {
510 if (is_numeric($post['membership_filter'])) {
511 $filters->membership_filter
512 = $post['membership_filter'];
513 }
514 }
515 //account status to filter
516 if (isset($post['filter_account'])) {
517 if (is_numeric($post['filter_account'])) {
518 $filters->filter_account = $post['filter_account'];
519 }
520 }
521 //email filter
522 if (isset($post['email_filter'])) {
523 $filters->email_filter = (int)$post['email_filter'];
524 }
525 //group filter
526 if (
527 isset($post['group_filter'])
528 && $post['group_filter'] > 0
529 ) {
530 $filters->group_filter = (int)$post['group_filter'];
531 }
532 //number of rows to show
533 if (isset($post['nbshow'])) {
534 $filters->show = (int)$post['nbshow'];
535 }
536
537 if (isset($post['advanced_filtering'])) {
538 if (!$filters instanceof AdvancedMembersList) {
539 $filters = new AdvancedMembersList($filters);
540 }
541 //Advanced filters
542 $filters->reinit();
543 unset($post['advanced_filtering']);
544 $freed = false;
545 foreach ($post as $k => $v) {
546 if (strpos($k, 'free_', 0) === 0) {
547 if (!$freed) {
548 $i = 0;
549 foreach ($post['free_field'] as $f) {
550 if (
551 trim($f) !== ''
552 && trim($post['free_text'][$i]) !== ''
553 ) {
554 $fs_search = htmlspecialchars($post['free_text'][$i], ENT_QUOTES);
555 $log_op
556 = (int)$post['free_logical_operator'][$i];
557 $qry_op
558 = (int)$post['free_query_operator'][$i];
559 $type = (int)$post['free_type'][$i];
560 $fs = array(
561 'idx' => $i,
562 'field' => $f,
563 'type' => $type,
564 'search' => $fs_search,
565 'log_op' => $log_op,
566 'qry_op' => $qry_op
567 );
568 $filters->free_search = $fs;
569 }
570 $i++;
571 }
572 $freed = true;
573 }
574 } elseif ($k == 'groups_search') {
575 $i = 0;
576 $filters->groups_search_log_op = (int)$post['groups_logical_operator'];
577 foreach ($post['groups_search'] as $g) {
578 if (trim($g) !== '') {
579 $gs = array(
580 'idx' => $i,
581 'group' => $g
582 );
583 $filters->groups_search = $gs;
584 }
585 $i++;
586 }
587 } else {
588 switch ($k) {
589 case 'contrib_min_amount':
590 case 'contrib_max_amount':
591 if (trim($v) !== '') {
592 $v = (float)$v;
593 } else {
594 $v = null;
595 }
596 break;
597 }
598 $filters->$k = $v;
599 }
600 }
601 }
602 }
603
604 if (isset($post['savesearch'])) {
605 return $response
606 ->withStatus(301)
607 ->withHeader(
608 'Location',
609 $this->routeparser->urlFor(
610 'saveSearch',
611 $post
612 )
613 );
614 }
615
616 $this->session->{$this->getFilterName()} = $filters;
617
618 return $response
619 ->withStatus(301)
620 ->withHeader('Location', $this->routeparser->urlFor('members'));
621 }
622
623 /**
624 * Advanced search page
625 *
626 * @param Request $request PSR Request
627 * @param Response $response PSR Response
628 *
629 * @return Response
630 */
631 public function advancedSearch(Request $request, Response $response): Response
632 {
633 if (isset($this->session->{$this->getFilterName()})) {
634 $filters = $this->session->{$this->getFilterName()};
635 if (!$filters instanceof AdvancedMembersList) {
636 $filters = new AdvancedMembersList($filters);
637 }
638 } else {
639 $filters = new AdvancedMembersList();
640 }
641
642 $groups = new Groups($this->zdb, $this->login);
643 $groups_list = $groups->getList();
644
645 //we want only visible fields
646 $fields = $this->members_fields;
647 $fc = $this->fields_config;
648 $fc->filterVisible($this->login, $fields);
649
650 //dynamic fields
651 $member = new Adherent($this->zdb);
652 $member
653 ->disableAllDeps()
654 ->enableDep('dynamics')
655 ->loadFromLoginOrMail($this->login->login);
656 $adh_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $member);
657
658 $contrib = new Contribution($this->zdb, $this->login);
659 $contrib_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $contrib);
660
661 //Status
662 $statuts = new Status($this->zdb);
663
664 //Contributions types
665 $ct = new ContributionsTypes($this->zdb);
666
667 //Payment types
668 $ptypes = new PaymentTypes(
669 $this->zdb,
670 $this->preferences,
671 $this->login
672 );
673 $ptlist = $ptypes->getList();
674
675 $filters->setViewCommonsFilters($this->preferences, $this->view);
676
677 $social = new Social($this->zdb);
678 $types = $member->getMemberRegisteredTypes();
679 $social_types = [];
680 foreach ($types as $type) {
681 $social_types[$type] = $social->getSystemType($type);
682 }
683
684 // display page
685 $this->view->render(
686 $response,
687 'pages/advanced_search.html.twig',
688 array(
689 'page_title' => _T("Advanced search"),
690 'filter_groups_options' => $groups_list,
691 'search_fields' => $fields,
692 'adh_dynamics' => $adh_dynamics->getSearchFields(),
693 'contrib_dynamics' => $contrib_dynamics->getSearchFields(),
694 'adh_socials' => $social_types,
695 'statuts' => $statuts->getList(),
696 'contributions_types' => $ct->getList(),
697 'filters' => $filters,
698 'payments_types' => $ptlist
699 )
700 );
701 return $response;
702 }
703
704 /**
705 * Members list for ajax
706 *
707 * @param Request $request PSR Request
708 * @param Response $response PSR Response
709 * @param string|null $option One of 'page' or 'order'
710 * @param string|integer|null $value Value of the option
711 *
712 * @return Response
713 */
714 public function ajaxList(Request $request, Response $response, string $option = null, string|int $value = null): Response
715 {
716 $post = $request->getParsedBody();
717
718 $filters = $this->session->{$this->getFilterName(['prefix' => 'ajax'])} ?? new MembersList();
719
720 if ($option == 'page') {
721 $filters->current_page = (int)$value;
722 }
723
724 //numbers of rows to display
725 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
726 $filters->show = (int)$post['nbshow'];
727 }
728
729 $members = new Members($filters);
730 if (!$this->login->isAdmin() && !$this->login->isStaff()) {
731 if ($this->login->isGroupManager()) {
732 $members_list = $members->getManagedMembersList(true);
733 } else {
734 Analog::log(
735 str_replace(
736 ['%id', '%login'],
737 [$this->login->id, $this->login->login],
738 'Trying to list group members without access from #%id (%login)'
739 ),
740 Analog::ERROR
741 );
742 throw new \Exception('Access denied.');
743 }
744 } else {
745 $members_list = $members->getMembersList(true);
746 }
747
748 //assign pagination variables to the template and add pagination links
749 $filters->setViewPagination($this->routeparser, $this->view, false);
750
751 $this->session->{$this->getFilterName(['prefix' => 'ajax'])} = $filters;
752
753 $selected_members = null;
754 $unreachables_members = null;
755 if (!isset($post['from'])) {
756 $mailing = $this->session->mailing;
757 if (!isset($post['members'])) {
758 $selected_members = $mailing->recipients;
759 $unreachables_members = $mailing->unreachables;
760 } else {
761 $m = new Members();
762 $selected_members = $m->getArrayList($post['members']);
763 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
764 $unreachables_members = $m->getArrayList($post['unreachables']);
765 }
766 }
767 } else {
768 switch ($post['from']) {
769 case 'groups':
770 if (!isset($post['gid'])) {
771 Analog::log(
772 'Trying to list group members with no group id provided',
773 Analog::ERROR
774 );
775 throw new \Exception('A group id is required.');
776 }
777 if (!isset($post['members'])) {
778 $group = new Group((int)$post['gid']);
779 $selected_members = array();
780 if (!isset($post['mode']) || $post['mode'] == 'members') {
781 $selected_members = $group->getMembers();
782 } elseif ($post['mode'] == 'managers') {
783 $selected_members = $group->getManagers();
784 } else {
785 Analog::log(
786 'Trying to list group members with unknown mode',
787 Analog::ERROR
788 );
789 throw new \Exception('Unknown mode.');
790 }
791 } else {
792 $m = new Members();
793 $selected_members = $m->getArrayList($post['members']);
794 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
795 $unreachables_members = $m->getArrayList($post['unreachables']);
796 }
797 }
798 break;
799 case 'attach':
800 if (!isset($post['id_adh'])) {
801 throw new \RuntimeException(
802 'Current selected member must be excluded while attaching!'
803 );
804 }
805 break;
806 }
807 }
808
809 $params = [
810 'filters' => $filters,
811 'members_list' => $members_list,
812 'selected_members' => $selected_members,
813 'unreachables_members' => $unreachables_members
814 ];
815
816 if (isset($post['multiple'])) {
817 $params['multiple'] = true;
818 }
819
820 if (isset($post['gid'])) {
821 $params['the_id'] = (int)$post['gid'];
822 }
823
824 if (isset($post['id_adh'])) {
825 $params['excluded'] = (int)$post['id_adh'];
826 }
827
828 // display page
829 $this->view->render(
830 $response,
831 'elements/ajax_members.html.twig',
832 $params
833 );
834 return $response;
835 }
836
837 /**
838 * Batch actions handler
839 *
840 * @param Request $request PSR Request
841 * @param Response $response PSR Response
842 *
843 * @return Response
844 */
845 public function handleBatch(Request $request, Response $response): Response
846 {
847 $post = $request->getParsedBody();
848
849 if (isset($post['entries_sel'])) {
850 if (isset($this->session->{$this->getFilterName()})) {
851 $filters = $this->session->{$this->getFilterName()};
852 } else {
853 $filters = new MembersList();
854 }
855
856 $filters->selected = $post['entries_sel'];
857 $knowns = [
858 'cards' => 'pdf-members-cards',
859 'labels' => 'pdf-members-labels',
860 'sendmail' => 'mailing',
861 'attendance_sheet' => 'attendance_sheet_details',
862 'csv' => 'csv-memberslist',
863 'delete' => 'removeMembers',
864 'masschange' => 'masschangeMembers',
865 'masscontributions' => 'massAddContributionsChooseType'
866 ];
867
868 foreach ($knowns as $known => $redirect_url) {
869 if (isset($post[$known])) {
870 $this->session->{$this->getFilterName(['suffix' => $known])} = $filters;
871 $redirect_url = $this->routeparser->urlFor($redirect_url);
872 if ($known === 'sendmail') {
873 $redirect_url .= '?mailing_new=new';
874 }
875 return $response
876 ->withStatus(301)
877 ->withHeader('Location', $redirect_url);
878 }
879 }
880
881 throw new \RuntimeException('Does not know what to batch :(');
882 } else {
883 $this->flash->addMessage(
884 'error_detected',
885 _T("No member was selected, please check at least one name.")
886 );
887
888 return $response
889 ->withStatus(301)
890 ->withHeader('Location', $this->routeparser->urlFor('members'));
891 }
892 }
893
894 // /CRUD - Read
895 // CRUD - Update
896
897 /**
898 * Edit page
899 *
900 * @param Request $request PSR Request
901 * @param Response $response PSR Response
902 * @param ?integer $id Member id/array of members id
903 * @param string $action null or 'add'
904 *
905 * @return Response
906 */
907 public function edit(
908 Request $request,
909 Response $response,
910 int $id = null,
911 string $action = 'edit'
912 ): Response {
913 //instantiate member object
914 $member = new Adherent($this->zdb);
915
916 if ($this->session->member !== null) {
917 //retrieve from session, in add or edit
918 $member = $this->session->member;
919 $this->session->member = null;
920 $id = $member->id;
921 }
922 $member->enableAllDeps();
923
924 if ($id !== null) {
925 //load requested member
926 $member->load($id);
927 $can = $member->canEdit($this->login);
928 } else {
929 $can = $member->canCreate($this->login);
930 }
931
932 if (!$can) {
933 $this->flash->addMessage(
934 'error_detected',
935 _T("You do not have permission for requested URL.")
936 );
937
938 return $response
939 ->withHeader(
940 'Location',
941 $this->routeparser->urlFor('me')
942 );
943 }
944
945 //if adding a child, force parent here
946 if ($action === 'addchild') {
947 $member->setParent((int)$this->login->id);
948 }
949
950 // flagging required fields
951 $fc = $this->fields_config;
952
953 // password required if we create a new member
954 if ($id !== null) {
955 $fc->setNotRequired('mdp_adh');
956 }
957
958 //handle requirements for parent fields
959 $parent_fields = $member->getParentFields();
960 $tpl_parent_fields = []; //for JS when detaching
961 foreach ($parent_fields as $field) {
962 if ($fc->isRequired($field)) {
963 $tpl_parent_fields[] = $field;
964 if ($member->hasParent()) {
965 $fc->setNotRequired($field);
966 }
967 }
968 }
969
970 // flagging required fields invisible to members
971 if ($this->login->isAdmin() || $this->login->isStaff()) {
972 $fc->setNotRequired('activite_adh');
973 $fc->setNotRequired('id_statut');
974 }
975
976 // template variable declaration
977 $title = _T("Member Profile");
978 if ($member->id != '') {
979 $title .= ' (' . _T("modification") . ')';
980 } else {
981 $title .= ' (' . _T("creation") . ')';
982 }
983
984 //Status
985 $statuts = new Status($this->zdb);
986 //Titles
987 $titles = new Titles($this->zdb);
988
989 //Groups
990 $groups = new Groups($this->zdb, $this->login);
991 $groups_list = $groups->getSimpleList(true);
992
993 $form_elements = $fc->getFormElements(
994 $this->login,
995 $id === null
996 );
997
998 // members
999 $m = new Members();
1000 $pid = null;
1001 if ($member->hasParent()) {
1002 $pid = ($member->parent instanceof Adherent ? $member->parent->id : $member->parent);
1003 }
1004 $members = $m->getDropdownMembers(
1005 $this->zdb,
1006 $this->login,
1007 $pid
1008 );
1009
1010 $route_params['members'] = [
1011 'filters' => $m->getFilters(),
1012 'count' => $m->getCount()
1013 ];
1014
1015 if (count($members)) {
1016 $route_params['members']['list'] = $members;
1017 }
1018
1019 if ($action === 'edit') {
1020 $route_params['navigate'] = $this->handleNavigationLinks($member->id);
1021 }
1022
1023 // display page
1024 $this->view->render(
1025 $response,
1026 'pages/member_form.html.twig',
1027 array(
1028 'parent_tpl' => 'page.html.twig',
1029 'autocomplete' => true,
1030 'page_title' => $title,
1031 'member' => $member,
1032 'self_adh' => false,
1033 // pseudo random int
1034 'time' => time(),
1035 'titles_list' => $titles->getList(),
1036 'statuts' => $statuts->getList(),
1037 'groups' => $groups_list,
1038 'fieldsets' => $form_elements['fieldsets'],
1039 'hidden_elements' => $form_elements['hiddens'],
1040 'parent_fields' => $tpl_parent_fields,
1041 'addchild' => ($action === 'addchild'),
1042 'osocials' => new Social($this->zdb)
1043 ) + $route_params
1044 );
1045 return $response;
1046 }
1047
1048 /**
1049 * Edit action
1050 *
1051 * @param Request $request PSR Request
1052 * @param Response $response PSR Response
1053 * @param integer $id Member id
1054 *
1055 * @return Response
1056 */
1057 public function doEdit(Request $request, Response $response, int $id): Response
1058 {
1059 return $this->store($request, $response);
1060 }
1061
1062 /**
1063 * Massive change page
1064 *
1065 * @param Request $request PSR Request
1066 * @param Response $response PSR Response
1067 *
1068 * @return Response
1069 */
1070 public function massChange(Request $request, Response $response): Response
1071 {
1072 $filters = $this->session->{$this->getFilterName(['suffix' => 'masschange'])} ?? new MembersList();
1073
1074 $data = [
1075 'id' => $filters->selected,
1076 'redirect_uri' => $this->routeparser->urlFor('members')
1077 ];
1078
1079 $fc = $this->fields_config;
1080 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1081
1082 //dynamic fields
1083 $member = new Adherent($this->zdb);
1084 $member->disableAllDeps()->enableDep('dynamics');
1085
1086 //Status
1087 $statuts = new Status($this->zdb);
1088 //Titles
1089 $titles = new Titles($this->zdb);
1090
1091 // display page
1092 $this->view->render(
1093 $response,
1094 'modals/mass_change_members.html.twig',
1095 array(
1096 'mode' => ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') ? 'ajax' : '',
1097 'page_title' => str_replace(
1098 '%count',
1099 (string)count($data['id']),
1100 _T('Mass change %count members')
1101 ),
1102 'form_url' => $this->routeparser->urlFor('masschangeMembersReview'),
1103 'cancel_uri' => $this->routeparser->urlFor('members'),
1104 'data' => $data,
1105 'member' => $member,
1106 'fieldsets' => $form_elements['fieldsets'],
1107 'titles_list' => $titles->getList(),
1108 'statuts' => $statuts->getList(),
1109 'require_mass' => true
1110 )
1111 );
1112 return $response;
1113 }
1114
1115 /**
1116 * Massive changes validation page
1117 *
1118 * @param Request $request PSR Request
1119 * @param Response $response PSR Response
1120 *
1121 * @return Response
1122 */
1123 public function validateMassChange(Request $request, Response $response): Response
1124 {
1125 $post = $request->getParsedBody();
1126 $changes = [];
1127
1128 if (!isset($post['confirm'])) {
1129 $this->flash->addMessage(
1130 'error_detected',
1131 _T("Mass changes has not been confirmed!")
1132 );
1133 } else {
1134 //we want only visibles fields
1135 $fc = $this->fields_config;
1136 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1137
1138 foreach ($form_elements['fieldsets'] as $form_element) {
1139 foreach ($form_element->elements as $field) {
1140 if (
1141 isset($post['mass_' . $field->field_id])
1142 && (isset($post[$field->field_id]) || $field->type === FieldsConfig::TYPE_BOOL)
1143 ) {
1144 $changes[$field->field_id] = [
1145 'label' => $field->label,
1146 'value' => $post[$field->field_id] ?? 0
1147 ];
1148 }
1149 }
1150 }
1151
1152 //handle dynamic fields
1153 $member = new Adherent($this->zdb);
1154 $member
1155 ->enableAllDeps()
1156 ->setDependencies(
1157 $this->preferences,
1158 $this->members_fields,
1159 $this->history
1160 );
1161 $dynamic_fields = $member->getDynamicFields()->getFields();
1162 foreach ($dynamic_fields as $field) {
1163 $mass_id = 'mass_info_field_' . $field->getId();
1164 $field_id = 'info_field_' . $field->getId() . '_1';
1165 if (
1166 isset($post[$mass_id])
1167 && (isset($post[$field_id]) || $field instanceof Boolean)
1168 ) {
1169 $changes[$field_id] = [
1170 'label' => $field->getName(),
1171 'value' => $post[$field_id] ?? 0
1172 ];
1173 }
1174 }
1175 }
1176
1177 $filters = $this->session->{$this->getFilterName(['suffix' => 'masschange'])};
1178 $data = [
1179 'id' => $filters->selected,
1180 'redirect_uri' => $this->routeparser->urlFor('members')
1181 ];
1182
1183 //Status
1184 $statuts = new Status($this->zdb);
1185 //Titles
1186 $titles = new Titles($this->zdb);
1187
1188 // display page
1189 $this->view->render(
1190 $response,
1191 'modals/mass_change_members.html.twig',
1192 array(
1193 'mode' => ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') ? 'ajax' : '',
1194 'page_title' => str_replace(
1195 '%count',
1196 (string)count($data['id']),
1197 _T('Review mass change %count members')
1198 ),
1199 'form_url' => $this->routeparser->urlFor('massstoremembers'),
1200 'cancel_uri' => $this->routeparser->urlFor('members'),
1201 'data' => $data,
1202 'titles_list' => $titles->getList(),
1203 'statuts' => $statuts->getList(),
1204 'changes' => $changes
1205 )
1206 );
1207 return $response;
1208 }
1209
1210 /**
1211 * Do massive changes
1212 *
1213 * @param Request $request PSR Request
1214 * @param Response $response PSR Response
1215 *
1216 * @return Response
1217 */
1218 public function doMassChange(Request $request, Response $response): Response
1219 {
1220 $post = $request->getParsedBody();
1221 $redirect_url = $post['redirect_uri'];
1222 $error_detected = [];
1223 $mass = 0;
1224 $dynamic_fields = null;
1225
1226 unset($post['redirect_uri']);
1227 if (!isset($post['confirm'])) {
1228 $error_detected[] = _T("Mass changes has not been confirmed!");
1229 } else {
1230 unset($post['confirm']);
1231 $ids = $post['id'];
1232 unset($post['id']);
1233
1234 $fc = $this->fields_config;
1235 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1236 $disabled = $this->members_fields;
1237 foreach (array_keys($post) as $key) {
1238 $found = false;
1239 foreach ($form_elements['fieldsets'] as $fieldset) {
1240 if (isset($fieldset->elements[$key])) {
1241 $found = true;
1242 break;
1243 }
1244 }
1245
1246 if (!$found) {
1247 //try on dynamic fields
1248 if ($dynamic_fields === null) {
1249 //handle dynamic fields
1250 $member = new Adherent($this->zdb);
1251 $member
1252 ->enableAllDeps()
1253 ->setDependencies(
1254 $this->preferences,
1255 $this->members_fields,
1256 $this->history
1257 );
1258 $dynamic_fields = $member->getDynamicFields()->getFields();
1259 }
1260 foreach ($dynamic_fields as $field) {
1261 $field_id = 'info_field_' . $field->getId() . '_1';
1262 if ($key == $field_id) {
1263 $found = true;
1264 break;
1265 }
1266 }
1267 }
1268
1269 if (!$found) {
1270 Analog::log(
1271 'Permission issue mass editing field ' . $key,
1272 Analog::WARNING
1273 );
1274 unset($post[$key]);
1275 } else {
1276 unset($disabled[$key]);
1277 }
1278 }
1279
1280 if (!count($post)) {
1281 $error_detected[] = _T("Nothing to do!");
1282 } else {
1283 foreach ($ids as $id) {
1284 $is_manager = !$this->login->isAdmin()
1285 && !$this->login->isStaff()
1286 && $this->login->isGroupManager();
1287 $member = new Adherent($this->zdb);
1288 $member
1289 ->disableAllDeps()
1290 ->disableEvents();
1291 if ($is_manager) {
1292 $member->enableDep('groups');
1293 }
1294 $member->load((int)$id);
1295 $member->setDependencies(
1296 $this->preferences,
1297 $this->members_fields,
1298 $this->history
1299 );
1300 if (!$member->canEdit($this->login)) {
1301 continue;
1302 }
1303
1304 $valid = $member->check($post, [], $disabled);
1305 if ($valid === true) {
1306 $done = $member->store();
1307 if (!$done) {
1308 $error_detected[] = _T("An error occurred while storing the member.");
1309 } else {
1310 ++$mass;
1311 }
1312 } else {
1313 $error_detected = array_merge($error_detected, $valid);
1314 }
1315 }
1316 }
1317 }
1318
1319 if ($mass == 0 && !count($error_detected)) {
1320 $error_detected[] = _T('Something went wront during mass edition!');
1321 } else {
1322 $this->flash->addMessage(
1323 'success_detected',
1324 sprintf(
1325 //TRANS: first parameter is the number of edited members
1326 _Tn(
1327 '%1$s member has been changed successfully!',
1328 '%1$s members has been changed successfully!',
1329 $mass
1330 ),
1331 $mass
1332 )
1333 );
1334 }
1335
1336 if (count($error_detected) > 0) {
1337 foreach ($error_detected as $error) {
1338 $this->flash->addMessage(
1339 'error_detected',
1340 $error
1341 );
1342 }
1343 }
1344
1345 if (!($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest')) {
1346 return $response
1347 ->withStatus(301)
1348 ->withHeader('Location', $redirect_url);
1349 } else {
1350 return $this->withJson(
1351 $response,
1352 [
1353 'success' => count($error_detected) === 0
1354 ]
1355 );
1356 }
1357 }
1358
1359 /**
1360 * Store
1361 *
1362 * @param Request $request PSR Request
1363 * @param Response $response PSR Response
1364 *
1365 * @return Response
1366 */
1367 public function store(Request $request, Response $response): Response
1368 {
1369 if (!$this->preferences->pref_bool_selfsubscribe && !$this->login->isLogged()) {
1370 return $response
1371 ->withStatus(301)
1372 ->withHeader('Location', $this->routeparser->urlFor('slash'));
1373 }
1374
1375 $post = $request->getParsedBody();
1376 $member = new Adherent($this->zdb);
1377 $member
1378 ->enableAllDeps()
1379 ->setDependencies(
1380 $this->preferences,
1381 $this->members_fields,
1382 $this->history
1383 );
1384
1385 $success_detected = [];
1386 $error_detected = [];
1387
1388 if ($this->isSelfMembership() && !isset($post[Adherent::PK])) {
1389 //mark as self membership
1390 $member->setSelfMembership();
1391
1392 //check captcha
1393 /** @var Gaptcha $gaptcha */
1394 $gaptcha = $this->session->gaptcha;
1395 if (!$gaptcha->check($post['gaptcha'])) {
1396 $error_detected[] = _T('Invalid captcha');
1397 }
1398 }
1399
1400 // new or edit
1401 if (isset($post['id_adh'])) {
1402 $member->load((int)$post['id_adh']);
1403 if (!$member->canEdit($this->login)) {
1404 //redirection should have been done before. Just throw an Exception.
1405 throw new \RuntimeException(
1406 str_replace(
1407 '%id',
1408 (string)$member->id,
1409 'No right to store member #%id'
1410 )
1411 );
1412 }
1413 } else {
1414 if ($member->id != '') {
1415 $member->load($this->login->id);
1416 }
1417 }
1418
1419 // flagging required fields
1420 $fc = $this->fields_config;
1421
1422 // password required if we create a new member but not from self subscription
1423 if ($member->id != '' || $this->isSelfMembership()) {
1424 $fc->setNotRequired('mdp_adh');
1425 }
1426
1427 if (
1428 $member->hasParent() && !isset($post['detach_parent'])
1429 || isset($post['parent_id']) && !empty($post['parent_id'])
1430 ) {
1431 $parent_fields = $member->getParentFields();
1432 foreach ($parent_fields as $field) {
1433 if ($fc->isRequired($field)) {
1434 $fc->setNotRequired($field);
1435 }
1436 }
1437 }
1438
1439 // flagging required fields invisible to members
1440 if ($this->login->isAdmin() || $this->login->isStaff()) {
1441 $fc->setNotRequired('activite_adh');
1442 $fc->setNotRequired('id_statut');
1443 }
1444
1445 $form_elements = $fc->getFormElements(
1446 $this->login,
1447 $member->id == '',
1448 $this->isSelfMembership()
1449 );
1450 $fieldsets = $form_elements['fieldsets'];
1451 $required = array();
1452 $disabled = array();
1453
1454 foreach ($fieldsets as $category) {
1455 foreach ($category->elements as $field) {
1456 if ($field->required) {
1457 $required[$field->field_id] = true;
1458 }
1459 if ($field->disabled) {
1460 $disabled[] = $field->field_id;
1461 } elseif (!isset($post[$field->field_id])) {
1462 switch ($field->field_id) {
1463 //unchecked booleans are not sent from form
1464 case 'bool_admin_adh':
1465 case 'bool_exempt_adh':
1466 case 'bool_display_info':
1467 $post[$field->field_id] = 0;
1468 break;
1469 }
1470 }
1471 }
1472 }
1473
1474 $real_requireds = array_diff(array_keys($required), array_values($disabled));
1475
1476 // send email to member
1477 if ($this->isSelfMembership() || isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1478 $member->setSendmail(); //flag to send creation email
1479 }
1480
1481 // Validation
1482 $redirect_url = $this->routeparser->urlFor('member', ['id' => (string)$member->id]);
1483 if (!count($real_requireds) || isset($post[array_shift($real_requireds)])) {
1484 // regular fields
1485 $valid = $member->check($post, $required, $disabled);
1486 if ($valid !== true) {
1487 $error_detected = array_merge($error_detected, $valid);
1488 }
1489
1490 if (count($error_detected) == 0) {
1491 //all goes well, we can proceed
1492
1493 $new = false;
1494 if ($member->id == '') {
1495 $new = true;
1496 }
1497
1498 $store = $member->store();
1499 if ($store === true) {
1500 //member has been stored :)
1501 if ($new) {
1502 if ($this->isSelfMembership()) {
1503 $success_detected[] = _T("Your account has been created!");
1504 if (
1505 $this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1506 && $member->getEmail() != ''
1507 ) {
1508 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1509 }
1510 } else {
1511 $success_detected[] = _T("New member has been successfully added.");
1512 }
1513 } else {
1514 $success_detected[] = _T("Member account has been modified.");
1515 }
1516
1517 if ($this->login->isGroupManager()) {
1518 //add/remove user from groups
1519 $groups_adh = $post['groups_adh'] ?? [];
1520 $add_groups = Groups::addMemberToGroups(
1521 $member,
1522 $groups_adh
1523 );
1524
1525 if ($add_groups === false) {
1526 $error_detected[] = _T("An error occurred adding member to its groups.");
1527 }
1528 }
1529 if ($this->login->isSuperAdmin() || $this->login->isAdmin() || $this->login->isStaff()) {
1530 //add/remove manager from groups
1531 $managed_groups_adh = $post['groups_managed_adh'] ?? [];
1532 $add_groups = Groups::addMemberToGroups(
1533 $member,
1534 $managed_groups_adh,
1535 true
1536 );
1537 $member->loadGroups();
1538
1539 if ($add_groups === false) {
1540 $error_detected[] = _T("An error occurred adding member to its groups as manager.");
1541 }
1542 }
1543 } else {
1544 //something went wrong :'(
1545 $error_detected[] = _T("An error occurred while storing the member.");
1546 }
1547 }
1548
1549 if (count($error_detected) === 0) {
1550 $cropping = null;
1551 if ($this->preferences->pref_force_picture_ratio == 1) {
1552 $cropping = [];
1553 $cropping['ratio'] = isset($this->preferences->pref_member_picture_ratio) ? $this->preferences->pref_member_picture_ratio : 'square_ratio';
1554 $cropping['focus'] = isset($post['crop_focus']) ? $post['crop_focus'] : 'center';
1555 }
1556 $files_res = $member->handleFiles($_FILES, $cropping);
1557 if (is_array($files_res)) {
1558 $error_detected = array_merge($error_detected, $files_res);
1559 }
1560
1561 if (isset($post['del_photo'])) {
1562 if (!$member->picture->delete()) {
1563 $error_detected[] = _T("Delete failed");
1564 $str_adh = $member->id . ' (' . $member->sname . ' ' . ')';
1565 Analog::log(
1566 'Unable to delete picture for member ' . $str_adh,
1567 Analog::ERROR
1568 );
1569 }
1570 }
1571 }
1572
1573 if (count($error_detected) > 0) {
1574 foreach ($error_detected as $error) {
1575 if (strpos($error, '%member_url_') !== false) {
1576 preg_match('/%member_url_(\d+)/', $error, $matches);
1577 $url = $this->routeparser->urlFor('member', ['id' => $matches[1]]);
1578 $error = str_replace(
1579 '%member_url_' . $matches[1],
1580 $url,
1581 $error
1582 );
1583 }
1584 $this->flash->addMessage(
1585 'error_detected',
1586 $error
1587 );
1588 }
1589 }
1590
1591 if (count($success_detected) > 0) {
1592 foreach ($success_detected as $success) {
1593 $this->flash->addMessage(
1594 'success_detected',
1595 $success
1596 );
1597 }
1598 }
1599
1600 if (count($error_detected) === 0) {
1601 if ($this->isSelfMembership()) {
1602 $redirect_url = $this->routeparser->urlFor('login');
1603 } elseif (
1604 isset($post['redirect_on_create'])
1605 && $post['redirect_on_create'] > Adherent::AFTER_ADD_DEFAULT
1606 ) {
1607 switch ($post['redirect_on_create']) {
1608 case Adherent::AFTER_ADD_TRANS:
1609 $redirect_url = $this->routeparser->urlFor('addTransaction');
1610 break;
1611 case Adherent::AFTER_ADD_NEW:
1612 $redirect_url = $this->routeparser->urlFor('addMember');
1613 break;
1614 case Adherent::AFTER_ADD_SHOW:
1615 $redirect_url = $this->routeparser->urlFor('member', ['id' => (string)$member->id]);
1616 break;
1617 case Adherent::AFTER_ADD_LIST:
1618 $redirect_url = $this->routeparser->urlFor('members');
1619 break;
1620 case Adherent::AFTER_ADD_HOME:
1621 $redirect_url = $this->routeparser->urlFor('slash');
1622 break;
1623 }
1624 } elseif (!isset($post['id_adh']) && !$member->isDueFree()) {
1625 $redirect_url = $this->routeparser->urlFor(
1626 'addContribution',
1627 ['type' => 'fee']
1628 ) . '?id_adh=' . $member->id;
1629 } else {
1630 $redirect_url = $this->routeparser->urlFor('member', ['id' => (string)$member->id]);
1631 }
1632 } else {
1633 //store entity in session
1634 $this->session->member = $member;
1635
1636 if ($this->isSelfMembership()) {
1637 $redirect_url = $this->routeparser->urlFor('subscribe');
1638 } else {
1639 if ($member->id) {
1640 $redirect_url = $this->routeparser->urlFor(
1641 'editMember',
1642 ['id' => (string)$member->id]
1643 );
1644 } else {
1645 $redirect_url = $this->routeparser->urlFor((isset($post['addchild']) ? 'addMemberChild' : 'addMember'));
1646 }
1647 }
1648 }
1649 }
1650
1651 return $response
1652 ->withStatus(301)
1653 ->withHeader('Location', $redirect_url);
1654 }
1655
1656
1657 // /CRUD - Update
1658 // CRUD - Delete
1659
1660 /**
1661 * Get redirection URI
1662 *
1663 * @param array<string,mixed> $args Route arguments
1664 *
1665 * @return string
1666 */
1667 public function redirectUri(array $args): string
1668 {
1669 return $this->routeparser->urlFor('members');
1670 }
1671
1672 /**
1673 * Get form URI
1674 *
1675 * @param array<string,mixed> $args Route arguments
1676 *
1677 * @return string
1678 */
1679 public function formUri(array $args): string
1680 {
1681 return $this->routeparser->urlFor(
1682 'doRemoveMember',
1683 $args
1684 );
1685 }
1686
1687 /**
1688 * Get confirmation removal page title
1689 *
1690 * @param array<string,mixed> $args Route arguments
1691 *
1692 * @return string
1693 */
1694 public function confirmRemoveTitle(array $args): string
1695 {
1696 if (isset($args['id_adh']) || isset($args['id'])) {
1697 //one member removal
1698 $id_adh = $args['id_adh'] ?? $args['id'];
1699 $adh = new Adherent($this->zdb, (int)$id_adh);
1700 return sprintf(
1701 _T('Remove member %1$s'),
1702 $adh->sfullname
1703 );
1704 } else {
1705 //batch members removal
1706 $filters = $this->session->{$this->getFilterName(['suffix' => 'delete'])};
1707 $this->session->{$this->getFilterName(['suffix' => 'delete'])} = $filters;
1708 return str_replace(
1709 '%count',
1710 (string)count($filters->selected),
1711 _T('You are about to remove %count members.')
1712 );
1713 }
1714 }
1715
1716 /**
1717 * Remove object
1718 *
1719 * @param array<string,mixed> $args Route arguments
1720 * @param array<string,mixed> $post POST values
1721 *
1722 * @return bool
1723 */
1724 protected function doDelete(array $args, array $post): bool
1725 {
1726 if (isset($this->session->{$this->getFilterName(['suffix' => 'delete'])})) {
1727 $filters = $this->session->{$this->getFilterName(['suffix' => 'delete'])};
1728 } else {
1729 $filters = new MembersList();
1730 }
1731 $members = new Members($filters);
1732
1733 if (!is_array($post['id'])) {
1734 $ids = (array)$post['id'];
1735 } else {
1736 $ids = $post['id'];
1737 }
1738
1739 return $members->removeMembers($ids);
1740 }
1741
1742 // CRUD - Delete
1743
1744 /**
1745 * Set self memebrship flag
1746 *
1747 * @return MembersController
1748 */
1749 private function setSelfMembership(): MembersController
1750 {
1751 $this->is_self_membership = true;
1752 return $this;
1753 }
1754
1755 /**
1756 * Is self membership?
1757 *
1758 * @return bool
1759 */
1760 private function isSelfMembership(): bool
1761 {
1762 return $this->is_self_membership;
1763 }
1764
1765 /**
1766 * Handle navigation links
1767 *
1768 * @param int $id_adh Current member ID
1769 *
1770 * @return array<string,int>
1771 */
1772 private function handleNavigationLinks(int $id_adh): array
1773 {
1774 $navigate = array();
1775
1776 if (isset($this->session->{$this->getFilterName()})) {
1777 $filters = clone $this->session->{$this->getFilterName()};
1778 } else {
1779 $filters = new MembersList();
1780 }
1781 //we must navigate between all members
1782 $filters->show = 0;
1783
1784 if (
1785 $this->login->isAdmin()
1786 || $this->login->isStaff()
1787 || $this->login->isGroupManager()
1788 ) {
1789 $m = new Members($filters);
1790
1791 $ids = array();
1792 $fields = [Adherent::PK, 'nom_adh', 'prenom_adh'];
1793 if ($this->login->isAdmin() || $this->login->isStaff()) {
1794 $ids = $m->getMembersList(false, $fields);
1795 } else {
1796 $ids = $m->getManagedMembersList(false, $fields);
1797 }
1798
1799 $ids = $ids->toArray();
1800 foreach ($ids as $k => $m) {
1801 if ($m['id_adh'] == $id_adh) {
1802 $navigate = array(
1803 'cur' => $m['id_adh'],
1804 'count' => $filters->counter,
1805 'pos' => $k + 1
1806 );
1807 if ($k > 0) {
1808 $navigate['prev'] = $ids[$k - 1]['id_adh'];
1809 }
1810 if ($k < count($ids) - 1) {
1811 $navigate['next'] = $ids[$k + 1]['id_adh'];
1812 }
1813 break;
1814 }
1815 }
1816 }
1817
1818 return $navigate;
1819 }
1820
1821 /**
1822 * Get filter name in session
1823 *
1824 * @param array<string,mixed>|null $args Route arguments
1825 *
1826 * @return string
1827 */
1828 public function getFilterName(array $args = null): string
1829 {
1830 $filter_name = 'filter_members';
1831
1832 if (isset($args['prefix'])) {
1833 $filter_name = $args['prefix'] . '_' . $filter_name;
1834 }
1835
1836 if (isset($args['suffix'])) {
1837 $filter_name .= '_' . $args['suffix'];
1838 }
1839
1840 return $filter_name;
1841 }
1842 }