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