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