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