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