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