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