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