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