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