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