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