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